DTOs e AutoMapper
ResumoDTOs (Data Transfer Objects) definem o contrato público da API, separado das entidades de domínio. O AutoMapper reduz o código repetitivo de mapeamento entre entidade e DTO.
Notaa partir da v15.0.0 (2 de julho de 2025, Lucky Penny Software), o AutoMapper adota modelo comercial para uso empresarial. A v14.x (última livre, MIT) permanece gratuita. Avalie a licença antes de adotar a v15+ em produção; alternativas open-source incluem mapeamento manual ou geradores de código em tempo de compilação (ex.: Mapperly, MIT). Os conceitos de DTO e mapeamento desta aula valem para qualquer abordagem.
1. Objetivos de Aprendizagem#
- Explicar por que DTOs e entidades devem ser distintos.
- Criar DTOs de leitura (
records). - Configurar e usar o AutoMapper.
2. Pré-requisitos#
- Endpoint GET retornando entidades (tópico 18.2).
3. Conceito#
DTO vs. Entidade: a entidade modela persistência e regras de negócio; o DTO modela o contrato de transporte. Separá-los evita: vazamento de campos internos, loops de serialização, e acoplamento do cliente à estrutura do banco. O porquê: você pode evoluir o schema do banco sem quebrar clientes, e vice-versa.
record é ideal para DTOs de leitura: imutável e com igualdade por valor.
4. Mão na Massa#
4.1. Setup#
dotnet add Catalog.Api package AutoMapper
Notaa partir das versões recentes, o pacote
AutoMapperjá inclui a integração com DI; não é necessário o antigoAutoMapper.Extensions.Microsoft.DependencyInjectionseparado.
4.2. Implementação Passo a Passo#
- DTO como
record:
namespace Catalog.Shared.DataTransferObjects;
public record CategoriaDto(Guid Id, string Nome, int QuantidadeProdutos);
- Perfil de mapeamento:
using AutoMapper;
using Catalog.Entities.Models;
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Categoria, CategoriaDto>()
.ForCtorParam(nameof(CategoriaDto.QuantidadeProdutos),
opt => opt.MapFrom(src => src.Produtos.Count));
}
}
- Registro:
builder.Services.AddAutoMapper(typeof(MappingProfile));
- Uso no serviço/controller:
var categorias = await _repo.Categoria.GetAllAsync(trackChanges: false);
var dtos = _mapper.Map<IEnumerable<CategoriaDto>>(categorias);
return Ok(dtos);
4.3. Executando#
dotnet run
curl http://localhost:5000/api/categorias
Agora a resposta traz apenas os campos do DTO.
5. Exemplo Completo#
[HttpGet]
public async Task<IActionResult> GetCategorias()
{
var categorias = await _service.Categoria.GetAllAsync(trackChanges: false);
return Ok(categorias); // serviço já retorna IEnumerable<CategoriaDto>
}
6. Boas Práticas e Armadilhas#
| Faça | Evite |
|---|---|
| Usar DTOs distintos para leitura e escrita | Reusar a entidade como corpo de request/response |
Validar os perfis do AutoMapper em testes (AssertConfigurationIsValid) | Mapeamentos silenciosamente incompletos |
Preferir records para DTOs imutáveis | Classes mutáveis com setters públicos em DTOs de leitura |
AtençãoAutoMapper mal configurado pode mascarar bugs (campos não mapeados viram default). Rode
configuration.AssertConfigurationIsValid()num teste para garantir cobertura.
7. Segurança e Produção#
- DTOs de escrita evitam over-posting: o cliente não consegue setar campos que não estão no DTO (ex.:
IsAdmin). - Nunca inclua dados sensíveis em DTOs de resposta pública.
8. Exercícios#
- Fácil: crie
ProdutoDtoe mapeie a partir deProduto. - Médio: adicione um
record ProdutoForCreationDto(só campos de entrada). - Desafio: escreva um teste que falhe se um novo campo da entidade não tiver mapeamento correspondente.
9. Resumo#
DTOs separam o contrato da API do domínio, evitando vazamentos e over-posting. O AutoMapper elimina o mapeamento manual, mas deve ser validado por testes.
10. Próximos Passos#
Módulo 19: tratamento global de erros com middleware e ProblemDetails.
11. Referências#
- Microsoft Learn — DTOs em APIs web
- Documentação oficial — AutoMapper
- Microsoft Learn — Tipo record em C#