Creando un API RESTFul Completo con Net Core para un Sistema CRM desde cero (2 de 5)
Segunda entrega: Aprende a hacer tus Controladores, DTO y AutoMapper.

Hola devs, este es el segundo de cinco posts donde estaremos creando un API con arquitectura RESTFul completo para hacer el backend de un sistema de gestión de relación con los clientes o más conocido por sus siglas en inglés: CRM (Customer relationship management) ya sabes el dicho... dilo en Inglés y cobrarás el triple 🤣 anotando cracks que ya empezamos en este provechoso proyecto. 😊

Para que no te pierdas, aquí están todas las entradas de la serie:

Configurando el proyecto

Hay algunas configuraciones que se deben hacer previamente para que no nos de errores.

Primero hay que actualizar las propiedades del proyecto: Clic derecho al proyecto y propiedades:

Luego en la pestaña Depurar configurar lo siguiente:

Ahora hay que configurar el archivo csproj ingresando directamente a su contenido xml, para esto doble clic al proyecto y se cargará su contenido en formato xml, añadir las siguientes líneas:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
    <NoWarn>$(NoWarn);1591</NoWarn>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
    <DocumentationFile></DocumentationFile>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.16" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.16" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.16">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
  </ItemGroup>

</Project>

Esto se hace para que se pueda cargar correctamente Swagger 😉

Si no hacemos esto tendremos problemas con el archivo de configuración que utiliza Swagger cada vez que cargues el proyecto en una PC distinta, para qué complicarte la vida?

Mira estos datitos que te lanzo crack 👀 sólo aquí, y son producto de la experiencia que uno va adquiriendo por prueba y error, aquí te lo comparto, lo mismo haz tu compartiendo esta entrada en tus redes, verás como despegas. 🚀

Creando el primer controlador: AgentesController

En la carpeta Controllers, clic derecho: Agregar clase y la nombramos AgentesController y de momento crearemos dos endpoints: Get y Post, también haremos uso de la inyección de dependencias mediante el constructor de la clase.

Deberías saber qué es Inyección de dependencias, aquí tengo una entrada que habla del tema.

    [ApiController]
    [Route("api/[controller]")]
    public class AgentesController : ControllerBase
    {
        private readonly ApplicationDBContext context;

        public AgentesController(ApplicationDBContext context)
        {
            this.context = context;
        }

        [HttpGet]
        public async Task<List<Agente>> Get()
        {
            return await context.Agentes.ToListAsync();
        }

        [HttpPost]
        public async Task<ActionResult> Post([FromBody] Agente agente)
        {
            var existe = await context.Agentes.AnyAsync(x => x.Nombre == agente.Nombre);
            if (existe)            
                return BadRequest($"Ya existe un agente con el nombre {agente.Nombre}");

            context.Add(agente);
            await context.SaveChangesAsync();
            return Ok();
        }
    }

Ahora ya podemos hacer una pequeña prueba, guardamos todo y compilamos.

Corremos el proyecto pulsando

CTRL + F5

Genial, funciona! 😊

Uso de Data Transfer Objects DTO

A estas alturas ya deberías saber que es esto de DTO, pero por si no, es fácil de entender: aquí hay una entrada que habla del tema.

En resumen, el patrón DTO lo que hace es crear clases intermedias entre las entidades y las acciones del negocio, así el usuario no trabaja directamente sobre una entidad, lo cual tiene desventajas: merma en la seguridad y además poca practicidad. Por ejemplo tu entidad puede tener 50 propiedades pero si tu sólo necesitas mostrar en un formulario 5 de ellas, cómo haces? igual te traes las 50? claro que no, es poco práctico, inseguro e ineficiente, felizmente para eso existen las clases intermedias DTO! Lo que deberías hacer es traerte la DTO creada para ese fin con sólo las 5 propiedades 😜.

Una clase DTO puede ser para crear, otra para mostrar, otra para mostrar pero algo más específico y así.

Creamos una carpeta llamada DTO y en ella estaremos agregando nuestras clases, de momento crearemos dos para la entidad Agente y dos para Prospecto:

