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.
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.
Source object to map from.
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.
Domain entity to map from.
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.
Collection of domain entities to map from.
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:
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 );
}
}
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 ();
}