Skip to main content
The Mapping module provides a streamlined way to convert between domain entities, DTOs, and other object types using AutoMapper. It includes extension methods for convenient, fluent transformations throughout your application.

Core Component

CustomMapper

A static class that provides extension methods for object mapping.
Instance
IMapper
Static instance of AutoMapper’s IMapper configured during application startup.
Core.Application.Mapping/CustomMapper.cs
using AutoMapper;
using Core.Domain.Entities;

namespace Core.Application
{
    public static class CustomMapper
    {
        public static IMapper Instance { get; set; }

        public static T To<T>(this object input)
        {
            IMapper mapper = Instance;
            return mapper.Map<T>(input);
        }

        public static T To<T>(this IValidate input)
        {
            IMapper mapper = Instance;
            return mapper.Map<T>(input);
        }

        public static IEnumerable<T> To<T>(this IEnumerable<IValidate> input)
        {
            IMapper mapper = Instance;
            return mapper.Map<IEnumerable<T>>(input);
        }
    }
}

Extension Methods

To<T>(object)

Maps any object to the target type.
input
object
Source object to map from.
T
type parameter
Target type to map to.
Returns: New instance of type T with mapped properties.
var dto = domainEntity.To<DummyEntityDto>();

To<T>(IValidate)

Maps domain entities implementing IValidate to the target type.
input
IValidate
Domain entity to map from.
T
type parameter
Target type to map to.
Returns: New instance of type T with mapped properties.
var domainEvent = entity.To<DummyEntityCreated>();

To<T>(IEnumerable<IValidate>)

Maps collections of domain entities to collections of target type.
input
IEnumerable<IValidate>
Collection of domain entities to map from.
T
type parameter
Target type for collection items.
Returns: Collection of type T with mapped properties.
var dtos = entities.To<DummyEntityDto>();

Mapping Profiles

Define your mappings using AutoMapper profiles:
Application/Mappings/Mapping.cs
using Application.DataTransferObjects;
using Application.DomainEvents;
using AutoMapper;
using Domain.Entities;

namespace Application.Mappings
{
    /// <summary>
    /// Define all object mappings here
    /// </summary>
    public class Mapping : Profile
    {
        public Mapping()
        {
            // Entity to Domain Event mappings
            CreateMap<DummyEntity, DummyEntityCreated>().ReverseMap();
            CreateMap<DummyEntity, DummyEntityUpdated>().ReverseMap();
            
            // Entity to DTO mappings
            CreateMap<DummyEntity, DummyEntityDto>().ReverseMap();
        }
    }
}

ReverseMap()

The ReverseMap() method creates a bidirectional mapping:
// Both directions work:
var dto = entity.To<DummyEntityDto>();      // Entity → DTO
var entity = dto.To<DummyEntity>();          // DTO → Entity

Registration

Register AutoMapper and configure the CustomMapper instance:
Application/Registrations/ApplicationServicesRegistration.cs
using Core.Application;
using Microsoft.Extensions.DependencyInjection;
using System.Reflection;

public static class ApplicationServicesRegistration
{
    public static IServiceCollection AddApplicationServices(this IServiceCollection services)
    {
        // Register AutoMapper and scan for profiles in the current assembly
        services.AddAutoMapper(config => 
            config.AddMaps(Assembly.GetExecutingAssembly()));

        return services;
    }
}

Initialize CustomMapper Instance

Set the static instance during application startup:
Template-API/Startup.cs
using Core.Application;
using AutoMapper;

public class Startup
{
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        // Initialize CustomMapper with the configured IMapper instance
        CustomMapper.Instance = app.ApplicationServices.GetRequiredService<IMapper>();

        // ... rest of configuration
    }
}
The CustomMapper.Instance must be set before any mapping operations are attempted, typically in Startup.Configure().

Usage Examples

Entity to DTO

Convert domain entities to DTOs for API responses:
Application/UseCases/DummyEntity/Queries/GetAllDummyEntitiesHandler.cs
using Core.Application;

public class GetAllDummyEntitiesHandler : IRequestQueryHandler<GetAllDummyEntitiesQuery, QueryResult<DummyEntityDto>>
{
    private readonly IDummyEntityRepository _repository;

    public async Task<QueryResult<DummyEntityDto>> Handle(
        GetAllDummyEntitiesQuery request, 
        CancellationToken cancellationToken)
    {
        // Fetch domain entities from repository
        IList<DummyEntity> entities = await _repository.FindAllAsync();

        // Map collection of entities to DTOs
        var dtos = entities.To<DummyEntityDto>();

        return new QueryResult<DummyEntityDto>(
            dtos, 
            entities.Count, 
            request.PageIndex, 
            request.PageSize);
    }
}

Entity to Domain Event

Transform entities into domain events:
Application/UseCases/DummyEntity/Commands/CreateDummyEntityHandler.cs
using Core.Application;

public class CreateDummyEntityHandler : IRequestCommandHandler<CreateDummyEntityCommand, string>
{
    private readonly ICommandQueryBus _domainBus;
    private readonly IDummyEntityRepository _repository;

    public async Task<string> Handle(CreateDummyEntityCommand request, CancellationToken cancellationToken)
    {
        var entity = new DummyEntity(request.dummyPropertyOne, request.dummyPropertyTwo);
        
        object createdId = await _repository.AddAsync(entity);

        // Map entity to domain event and publish
        await _domainBus.Publish(entity.To<DummyEntityCreated>(), cancellationToken);

        return createdId.ToString();
    }
}