Para Agente:

    public class AgenteCreacionDTO
    {
        [Required]
        [StringLength(70, ErrorMessage = "El campo {0} no debe exceder de {1} caracteres.")]
        public string Nombre { get; set; }
    }
    public class AgenteDTO
    {
        public int Id { get; set; }
        public string Nombre { get; set; }
    }

Para Prospecto:

    public class ProspectoCreacionDTO
    {
        [Required]
        [StringLength(70, ErrorMessage = "El campo {0} no debe exceder de {1} caracteres.")]
        public string Nombre { get; set; }
        public string UrlPerfil { get; set; }
    }
    public class ProspectoDTO
    {
        public int Id { get; set; }
        public string Nombre { get; set; }
        public string UrlPerfil { get; set; }
    }

En las clases DTO que modifican información se deben incluir también las reglas de validación o data annotations, es una buena práctica para que en el front-end estas validaciones tengan efecto.

La estructura del proyecto entonces va quedando así:

Configurando AutoMapper

Automapper es una librería que permite mapear de una forma más rápida y automatizada nuestros objetos, ya que usamos DTO entonces podrás notar que vamos a tener que estar mapeando entre uno y otro tipo de dato o clase.

Si quieres más información puedes leer su documentación o si eres más inteligente puedes checar y practicar esta entrada en la que te enseña a usarlo, practícala y pasarás de nivel cero a mid-level. RECOMENDADO.

Bueno vamos a instalarla en el proyecto, esto lo haremos desde Nuget, así que ábrelo y busca:

AutoMapper.Extensions.Microsoft.DependencyInjection

Instálalo

Ahora debemos actualizar el método ConfigureServices de la clase Startup, esto lo hago para poder utilizar inyección de dependencias con automapper, sirviendo un objeto de automapper a la clase que lo requiera:

        public void ConfigureServices(IServiceCollection services)
        {

            services.AddControllers();

            services.AddDbContext<ApplicationDBContext>(options =>
                {
                    options.UseSqlServer(Configuration.GetConnectionString("defaultConnection"));
                }
            );

            var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
            var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);

            services.AddSwaggerGen(config =>
            {
                config.SwaggerDoc("v1",
                    new Microsoft.OpenApi.Models.OpenApiInfo() //Swashbuckle.AspNetCore.Swagger.info() se usa en versiones anteriores
                    {
                        Title = "Ejemplo de swagger",
                        Description = "Esta es una documentación de swagger, aquí tambien puede ir información necesaria para utilizar el Api, etc..."
                    }
                );
                config.IncludeXmlComments(xmlPath);
            });

            //Configurando automapper
            services.AddAutoMapper(typeof(Startup));
        }

Debes saber ahora que los mapeos de Automapper no funcionan como si fuese un piloto automático mapeando todas tus clases, obviamente hay que configurar esto, y eso lo hacemos con lo que AutoMapper llama Perfiles, es simplemente una clase centralizada donde estarán las reglas de mapeo para tu proyecto, así de simple.

Así que creamos una carpeta y será para utilidades, por eso la llamaré Utils y dentro tendrá, entre otras cosas, la clase encargada del perfil de los mapeos, la llamaré AutoMapperProfile

using AutoMapper;
using CRM.DTO;
using CRM.Entidades;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace CRM.Utils
{
    public class AutoMapperProfile : Profile
    {
        public AutoMapperProfile()
        {
            //Aquí van las reglas de mapeo <origen,destino>
            CreateMap<AgenteCreacionDTO, Agente>();
            CreateMap<Agente, AgenteDTO>();
            CreateMap<ProspectoCreacionDTO, Prospecto>();
            CreateMap<Prospecto,ProspectoDTO>();
        }
    }
}

Nota cómo tuve que añadir los respectivos 'using' eh.

Aplicando mapeos DTO a los Controladores

Hasta el momento tenemos sólo un controlador si recuerdas: AgentesController

Ahora vamos a actualizarlo para que ya no utilice directamente las entidades sino mas bien utilice las clases DTO que creamos antes, y luego vamos a crear el segundo controlador ProspectosController.

También añadiremos más endpoints, o métodos a los controladores.

Actualizando AgentesController:

