Skip to main content

Mapping

October 7, 2025About 2 min

Mapping

When an API talks to the outside world, it shouldn't leak its domain internals. Mapping gives us a clean boundary:

  • Encapsulation & safety – Expose only what clients need (DTOs), hide domain-only fields, and avoid over-posting attacks.
  • Stable contracts – Evolve domain models without breaking clients; version DTOs independently.
  • Clear invariants – Create/modify domain objects via explicit logic; don't let raw HTTP payloads shape your core.
  • Consistency & DRY – Centralize transformation rules instead of hand-writing the same conversions in controllers.
  • Testability – Map rules live in one place and can be unit-tested.

How we use AutoMapper (in our architecture)

  • Put mapping profiles in the Application layer (it sees Domain + Contracts).
  • Use AutoMapper primarily for Domain → Contracts (read models / responses).
  • The Use Case classes inject the IMapper interface

Implementation

  • Install the AutoMapper and the Microsoft.Extensions.Configuration nuget package in the application library
  • Get a license key for AutoMapperopen in new window and add it to your appsettings.Development.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "ConnectionStrings": {
    "BricksForKidsDBConnectionString": "Connection string"
  },
  "AutoMapperLicenseKey": "Your key comes here"
}










 

  • Create a DependencyInjection class (similar to the one in our infrastructure library)
namespace SweDemoBackend.Application
{
  public static class DependencyInjection
  {
    public static IServiceCollection AddApplication(this IServiceCollection services, IConfiguration configuration)
    {
      // Scans the Application assembly for Profile classes
      services.AddAutoMapper(cfg => cfg.LicenseKey = configuration["AutoMapperLicenseKey"], Assembly.GetExecutingAssembly());

      // Add use cases
      services.AddScoped<LegoSetUseCase>();

      return services;
    }
  }
}
  • Call this extension method in the Program.cs of the API.

Mapping profiles

Instead of manually creating our DTOs from our Entities we want to create Mapping Profiles. No longer this:

namespace SweDemoBackend.Application.UseCases
{
  public class LegoSetUseCase
  {
    private readonly ILegoSetRepository _repo;

    public LegoSetUseCase(ILegoSetRepository repo)
    {
      _repo = repo;
    }

    public async Task<IEnumerable<LegoSetDto>> GetAllLegoSets(CancellationToken ct = default)
    {
      var entities = await _repo.GetLegoSetsAsync(ct);

      //Mapping
      List<LegoSetDto> dtos = new List<LegoSetDto>();

      entities.ToList().ForEach(e =>
      dtos.Add(new LegoSetDto()
      {
        Id = e.Id,
        Name = e.Name,
        NumberOfPieces = e.NumberOfPieces,
      }));

      return dtos;
    }
  }
}
















 
 
 
 
 
 
 
 
 





Create a LegoSetProfile class in the application library and extend from the Profile class (from AutoMapper):

namespace SweDemoBackend.Application.MappingProfiles
{
  public class LegoSetProfile : Profile
  {
    public LegoSetProfile()
    {
      CreateMap<LegoSet, LegoSetDto>();
    }
  }
}

The actual mapping is done by injecting the IMapper interface in each UseCase class where mapping is needed:

namespace SweDemoBackend.Application.UseCases
{
  public class LegoSetUseCase
  {
    private readonly ILegoSetRepository _repo;
    private readonly IMapper _mapper;

    public LegoSetUseCase(ILegoSetRepository repo, IMapper mapper)
    {
      _repo = repo;
      _mapper = mapper;
    }

    public async Task<IEnumerable<LegoSetDto>> GetAllLegoSets(CancellationToken ct = default)
    {
      var entities = await _repo.GetLegoSetsAsync(ct);

      //Mapping
      var dtos = _mapper.Map<List<LegoSetDto>>(entities);

      return dtos;
    }
  }
}





 

 


 







 





How are the mapping profiles found?

The highlighted line of code registers AutoMapper and scans the Application assembly for all classes that derive from AutoMapper.Profile, auto-loading their mappings so you don't have to register each profile manually.

 public static IServiceCollection AddApplication(this IServiceCollection services, IConfiguration configuration)
 {
   // Scans the Application assembly for Profile classes
   services.AddAutoMapper(cfg => cfg.LicenseKey = configuration["AutoMapperLicenseKey"], Assembly.GetExecutingAssembly());

   // Add use cases
   services.AddScoped<LegoSetUseCase>();

   return services;
 }



 






AutoMapper functionalities/patterns

AutoMapper shines when you need more than 1:1 property copies. Below are the most common AutoMapper use cases.

Custom member mapping (ForMember,MapFrom)

Great for shaping DomainDTO reads, flattening nested objects, or converting value objects.

CreateMap<Workshop, WorkshopDto>()
            .ForMember(d => d.Id, o => o.MapFrom(s => s.WorkshopId))
            .ForMember(d => d.CapacityLabel, o => o.MapFrom(s =>
                s.Capacity >= 100 ? "Large" : "Standard"));
  • By default properties with the same name and type are copied by convention
  • .ForMember(d => d.Id, o => o.MapFrom(s => s.WorkshopId)): Overrides the default mapping for Id: we now map the WorkshopId into the DTO's Id
  • .ForMember(d => d.CapacityLabel, o => o.MapFrom(s => s.Capacity >= 100 ? "Large" : "Standard"));: Creates a computed field on the DTO, based on the Capacity property from the entity

Create opposite mapping with ReverseMap()

When you define a map …

CreateMap<Workshop, WorkshopDto>();

… calling .ReverseMap() tells AutoMapper to automatically create the opposite mapping too (Destination → Source). The reverse map inherits your configuration and conventions, so you don’t have to write two separate maps.

Why it’s useful
  • Cuts boilerplate when conversions are symmetrical (e.g., edit forms, admin tools, import/export).
  • You can still customize the reverse side right after calling .ReverseMap().
CreateMap<Workshop, WorkshopDto>()
            .ForMember(d => d.Id, o => o.MapFrom(s => s.WorkshopId))
            .ForMember(d => d.CapacityLabel, o => o.MapFrom(s =>
                s.Capacity >= 100 ? "Large" : "Standard"))
            .ReverseMap()
            .ForMember(s => s.CreatedAt,o => o.Ignore());