Hey devs! esta es la tercera de cinco entradas 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).
Para que no te pierdas, aqu铆 est谩n todas las entradas de la serie:
- https://bravedeveloper.com/2022/04/22/creando-un-api-restful-completo-con-net-core-para-un-sistema-crm-desde-cero-1-de-5/
- https://bravedeveloper.com/2022/04/27/creando-un-api-restful-completo-con-net-core-para-un-sistema-crm-desde-cero-2-de-5/
- https://bravedeveloper.com/2022/05/02/creando-un-api-restful-completo-con-net-core-para-un-sistema-crm-desde-cero-3-de-5/
- https://bravedeveloper.com/2022/05/13/creando-un-api-restful-completo-con-net-core-para-un-sistema-crm-desde-cero-4-de-5/
- https://bravedeveloper.com/2022/05/23/creando-un-api-restful-completo-con-net-core-para-un-sistema-crm-desde-cero-5-de-5/
En esta ocasi贸n aprender谩s a hacer una relaci贸n de uno a muchos entre dos entidades: Prospectos y contactos.
Los contactos son las veces que un agente visita o contacta a un prospecto, entonces si te fijas bien, un agente puede contactar una o mil veces a un prospecto hasta hacerlo cliente. Empezamos entonces 馃槈.
Actualizando Entidades
Creando entidad Contacto:
public class Contacto
{
[Key]
public int Id { get; set; }
[Required]
public DateTime Fecha { get; set; }
[Required]
[StringLength(25)]
public string Medio { get; set; }
[StringLength(250)]
public string Descripcion { get; set; }
public int ProspectoId { get; set; } //sirve de nexo con la entidad Prospecto
//Propiedades de navegaci贸n
public Prospecto Prospecto { get; set; } //es para poder navegar hasta los datos del prospecto
}
Si no ponemos la anotaci贸n StringLength, al momento de migrar a la base de datos se har铆a con el tipo de datos nvarchar(MAX) lo cual ser铆a un desperdicio de recursos. En este proyecto dej茅 la propiedad UrlPerfil de la clase entidad Prospecto sin esa anotaci贸n y f铆jate su tipo de datos c贸mo qued贸 馃憖
Nota adem谩s que a帽ad铆 una propiedad al final del tipo Prospecto, esta es una propiedad de navegaci贸n, ya que esta entidad est谩 relacionada con la entidad Prospecto, de esta forma le estoy diciendo a Entity Framework que estoy relacionando ambas tablas.
PRO TIP: Entity Framework Code first dice: Cuando dos entidades est谩n relacionadas con una relaci贸n uno a muchos, la entidad desde la cual parte la relaci贸n (osea entidad origen) llevar谩 una propiedad tipo List<> y la entidad destino, en este caso Contacto, llevar谩 una propiedad del mismo tipo que la entidad origen. Esto lo notar谩s a continuaci贸n...
Ahora entonces toca actualizar la entidad Prospecto, a帽adi茅ndole la propiedad tipo List<Contacto>
public class Prospecto
{
[Key]
public int Id { get; set; }
[Required]
[StringLength(70, ErrorMessage = "El campo {0} no debe exceder de {1} caracteres.")]
public string Nombre { get; set; }
public string UrlPerfil { get; set; }
//Propiedades de navegaci贸n
public List<Contacto> Contactos { get; set; }
}
Actualizar Contexto
Cada vez que a帽ades una nueva clase entidad, como ahora con el caso de Contacto, debes actualizar el contexto, en este proyecto es la clase ApplicationDBContext:
public class ApplicationDBContext : DbContext
{
public ApplicationDBContext(DbContextOptions options) : base(options)
{
}
public DbSet<Agente> Agentes { get; set; }
public DbSet<Prospecto> Prospectos { get; set; }
public DbSet<Contacto> Contactos { get; set; }
}
Aplicando migraci贸n
Como hemos actualizado nuestras entidades, eso significa nuevas tablas en la base de datos, adem谩s hemos inclu铆do relaciones entre tablas, para que todo esto se vea reflejado en la base de datos, ejecutamos una nueva migraci贸n, y as铆 debes ejecutar migraciones cada vez que quieras actualizar tu modelo f铆sico de base de datos.
Para aplicar una nueva migraci贸n debes crear una migraci贸n, yo la llamar茅 Contacto, el comando es el siguiente:
add-migration Contacto
Ahora ejecutamos la migraci贸n con:
update-database
Si revisas tu base de datos, deber铆as tener 3 tablas y dos de ellas relacionadas, con una relaci贸n tipo uno a muchos.
Para ver esto mejor, cr茅ate un diagrama de base de datos en SSMS.
Si tienes problemas para crear un nuevo diagrama de base de datos en tu base de datos CRM, haz clic derecho a tu base de datos, anda a Propiedades > Archivos > Propietario y agrega al usuario sa.
Para crear un diagrama, clic derecho a Diagramas de base de datos > Crear nuevo, a帽ade las tablas, excepto una tabla de metadatos creada por EF llamada _EFMigrationsHistory, ordena las tablas, elige una vista de tabla est谩ndar para cada una y guarda el diagrama:
A帽adiendo los DTO para Contacto
Agregamos por el momento dos DTO para la entidad nueva: ContactoDTO y ContactoCreacionDTO
ContactoDTO:
public class ContactoDTO
{
public int Id { get; set; }
public DateTime Fecha { get; set; }
public string Medio { get; set; }
public string Descripcion { get; set; }
}
ContactoCreacionDTO:
public class ContactoCreacionDTO
{
[Required]
public DateTime Fecha { get; set; }
[Required]
[StringLength(25)]
public string Medio { get; set; }
[StringLength(250)]
public string Descripcion { get; set; }
}
Como notar谩s no estoy incluyendo la propiedad ProspectoId, y es porque 茅sta ir谩 inclu铆da en la ruta del controlador ContactosController como ver谩s a continuaci贸n 馃槈
Controlador ContactosController
Agregar esta clase que ser谩 el controlador para la entidad Contactos
Por el momento este controlador tendr谩 dos endpoints: Un Get(id) y un Post
[ApiController]
[Route("api/prospectos/{prospectoId:int}/comentarios")] //Ojo con la ruta dependiente, desde aqu铆 se obtiene el prospectoId
public class ContactosController : ControllerBase
{
private readonly ApplicationDBContext context;
private readonly IMapper mapper;
public ContactosController(ApplicationDBContext context, IMapper mapper)
{
this.context = context;
this.mapper = mapper;
}
[HttpGet]
public async Task<ActionResult<List<ContactoDTO>>> Get(int prospectoId)
{
var existeProspecto = await context.Prospectos.AnyAsync(x => x.Id == prospectoId);
if (!existeProspecto)
return NotFound("El prospecto no existe");
var contactos = await context.Contactos.Where(x => x.ProspectoId == prospectoId).ToListAsync();
return mapper.Map<List<ContactoDTO>>(contactos);
}
[HttpPost]
public async Task<ActionResult> Post(int prospectoId, ContactoCreacionDTO contactoCreacionDTO)
{
var existeProspecto = await context.Prospectos.AnyAsync(x => x.Id == prospectoId);
if (!existeProspecto)
return NotFound("El prospecto no existe");
var contacto = mapper.Map<Contacto>(contactoCreacionDTO);
contacto.ProspectoId = prospectoId;
context.Contactos.Add(contacto);
await context.SaveChangesAsync();
return Ok();
}
}
A帽adir los perfiles de mapeo AutoMapper
Vamos a la clase AutoMapperProfile y a帽adimos:
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>();
CreateMap<ContactoCreacionDTO, Contacto>();
CreateMap<Contacto, ContactoDTO>();
}
}
Pruebas en Swagger
Una vez hecho todo esto, toca que pruebes en Swagger, veamos:
Aprovechamos para actualizar la propiedad Title dentro de la configuraci贸n de Swagger en la clase Startup y el m茅todo ConfigureServices le pondr茅 Sistema CRM馃槈
Tenemos esto, genial!
He creado tres prospectos:
Probaremos entonces un error, tratar茅 de obtener los contactos del prospecto con Id que no existe, digamos Id = 4
Muy bien, est谩 validando bien 馃槈.
Ahora creo un par de contactos y luego obtendr茅 los contactos realizados a nuestro prospecto con Id = 3 llamada Rosa Mart铆nez (el nombre de mi madre 馃槝)
Y se nos retorna lo correcto, los dos contactos que tiene, GENIAL! 馃槑
Nuestro API ya va tomando forma crack! 馃榾
Seguiremos en la pr贸xima entrega aprendiendo m谩s cosas interesante, y que te dar谩n de comer mi estimado lector as铆 que a echarle ganas 馃槈
Seguro que est谩s feliz como estos devs 馃榿
Si esta entrada te ha encantado, seguro que s铆, entonces ya sabes qu茅 hacer crack! Comparte 馃槈
2 comentarios en 芦Creando un API RESTFul Completo con Net Core para un Sistema CRM desde cero (3 de 5)禄