Command to Entity

While you can use mapping for commands, explicit construction is often clearer:
// Using explicit construction (recommended)
var entity = new DummyEntity(
    request.dummyPropertyOne, 
    request.dummyPropertyTwo);

// Using mapping (alternative)
var entity = request.To<DummyEntity>();
Explicit entity construction is preferred as it ensures proper domain logic and validation are executed.

Advanced Mapping Configurations

Custom Property Mapping

Map properties with different names or types:
Application/Mappings/Mapping.cs
public class Mapping : Profile
{
    public Mapping()
    {
        CreateMap<DummyEntity, DummyEntityDto>()
            .ForMember(dest => dest.DisplayName, 
                opt => opt.MapFrom(src => src.dummyPropertyOne))
            .ForMember(dest => dest.Status, 
                opt => opt.MapFrom(src => src.dummyPropertyTwo.ToString()));
    }
}

Conditional Mapping

Apply conditions during mapping:
CreateMap<DummyEntity, DummyEntityDto>()
    .ForMember(dest => dest.IsActive, 
        opt => opt.MapFrom(src => src.Status == EntityStatus.Active))
    .ForMember(dest => dest.SecretField, 
        opt => opt.Condition(src => src.IncludeSecrets));

Value Resolvers

Implement complex mapping logic:
public class FullNameResolver : IValueResolver<DummyEntity, DummyEntityDto, string>
{
    public string Resolve(
        DummyEntity source, 
        DummyEntityDto destination, 
        string destMember, 
        ResolutionContext context)
    {
        return $"{source.FirstName} {source.LastName}".Trim();
    }
}

public class Mapping : Profile
{
    public Mapping()
    {
        CreateMap<DummyEntity, DummyEntityDto>()
            .ForMember(dest => dest.FullName, 
                opt => opt.MapFrom<FullNameResolver>());
    }
}

Nested Object Mapping

AutoMapper automatically maps nested objects:
public class Mapping : Profile
{
    public Mapping()
    {
        // Define mapping for nested type
        CreateMap<Address, AddressDto>();
        
        // Parent mapping automatically uses nested mapping
        CreateMap<Customer, CustomerDto>();
    }
}

// Usage
var customerDto = customer.To<CustomerDto>(); // Address is mapped automatically

Collection Mapping

Map different collection types:
public class Mapping : Profile
{
    public Mapping()
    {
        CreateMap<DummyEntity, DummyEntityDto>();
        
        // List to Array
        CreateMap<List<DummyEntity>, DummyEntityDto[]>();
        
        // Array to IEnumerable
        CreateMap<DummyEntity[], IEnumerable<DummyEntityDto>>();
    }
}

Data Transfer Objects (DTOs)

Define DTOs for data transfer between layers:
Application/DataTransferObjects/DummyEntityDto.cs
namespace Application.DataTransferObjects
{
    public class DummyEntityDto
    {
        public string Id { get; set; }
        public string DummyPropertyOne { get; set; }
        public string DummyPropertyTwo { get; set; }
        public DateTime CreatedDate { get; set; }
        public DateTime? ModifiedDate { get; set; }
    }
}

Best Practices

Keep mapping profiles organized by domain area. Create separate profile classes for different bounded contexts.
Never map directly to or from domain entities in API controllers. Always use DTOs as an anti-corruption layer.
Use ReverseMap() sparingly. Bidirectional mappings can hide important asymmetries in your domain model.

Profile Organization

// Good: Organized by domain
public class OrderMappingProfile : Profile { }
public class CustomerMappingProfile : Profile { }
public class ProductMappingProfile : Profile { }

// Avoid: Single monolithic profile
public class ApplicationMappingProfile : Profile 
{ 
    // 500 lines of mappings
}

Testing Mappings

Verify mapping configurations in unit tests:
Tests/Mappings/MappingTests.cs
using AutoMapper;
using Xunit;

public class MappingTests
{
    private readonly IMapper _mapper;

    public MappingTests()
    {
        var configuration = new MapperConfiguration(cfg =>
        {
            cfg.AddMaps(typeof(Mapping).Assembly);
        });

        _mapper = configuration.CreateMapper();
    }

    [Fact]
    public void Configuration_IsValid()
    {
        // Validates all mapping configurations
        _mapper.ConfigurationProvider.AssertConfigurationIsValid();
    }

    [Fact]
    public void DummyEntity_MapsTo_DummyEntityDto()
    {
        var entity = new DummyEntity("test", DummyValues.Value1);
        
        var dto = _mapper.Map<DummyEntityDto>(entity);
        
        Assert.Equal(entity.Id, dto.Id);
        Assert.Equal(entity.dummyPropertyOne, dto.DummyPropertyOne);
    }
}

Performance Considerations

Compiled Mappings

AutoMapper compiles mapping expressions for optimal runtime performance.

Static Instance

The static CustomMapper.Instance avoids repeated mapper lookups.

Projection

Use ProjectTo<T>() with IQueryable for efficient database queries.

Lazy Loading

Mapping profiles are loaded once at startup, not per request.

Query Projection

For optimal database performance, project directly in queries:
using AutoMapper.QueryableExtensions;

public async Task<List<DummyEntityDto>> GetAllOptimizedAsync()
{
    // Projects in the database - only selects needed columns
    return await _context.DummyEntities
        .ProjectTo<DummyEntityDto>(CustomMapper.Instance.ConfigurationProvider)
        .ToListAsync();
}

Build docs developers (and LLMs) love