Skip to content
Learni
Voir tous les tutoriels
.NET Développement

Comment maîtriser AutoMapper en .NET 2026

Read in English

Introduction

AutoMapper est une bibliothèque .NET incontournable pour automatiser le mapping entre objets DTO, entités et modèles de vue, évitant les boilerplates fastidieux. En 2026, avec .NET 10, elle reste optimisée pour les performances et intègre nativement les projections EF Core. Pourquoi l'utiliser ? Imaginez mapper un UserEntity complexe vers un UserDto lightweight en une ligne : c'est 80% de code en moins, moins d'erreurs et un refactoring accéléré. Ce tutoriel intermediate part des bases pour aller aux configurations avancées comme les profiles, résolveurs custom et validations. Vous obtiendrez un projet console fonctionnel, extensible à ASP.NET Core, avec des exemples concrets sur un scénario e-commerce : mapping de commandes et produits. Résultat : un mapper robuste, testable et performant dès la fin de lecture.

Prérequis

  • .NET 10 SDK installé (vérifiez avec dotnet --version)
  • Visual Studio 2026 ou VS Code avec C# extension
  • Connaissances de base en C# (classes, LINQ, DI)
  • Un éditeur de code et terminal

Créer le projet et installer AutoMapper

terminal
dotnet new console -n AutoMapperDemo
cd AutoMapperDemo

dotnet add package AutoMapper
 dotnet add package AutoMapper.Extensions.Microsoft.DependencyInjection

dotnet add package Microsoft.Extensions.DependencyInjection
 dotnet add package Microsoft.Extensions.Hosting

 dotnet add package Microsoft.Extensions.Hosting.Abstractions

code .

Cette commande crée un projet console .NET 10, installe AutoMapper core, son extension DI et les paquets Hosting/DI pour un setup moderne. L'extension DI simplifie l'injection du IMapper partout. Évitez les versions anciennes : utilisez toujours les dernières pour les optimisations JIT et AOT.

Définir les modèles source et cible

Avant de mapper, définissons des classes concrètes. Prenons un scénario e-commerce : OrderEntity (de la DB) vers OrderDto (pour l'API). L'entité a des champs internes comme Id auto-généré et une liste de produits ; le DTO expose seulement les infos publiques avec un total calculé.

Classes modèles complètes

Models.cs
namespace AutoMapperDemo;

public class ProductEntity
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public decimal Price { get; set; }
}

public class ProductDto
{
    public string Name { get; set; } = string.Empty;
    public decimal Price { get; set; }
}

public class OrderEntity
{
    public int Id { get; set; }
    public string CustomerName { get; set; } = string.Empty;
    public DateTime OrderDate { get; set; }
    public List<ProductEntity> Products { get; set; } = new();
    public decimal Discount { get; set; }
}

public class OrderDto
{
    public int Id { get; set; }
    public string CustomerName { get; set; } = string.Empty;
    public DateTime OrderDate { get; set; }
    public List<ProductDto> Products { get; set; } = new();
    public decimal Total => Products.Sum(p => p.Price) - Discount;
}

Ces classes illustrent un mapping typique : l'entité a un Discount source, le DTO calcule un Total dérivé. Notez les collections imbriquées – AutoMapper les gère récursivement. Piège : oubliez pas les initialisations de listes pour éviter les NullRef en runtime.

Configurer le mapper de base

Analogie : Le mapper est comme un traducteur automatique qui match les propriétés par nom/convention. Ici, on configure un mapping simple qui ignore Id si besoin et mappe les collections.

Configuration basique du IMapper

MapperConfig.cs
using AutoMapper;

namespace AutoMapperDemo;

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        CreateMap<ProductEntity, ProductDto>();
        CreateMap<OrderEntity, OrderDto>()
            .ForMember(dest => dest.Id, opt => opt.Ignore());
    }
}

public static class MapperConfig
{
    public static MapperConfiguration CreateConfiguration()
    {
        var config = new MapperConfiguration(cfg =>
        {
            cfg.AddProfile<MappingProfile>();
        });
        return config;
    }
}

On utilise un Profile pour organiser les mappings (best practice). CreateMap auto-map par nom ; ForMember ignore Id pour simuler un DTO sans PK. Cette config est thread-safe et réutilisable. Piège : sans Ignore(), AutoMapper écraserait l'ID client-side.

Intégrer avec Dependency Injection

Pour un usage pro, injectez IMapper via DI. C'est scalable pour APIs ASP.NET Core. On hoste l'app avec IHost pour tester.

Setup DI et mapping initial

Program.cs
using AutoMapper;
using AutoMapperDemo;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

class Program
{
    static async Task Main(string[] args)
    {
        var host = Host.CreateDefaultBuilder(args)
            .ConfigureServices((context, services) =>
            {
                var config = MapperConfig.CreateConfiguration();
                services.AddSingleton(config.CreateMapper());
                services.AddSingleton<IMapper>(sp => config.CreateMapper());
            })
            .Build();

        var mapper = host.Services.GetRequiredService<IMapper>();
        var service = new MappingService(mapper);
        await service.RunDemo();
    }
}