Como hemos configurado AutoMapper para que funcione con inyección de dependencias y además vamos a utilizarlo en nuestros controladores, hacemos la llamada desde el constructor del controlador al igual que hicimos con el contexto.

También reemplazaremos los objetos de las clases entidades por las DTO como se ve:

    [ApiController]
    [Route("api/[controller]")]
    public class AgentesController : ControllerBase
    {
        private readonly ApplicationDBContext context;
        private readonly IMapper mapper;

        public AgentesController(ApplicationDBContext context, IMapper mapper)//inyección de dependencias :)
        {
            this.context = context;
            this.mapper = mapper;
        }

        [HttpGet]
        public async Task<ActionResult<List<AgenteDTO>>> Get()
        {
            var agentes = await context.Agentes.ToListAsync();
            return mapper.Map<List<AgenteDTO>>(agentes);
        }

        [HttpGet("{id:int}")]
        public async Task<ActionResult<AgenteDTO>> Get(int id)
        {
            var agente = await context.Agentes.FirstOrDefaultAsync(x=>x.Id == id);

            if (agente == null)
                return NotFound("Registro no encontrado.");

            return mapper.Map<AgenteDTO>(agente);
        }

        [HttpPost]
        public async Task<ActionResult> Post([FromBody] AgenteCreacionDTO agenteCreacionDTO)
        {
            var existe = await context.Agentes.AnyAsync(x => x.Nombre == agenteCreacionDTO.Nombre);
            if (existe)            
                return BadRequest($"Ya existe un agente con el nombre {agenteCreacionDTO.Nombre}");

            var agente = mapper.Map<Agente>(agenteCreacionDTO);
            context.Add(agente);
            await context.SaveChangesAsync();
            return Ok();
        }
    }

Creando ProspectosController:

    [ApiController]
    [Route("api/[controller]")]
    public class ProspectosController : ControllerBase
    {
        private readonly ApplicationDBContext context;
        private readonly IMapper mapper;

        public ProspectosController(ApplicationDBContext context, IMapper mapper)
        {
            this.context = context;
            this.mapper = mapper;
        }

        [HttpGet]
        public async Task<ActionResult<List<ProspectoDTO>>> Get()
        {
            var prospectos = await context.Prospectos.ToListAsync();
            return mapper.Map<List<ProspectoDTO>>(prospectos);
        }

        [HttpGet("{id:int}")]
        public async Task<ActionResult<ProspectoDTO>> Get(int id)
        {
            var prospecto = await context.Prospectos.FirstOrDefaultAsync(x => x.Id == id);

            if (prospecto == null)
                return NotFound("Registro no encontrado.");

            return mapper.Map<ProspectoDTO>(prospecto);
        }

        [HttpPost]
        public async Task<ActionResult> Post([FromBody] ProspectoCreacionDTO prospectoCreacionDTO)
        {
            var existe = await context.Prospectos.AnyAsync(x => x.Nombre == prospectoCreacionDTO.Nombre);

            var prospecto = mapper.Map<Prospecto>(prospectoCreacionDTO);
            context.Prospectos.Add(prospecto);
            await context.SaveChangesAsync();
            return Ok();
        }

    }

Hasta el momento nuestro proyecto va quedando con la siguiente estructura:

Ahora toca hacer una prueba, corre el proyecto, ya sabes con:

CTRL + F5

Y tienes esto, genial! 😎

La interfaz de Swagger se actualizó automáticamente con los nuevos endpoints creados, eso es lo genial de Swagger, ahora prueba añadiendo registros, para esto abre el endpoint y dale en Try it out, en mi caso agrego al agente Gerson 😉

Perfecto, funciona y ahora lo obtengo!

Chequea tu SSMS y en la tabla de tu base de datos debe estar ese registro.

No te olvides de probar también las validaciones, busco el agente con id 2, el cual no existe:

Bien.

Sigue probando el otro controlador también y todos los endpoints!

Eso fue todo hasta ahora crack, nos vemos en la siguiente entrada, la parte 3 de este magnífico taller 🔥💪

Crack, estoy seguro que te está fascinando esto, así que comparte 😉

3 comentarios en «Creando un API RESTFul Completo con Net Core para un Sistema CRM desde cero (2 de 5)»

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *