An object mapper is a tool for converting objects of one type into objects of another type.
If you have followed the previous articles about DTOs, you will have encountered a repetitive and tedious task.
You have your User entity with 20 properties, and your UserDto with the same 20 properties. And you have to write this:
var dto = new UserDto
{
FirstName = user.FirstName,
LastName = user.LastName,
Email = user.Email,
// ... and so on for 17 more lines ...
};This is boilerplate code. It’s easy to forget a property, and when there are many mappings, it ends up cluttering the services.
Object mappers automate some of that work through conventions, compiled expressions, or code generation.
Let’s compare two common alternatives: AutoMapper and Mapster.
AutoMapper
AutoMapper is a veteran library widely used in the .NET ecosystem.
How it works
AutoMapper works based on Profiles. You must explicitly tell it which classes can be mapped to each other.
Installation
dotnet add package AutoMapperProfile Configuration
We create a class that inherits from Profile. Here we define the rules.
using AutoMapper;
public class MappingProfile : Profile
{
public MappingProfile()
{
// Simple mapping: Properties with the same name are copied automatically
CreateMap<Product, ProductDto>();
// Reverse mapping (from DTO to Entity)
CreateMap<CreateProductDto, Product>();
// Custom mapping: If names don't match
CreateMap<User, UserDto>()
.ForMember(dest => dest.FullName,
opt => opt.MapFrom(src => $"{src.FirstName} {src.LastName}"));
}
}Registration and Injection
In Program.cs, we register the service, indicating a type from the assembly where the profiles are located.
builder.Services.AddAutoMapper(
cfg => cfg.LicenseKey = "<license-key>",
typeof(MappingProfile));Since AutoMapper 13, AddAutoMapper is part of the main AutoMapper package. The old AutoMapper.Extensions.Microsoft.DependencyInjection package is discontinued. AutoMapper 15 requires reviewing its license and configuring a key; you can also discover it via the AUTOMAPPER_LICENSE_KEY environment variable.
Usage
We inject IMapper into our service or controller.
public class ProductService
{
private readonly IMapper _mapper;
public ProductService(IMapper mapper)
{
_mapper = mapper;
}
public ProductDto Get(int id)
{
var entity = _repo.Get(id);
// Converts entity to DTO
return _mapper.Map<ProductDto>(entity);
}
}Mapster
In recent years, Mapster has gained popularity as an alternative with a simple API and code generation options.
Mapster can adapt objects at runtime and also offers compile-time code generation. If performance matters, it’s better to measure the real-world scenario rather than choosing based solely on a generic benchmark.
How it works
Mapster bets on convention over configuration. Often you don’t need to configure anything.
Installation
dotnet add package MapsterDirect Usage
You don’t need to inject anything if you don’t want to. Mapster adds extension methods to all your objects.
using Mapster;
public ProductDto Get(int id)
{
var entity = _repo.Get(id);
// Extension method .Adapt<T>()
var dto = entity.Adapt<ProductDto>();
return dto;
}Global Configuration
If you need custom mappings, you can configure them at application startup:
TypeAdapterConfig<User, UserDto>
.NewConfig()
.Map(dest => dest.FullName, src => src.FirstName + " " + src.LastName);AutoMapper and Mapster
| Feature | AutoMapper | Mapster |
|---|---|---|
| Common Configuration | Profiles and IMapper | Conventions, global configuration, or generated code |
| Usage | IMapper injection | Extension methods like Adapt or configured services |
| Query Projection | ProjectTo | ProjectToType |
| License | Requires reviewing license from version 15 | MIT |
Manual Mapping
We can also not use any library and write explicit mapping methods.
Writing your own .ToDto() extension methods is repetitive, but it has clear advantages:
- Predictable Performance: Direct assignment avoids the mapper’s overhead.
- References: You can right-click -> “Find All References” on a property and know exactly where it’s being used. With AutoMapper, that relationship is hidden from the IDE.
- Debugging: If the mapping fails, you set a breakpoint and see it.
// Manual mapping with extension methods
public static class ProductMappers
{
public static ProductDto ToDto(this Product entity)
{
return new ProductDto(entity.Id, entity.Name, entity.Price);
}
}
// Usage
return entity.ToDto();