Introduction
AutoMapper is a mature .NET library that automates object mapping, eliminating tedious manual boilerplate. In 2026, with .NET 10 and its AOT optimizations, it's still essential for REST APIs, microservices, and Blazor apps, reducing code by 70% on average per official benchmarks.
Why use it? Imagine mapping a DTO to an entity: without AutoMapper, 20 lines of repetitive code; with it, one fluent line. This intermediate tutorial covers full setup in a console project that's extensible to ASP.NET Core, from basic mappings to custom resolvers and dependency injection. By the end, you'll master performance tweaks and avoid common pitfalls for scalable apps. Ready to boost your productivity? (128 words)
Prerequisites
- .NET SDK 10.0 or higher (check with
dotnet --version) - Editor like Visual Studio 2022 or VS Code with C# extension
- Basic C# knowledge (classes, LINQ) and DI in .NET
- Terminal (PowerShell or bash)
Create the Console Project
dotnet new console -o AutoMapperDemo --framework net10.0
cd AutoMapperDemo
code .This command creates a minimal .NET 10 console project, navigates into it, and opens VS Code. It lays the foundation for testing AutoMapper without unnecessary complexity, targeting the 2026 LTS framework.
Install AutoMapper
Add the essential NuGet packages. AutoMapper core handles mappings; Extensions.Microsoft.DependencyInjection integrates with .NET's native DI for smooth production setups.
Add the NuGet Packages
dotnet add package AutoMapper
dotnet add package AutoMapper.Extensions.Microsoft.DependencyInjection
dotnet add package AutoMapper.DependencyInjection
dotnet restoreThese packages install AutoMapper (5.0+ in 2026) with DI support. restore syncs everything; stick to stable versions for AOT compatibility.
Define Source and Destination Models
namespace AutoMapperDemo;
public class User
{
public int Id { get; set; }
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public DateTime BirthDate { get; set; }
public List<string> Hobbies { get; set; } = new();
}
public class UserDto
{
public int Id { get; set; }
public string FullName { get; set; } = string.Empty;
public int Age { get; set; }
public string[] Hobbies { get; set; } = Array.Empty<string>();
}These classes represent a User entity (DB source) and UserDto (API). Note the misaligned properties: FullName and Age will need a custom profile to avoid mapping errors.
Create the Mapping Profile
Profiles centralize configuration like a blueprint. They extend Profile and use CreateMap for declarative rules, making code testable and maintainable.
Implement MappingProfile
using AutoMapper;
namespace AutoMapperDemo;
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<User, UserDto>()
.ForMember(dest => dest.FullName, opt => opt.MapFrom(src => src.FirstName + " " + src.LastName))
.ForMember(dest => dest.Age, opt => opt.MapFrom(src => DateTime.Now.Year - src.BirthDate.Year))
.ForMember(dest => dest.Hobbies, opt => opt.MapFrom(src => src.Hobbies.ToArray()));
}
}This profile maps User to UserDto with ForMember for custom properties: concatenation for FullName, approximate age calculation (refine in production), and List-to-array conversion. Rule order matters for dependencies.
Set Up DI and Mapping
Integrate AutoMapper via IServiceCollection as a singleton service. Use MapperConfiguration to validate at startup and prevent runtime errors.
Update Program.cs with DI
using AutoMapper;
using AutoMapper.Extensions.Microsoft.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace AutoMapperDemo;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddAutoMapper(typeof(MappingProfile));
builder.Services.AddSingleton(provider =>
{
var config = new MapperConfiguration(cfg => cfg.AddProfile<MappingProfile>());
config.AssertConfigurationIsValid();
return config.CreateMapper();
});
var host = builder.Build();
var mapper = host.Services.GetRequiredService<IMapper>();
var user = new User
{
Id = 1,
FirstName = "Jean",
LastName = "Dupont",
BirthDate = new DateTime(1990, 5, 15),
Hobbies = new List<string> { "Lecture", "Natation" }
};
var dto = mapper.Map<UserDto>(user);
Console.WriteLine($"FullName: {dto.FullName}, Age: {dto.Age}, Hobbies: {string.Join(", ", dto.Hobbies)}");
await host.RunAsync();This modern Program.cs (.NET 10 top-level statements) sets up DI with AddAutoMapper, validates config via AssertConfigurationIsValid(), and tests the mapping. Output: "Jean Dupont, 36, Lecture, Natation". Easily scalable to web APIs.
Advanced Mapping with Custom Resolver
using AutoMapper;
namespace AutoMapperDemo;
public class AgeResolver : IValueResolver<User, UserDto, int>
{
public int Resolve(User source, UserDto destination, int destMember, ResolutionContext context)
{
var age = DateTime.Now.Year - source.BirthDate.Year;
if (DateTime.Now < source.BirthDate.AddYears(age)) age--;
return age;
}
}
public class AdvancedMappingProfile : Profile
{
public AdvancedMappingProfile()
{
CreateMap<User, UserDto>()
.ForMember(dest => dest.FullName, opt => opt.MapFrom(src => src.FirstName + " " + src.LastName))
.ForMember(dest => dest.Age, opt => opt.MapFrom<AgeResolver>())
.ForMember(dest => dest.Hobbies, opt => opt.Ignore()); // Ignore for testing
}
}Introduces a precise IValueResolver for age (handles birthdays). Ignore() skips Hobbies. Swap in Program.cs with AddProfile for better accuracy, perfect for complex business logic.
Bidirectional Mapping and Collections
using AutoMapper;
namespace AutoMapperDemo;
public class BidirectionalProfile : Profile
{
public BidirectionalProfile()
{
CreateMap<User, UserDto>().ReverseMap();
CreateMap<List<User>, List<UserDto>>().ReverseMap();
}
}
// Example usage in Program.cs:
// var users = new List<User> { user1, user2 };
// var dtos = mapper.Map<List<UserDto>>(users);
// var usersBack = mapper.Map<List<User>>(dtos);ReverseMap() enables reverse mapping in one call. For collections, map List explicitly. Pitfall: non-assignable properties (e.g., computed ones) need DoNotValidate() or custom resolvers.
Best Practices
- Always validate: Use
AssertConfigurationIsValid()at startup to catch errors early. - Profiles per feature: One Profile per bounded context (e.g., UsersProfile, OrdersProfile) for scalability.
- DI everywhere: Inject
IMappervia constructor, nevernew Mapper()for thread-safety. - Performance: Use
ProjectTo()with EF Core (avoids materialization); profile withMapperConfiguration.AllowAdditiveProfileAssembly. - Testing: Mock
IMapperwithSubstitute.For(NSubstitute) for unit tests.()
Common Errors to Avoid
- Missing DI:
new MapperConfiguration()without services leads to unshared singletons and memory leaks. - Circular mappings: Without
DisableCircularReferenceHandling(), you get stack overflows; useConstructUsingServiceLocator(). - Nullable properties: Set
AllowNullCollections = trueorForMember(..., opt => opt.Condition(src => src != null)). - AOT/trimming: For .NET native, add
orfalse [DynamicallyAccessedMembers]on resolvers.
Next Steps
- Official docs: AutoMapper GitHub
- Video: Performance tuning
- Advanced: Integrate with EF Core
ProjectTo()for optimized queries. - Training: Check our advanced .NET courses at Learni to master DDD + AutoMapper in microservices.