Seguramente ya sabes qué es el patrón DTO y si no lo sabes puedes aprenderlo aquí. Entonces ya tienes claro que se deben manejar distintas clases para comunicarnos con el cliente, por ejemplo no utilizar la misma clase para guardar un nuevo registro que para mostrar información al usuario, genial, eso quedó clarísimo entonces.
Pues bien eso implica estar a cada rato convirtiendo un objeto de un tipo a otro, veamos este sencillo ejemplo:
Tienes la siguiente entidad Autor
Su clase sería algo así
public class Autor
{
[Key]
public int Id { get; set; }
[Required]
[StringLength(maximumLength:120)]
public string Nombre { get; set; }
}
Muy bien esa es la entidad, es decir la clase que se corresponderá con una tabla en la base de datos, por eso estamos incluyendo la propiedad Id.
Pero cuando desde un cliente (podría ser web, móvil o cualquier cosa) queremos guardar un nuevo Autor, no es necesario que le enviemos un Id, de hecho estaría mal hacerlo por dos motivos:
- Dejamos de cumplir el principio I de SOLID, concretamente el ISP (Interface segregation principle) porque estamos entregando más información de la necesaria.
- el mensaje transmitido por la red es mayor de forma innecesaria, tu dirás pero es solo una propiedad, pero recuerda que estamos haciendo un ejemplo simplificado y que lo que ahora es un entero, podría ser en otro contexto un campo de tipo archivo.
Como ya sabemos aplicar el patrón DTO entonces en nuestro proyecto creamos una carpeta llamada DTO
Y creamos dos clases llamadas AutorDTO y AutorCreacionDTO
public class AutorDTO
{
[Key]
public int Id { get; set; }
[Required]
[StringLength(maximumLength:120)]
public string Nombre { get; set; }
}
public class AutorCreacionDTO
{
[Required]
[StringLength(maximumLength: 120)]
public string Nombre { get; set; }
}
Ahora sí tenemos dos clases cada una para su funcionalidad y no trabajamos directamente con la entidad, genial! 😉
A continuación compararemos las dos formas de trabajar los mapeos en un controlador:
Controlador con mapeo manual
Ahora que tenemos la entidad y sus DTO creadas, es momento de codificar el controlador al cual llamaremos AutoresController
Pero primero veamos cómo serían los endpoint Get y Post de Autores controller si no usáramos AutoMapper
[HttpGet]
public async Task<List<AutorDTO>> Get()
{
var autores = await context.Autores.ToListAsync();//autores traídos desde la BD
var autoresDTO = new List<AutorDTO>();//lista DTO de retorno
foreach(var autor in autores)
{
autoresDTO.Add(new AutorDTO { Id = autor.Id, Nombre = autor.Nombre });//mapeo manual por cada iteración
}
return autoresDTO;
}
En el caso del endpoint Post tendríamos algo así:
[HttpPost]
public async Task<ActionResult> Post([FromBody] AutorCreacionDTO autorCreacionDTO)
{
var autor = new Autor { Nombre = autorCreacionDTO.Nombre };//mapeo manual
await context.Autores.AddAsync(autor);
await context.SaveChangesAsync();
return Ok();
}
Manejar el mapeo de forma manual trae dos inconvenientes:
- Más código y trabajo repetitivo
- Por si el motivo anterior te pareció insuficiente, también es inseguro, porque al tener que estar mapeando en cada método de todos tus controladores, cuando hagas cambios en las entidades va a implicar que tendrás que actualizar todos y cada uno de los mapeos en los muchos métodos que tengas y Ay de ti si te olvidas jaja...
Manejemos los mapeos de forma centralizada mejor, empecemos a configurar AutoMapper
Configurando AutoMapper
Estamos trabajando en un Web API con .Net Core así que vamos a Nuget e instalaremos
AutoMapper.Extensions.Microsoft.DependencyInjection
Vamos a la clase Startup.cs y en el método ConfigureServices agregamos esto:
public void ConfigureServices(IServiceCollection services)
{
services.AddAutoMapper(typeof(Startup));
//...
services.AddControllers();
}
Crearemos los perfiles de AutoMapper para esto creamos una carpeta llamada Utils y la clase llamada AutoMapperProfiles que hereda de Profile (AutoMapper.Profile)
public class AutoMapperProfiles : Profile
{
public AutoMapperProfiles()
{
CreateMap<Autor, AutorDTO>().ReverseMap();//mapea desde Autor hacia AutorDTO y viceversa
CreateMap<AutorCreacionDTO, Autor>();//mapea desde AutorCreacionDTO hacia Autor
}
}
Si las propiedades entres las clases a mapear tienen el mismo nombre con esto basta, si fuesen distintos desde esta misma clase la puedes configurar con el método .ForMember la sintaxis sería así:
Mapper.CreateMap<Autor, AutorDTO>()
.ForMember(dest => dest.NombreCompleto, opt => opt.MapFrom(src => src.Nombre));
Con eso ya tenemos todo configurado, ahora veamos como quedan los controladores usando Automapper
Controlador con AutoMapper
[HttpGet]
public async Task<List<AutorDTO>> Get()
{
var autores = await context.Autores.ToListAsync();
return mapper.Map<List<AutorDTO>>(autores);//Aquí se hace el mapeo
}
Ahora veamos el endpoint de guardado
[HttpPost]
public async Task<ActionResult> Post([FromBody] AutorCreacionDTO autorCreacionDTO)
{
var autor = mapper.Map<Autor>(autorCreacionDTO);//aquí se hace el mapeo
await context.Autores.AddAsync(autor);
await context.SaveChangesAsync();
return Ok();
}
Y bueno mis cracks! eso es todo, y quería compartirlo con ustedes, recuerden que Automapper funciona con varias tecnologías no sólo con .Net, aquí vimos el ejemplo con .NET porque soy net lover 💙 y me da pa comer 🤣 pero visita la documentación de AutoMapper en https://automapper.org/ y verifica mucho más cosas que tiene.
Un gran abrazo a todos y si te gustó esta entrada ya sabes que hacer crack, comparte! Y sígueme en Linkedin, hasta la vista baby dev 😎
Muchas gracias, buena explicación para principiantes, me ha dado ideas y aclarado varios puntos 😄
Hey gracias Yeica, recuerda que ayuda bastante compartir la entrada en tus redes sociales y visitar este blog siempre, cada semana subo contenido, welcome! 💪🔥
Buen aporte, fácil y sencillo.
Entiendo que Automapper ayuda y reduce mucho las líneas de Código, pero Tambien tienen sus desventajas al momento del debug.
Creo que automapper estaria bien usarlo cuando ya se tiene cierto conocimiento y experiencia.
Hola estimado Pablo, así es, como todo en la vida, tiene sus pro y contras, sin embargo considero que sus ventajas superan por mucho a las desventajas, y por ello siempre debe usarse desde que uno hace sus primeros desarrollos para acostumbrarse a las buenas prácticas. Quizá no deba usarse sólo en sistemas muy pequeñitos y monolíticos. Saludos y vuelve pronto, comparte!
Muy buen aporte, claro y sencillo. Me ayudaste mucho. Tenia dudas sobre como configurar el automapper cuando los campos se llaman distinto
Muchas gracias!
Hola Facundo, claro allí usas el .ForMember, genial! que bueno que te haya ayudado, comparte este blog con tu equipo de desarrollo y así llegue a más personas, entusiastas como nosotros, un abrazo y vuelve cuando quieras!