RO EN

Mapping with AutoMapper between DTO and Model in .NET

Mapping with AutoMapper between DTO and Model in .NET
Doru Bulubasa
23 July 2025

In a modern ASP.NET application, a clear separation between what comes from outside (DTO – Data Transfer Object) and our domain models (Domain Models) is an essential good practice. This separation allows validation, control of exposed data, and adherence to DDD (Domain-Driven Design) principles.

🎯 Objective

Let's use AutoMapper to transform:

  • CreatePostDtoPost – in the case of commands (write),

  • PostPostDto – in the case of API responses (read).


🧱 Class structure

// DTO received from frontend for creating an article
public class CreatePostDto
{
    public string Title { get; set; } = string.Empty;
    public string Content { get; set; } = string.Empty;
}

// Domain model
public class Post
{
    public Guid Id { get; set; }
    public string Title { get; set; } = string.Empty;
    public string Content { get; set; } = string.Empty;
    public DateTime CreatedAt { get; set; }
}

// DTO returned in API responses
public class PostDto
{
    public Guid Id { get; set; }
    public string Title { get; set; } = string.Empty;
    public string Snippet { get; set; } = string.Empty;
}


🔁 AutoMapper configuration

Install the package:

dotnet add package AutoMapper.Extensions.Microsoft.DependencyInjection

✅ Mapping profile

public class PostMappingProfile : Profile

{

    public PostMappingProfile()

    {

        CreateMap<CreatePostDto, Post>()

            .ForMember(dest => dest.Id, opt => opt.Ignore())

            .ForMember(dest => dest.CreatedAt, opt => opt.MapFrom(_ => DateTime.UtcNow));


        CreateMap<Post, PostDto>()

            .ForMember(dest => dest.Snippet, opt => opt.MapFrom(src => src.Content.Substring(0, Math.Min(src.Content.Length, 100))));

    }

}

🧩 Registration in the DI container

builder.Services.AddAutoMapper(typeof(PostMappingProfile));


🛠️ Usage in CommandHandler

public class CreatePostCommandHandler : IRequestHandler<CreatePostCommand, Guid>

{

    private readonly IMapper _mapper;

    private readonly IPostRepository _repository;

    public CreatePostCommandHandler(IMapper mapper, IPostRepository repository)

    {

        _mapper = mapper;

        _repository = repository;

    }

    public async Task<Guid> Handle(CreatePostCommand request, CancellationToken cancellationToken)

    {

        var post = _mapper.Map<Post>(request.Dto);

        await _repository.AddAsync(post);

        return post.Id;

    }

}


📤 Usage when returning data (queries/API)

[HttpGet("{id}")]

public async Task<ActionResult<PostDto>> GetById(Guid id)

{

    var post = await _repository.GetByIdAsync(id);

    if (post is null) return NotFound();

    return _mapper.Map<PostDto>(post);

}


🧠 Conclusions

  • AutoMapper eliminates manual mapping, keeping the code clean and scalable.

  • It allows us to clearly define what data we accept and what data we expose.

  • We ensure that our domain models remain independent of the API structure.