public class MappingService
{
    private readonly IMapper _mapper;

    public MappingService(IMapper mapper) => _mapper = mapper;

    public async Task RunDemo()
    {
        var orderEntity = new OrderEntity
        {
            Id = 1,
            CustomerName = "Alice",
            OrderDate = DateTime.Now,
            Products = new List<ProductEntity>
            {
                new() { Name = "Laptop", Price = 1200m },
                new() { Name = "Souris", Price = 25m }
            },
            Discount = 50m
        };

        var orderDto = _mapper.Map<OrderDto>(orderEntity);
        Console.WriteLine($"Total: {orderDto.Total}");
        await Task.CompletedTask;
    }
}

DI avec AddSingleton pour performance (mappers sont stateless). Le MappingService demo un mapping live : entité → DTO avec total calculé (1225 - 50 = 1175). Exécutez dotnet run : output confirme. Piège : double CreateMapper() – utilisez une seule instance.

Mappings avancés avec conditions

Passez au niveau intermediate : conditions (Condition), résolveurs custom pour logiques métier.

Résolveur custom et conditions

AdvancedMappingProfile.cs
using AutoMapper;

namespace AutoMapperDemo;

public class CustomTotalResolver : IValueResolver<OrderEntity, OrderDto, decimal>
{
    public decimal Resolve(OrderEntity source, OrderDto destination, decimal destMember, ResolutionContext context)
    {
        var total = source.Products.Sum(p => p.Price);
        return total > 1000 ? total * 0.9m : total - source.Discount;
    }
}

public class AdvancedMappingProfile : Profile
{
    public AdvancedMappingProfile()
    {
        CreateMap<ProductEntity, ProductDto>();
        CreateMap<OrderEntity, OrderDto>()
            .ForMember(dest => dest.Total, opt => opt.MapFrom<CustomTotalResolver>())
            .ForPath(dest => dest.Products.First().Name, opt => opt.Condition((src, dest, name) => !string.IsNullOrEmpty(name)));
    }
}

Le IValueResolver custom applique une promo 10% si total >1000 (logique métier). Condition skippe si nom vide. Ajoutez ce profile à MapperConfig via cfg.AddProfile(). Remplacez dans Program.cs : total devient 1080 (10% off). Piège : résolveurs non thread-safe – gardez-les stateless.

Validation et tests unitaires

Best practice : Validez toujours les mappings au startup pour catcher les erreurs tôt.

Validation et test unitaire

MapperValidation.cs
using AutoMapper;
using FluentAssertions;
using Xunit;

namespace AutoMapperDemo.Tests;

public class MapperTests
{
    [Fact]
    public void ShouldMapOrderCorrectly()
    {
        // Arrange
        var config = MapperConfig.CreateConfiguration();
        config.AssertConfigurationIsValid(); // Validation
        var mapper = config.CreateMapper();

        var entity = new OrderEntity { /* data as before */ };

        // Act
        var dto = mapper.Map<OrderDto>(entity);

        // Assert
        dto.CustomerName.Should().Be(entity.CustomerName);
        dto.Products.Should().HaveCount(2);
    }
}

AssertConfigurationIsValid() lève si mapping incomplet (ex: prop manquante). Intégrez à tests Xunit (ajoutez dotnet add package xunit). FluentAssertions pour asserts lisibles. Piège : sans validation, runtime boom sur props non mappées.

Bonnes pratiques

  • Utilisez toujours des Profiles : séparez les mappings par domaine (OrdersProfile, UsersProfile) pour maintenabilité.
  • Injectez IMapper via DI : singleton pour perf, scoped en ASP.NET pour contexte DB.
  • Validez au startup : mapper.ConfigurationProvider.AssertConfigurationIsValid() en Program.cs.
  • Préférez ProjectTo pour EF : context.Orders.ProjectTo(mapper.ConfigurationProvider) au lieu de Map post-query.
  • Ignorez les IDs sensibles : ForMember(dest => dest.Id, opt => opt.Ignore()) pour sécurité.

Erreurs courantes à éviter

  • Mapper null ou non injecté : Vérifiez services.AddAutoMapper(typeof(Program)) avec l'extension DI.
  • Performances sur grandes collections : Utilisez ProjectTo EF au lieu de ToList().Map() (évite N+1).
  • Over-posting en DTO : Mappez seulement les props nécessaires ; validez avec ForAllMembers(opt => opt.Condition(...)).
  • Mauvaise gestion Null : Ajoutez AllowNullCollections = true ou initialisez listes dans DTO.

Pour aller plus loin

  • Docs officielles : AutoMapper.org
  • Intégrez avec EF Core : lisez sur ProjectTo pour queries optimisées.
  • Source du projet complet : adaptez à ASP.NET Core pour APIs.
  • Découvrez nos formations Learni sur .NET avancé pour maîtriser MediatR + AutoMapper en Clean Architecture.