Primero un poquito de contexto...
Qué haces cuando un endpoint de tu API retorna muchos registros, digamos tienes un API de una tienda de autopartes "Automotive Store" como utilizaste una arquitectura REST el URL del API de tu aplicación web es:
automotivestore.com/api
Para acceder a todos los clientes tendrías que hacer un request a un endpoint similar a:
automotivestore.com/api/clientes
Esta petición te retornaría digamos 10000 clientes ya que supongamos que esa es la cantidad de clientes que tienes en tu base de datos.
En principio esto no parecería un problema pero lo es porque son muchos registros los que tienes que devolver en una sola petición HTTP lo cual sería un problema de rendimiento, pero además también un problema de seguridad ya que para qué devolverías todo de uno solo?, y finalmente un problema de diseño ya que le estás pasando al frontend una responsabilidad muy grande: la de gestionar tal cantidad de información.
Imagínate si tu negocio crece y ya no tienes 10 mil sino 500 mil, te imaginas devolviendo eso en una sola petición HTTP? 🥵
Recuerda siempre como ingeniero de software debes diseñar tus sistemas con la escalabilidad en mente, esa es una cualidad de Software, eso marca la diferencia entre amateur y profesional.
Ahora imagina la base de datos de clientes de Microsoft? Qué harían ellos? Pues paginarían, entonces a eso debes apuntar tu también.
Aquí lo haremos de la forma correcta, es decir con buenas prácticas, ya que es posible paginar también en el Frontend, pero ello es un error de diseño, ya que esa responsabilidad debe estar a cargo del backend.
Afortunadamente a continuación aprenderás a paginar bien, empecemos 😉
Conceptos
La paginación es una técnica donde un endpoint GET no te retorna todos los registros sino que te retorna los registros de forma controlada, es decir por partes.
En términos técnicos, esto es lo que le pasa al endpoint GET que usa paginación de forma correcta:
- En el request el usuario envia la cantidad de registros por página y el número de página que quiere visualizar.
- En el head de la respuesta HTTP el API retorna la cantidad total de registros.
- En el body de la respuesta HTTP retorna los registros según la página y la cantidad solicitados.
Proyecto
El proyecto al que vamos a aplicar la paginación lo he creado para hacer esta demostración.
Tenemos un web API simple con un controlador y un endpoint GET
He ingresado de forma manual desde el SSMS (Sql server management studio) 20 registros como clientes:
Entonces al ejecutar el API y hacer la petición al único endpoint que existe obtengo esta pantalla de Swagger, la cual muestra los 20 registros de una vez.
Ese endpoint GET podría ser el tuyo al cual quieres aplicar paginación 😉
Aplicando Paginación
Primero lo primero, para aplicar paginación es necesario aplicar el patrón DTO, no te asustes simplemente se trata de crear una carpeta, a la que llamaré DTO y dentro tendrá clases que harán de intermediarias entre el usuario y las entidades, ya que a veces necesitamos mostrar o pedir información al usuario y esa estructura que pedimos no tiene una clase existente o no existe como entidad, para eso sirve el patrón DTO.
Si quieres aprender más del patrón DTO tengo dos entradas en este blog aquí te lo explico todo con ejemplo 💪 y aquí lo aplico a un proyecto 😉
1. Crear un nuevo DTO
En la carpeta DTO crear la clase PaginacionDTO:
public class PaginacionDTO
{
public int Pagina { get; set; } = 1;
private int recordsPorPagina = 5;
private readonly int cantidadMaximaPorPagina = 10;
public int RecordsPorPagina
{
get { return recordsPorPagina; }
set
{
recordsPorPagina = (value > cantidadMaximaPorPagina) ? cantidadMaximaPorPagina : value;//previene que el usuario mande cantidades incoherentes de registros por pág.
}
}
}
Aquí hay dos cosas muy importantes:
- Establecemos dos valores por defecto: la página a retornar si no nos la mandan será la número 1
- Si no nos mandan la cantidad de registros por página lo establecemos en 5, sin embargo para evitar que nos manden valores incoherentes como por ejemplo 5000, he añadido una pequeña lógica que en la cual establecemos un valor máximo a criterio nuestro, en este caso le pondré 10 ya que mi API tiene 20 registros, pero un valor comúnmente usado sería 50.
2. Crear métodos auxiliares
Crear la carpeta Utils y dentro codificaremos dos clases estáticas: HttpContextExtension y IQueryableExtensions
public static class HttpContextExtension
{
public async static Task InsertPaginationHeader<T>(this HttpContext httpContext, IQueryable<T> queryable)
{
if (httpContext == null)
throw new ArgumentNullException(nameof(httpContext));
double cantidad = await queryable.CountAsync();
httpContext.Response.Headers.Add("CantidadTotalRegistros", cantidad.ToString());
}
}
public static class IQueryableExtensions
{
public static IQueryable<T> Paginate<T>(this IQueryable<T> queryable, PaginacionDTO paginacionDTO)
{
return queryable
.Skip((paginacionDTO.Pagina - 1) * paginacionDTO.RecordsPorPagina)
.Take(paginacionDTO.RecordsPorPagina);
}
}
3. Actualizar el endpoint GET
Ya tenemos casi todo listo, lo que resta por hacer es actualizar nuestro controlador, en particular nuestro endpoint para aplicarle la funcionalidad de la paginación
[Route("api/[controller]")]
[ApiController]
public class ClientesController : ControllerBase
{
private readonly ApplicationDbContext context;
public ClientesController(ApplicationDbContext context)
{
this.context = context;
}
[HttpGet]
public async Task<List<Cliente>> Get([FromQuery]PaginacionDTO paginacionDTO)//esto a partir de ahora se le pedirá al usuario
{
//Aplicando paginación
var queryable = context.Clientes.AsQueryable();
await HttpContext.InsertPaginationHeader(queryable);
var clientes = await queryable.OrderBy(x => x.Id).Paginate(paginacionDTO).ToListAsync();//es recomendable ordenar cuando se pagina
return clientes;
}
}
Un tip: Una buena práctica es que no retornes una lista de la entidad Cliente, sino un DTO pero esa no es la finalidad de este post así que te lo dejo de tarea 😉 En los links de arriba sobre DTO explico eso, por si te interese.
Y llegó la hora de probar nuestra paginación 😀
Hora de las pruebas funcionales!
Probemos sin mandar los parámetros a ver si funcionan los valores por defecto que hemos configurado tanto para el número de página como para el número de registros por página
Ahora enviemos valores en los parámetros:
Ahora digamos que le enviamos un valor no coherente como la página 1 y 1000 registros por página:
Nota que sin importar que le hayamos pedido mil registros por página, nos retorna 10 que es el valor máximo que hemos configurado.
Además nota como en el header nos retorna también la cantidad total de registros, esto es para que el Frontend pueda hacer sus cálculos y saber cuantas páginas va a tener.
En la vida real...
Este blog es un ejemplo de paginación:
Cómo sabe el frontend cuantas páginas mostrar?
Fácil, pues en la cabecera tiene la información de la cantidad total de registros, digamos 95 entonces como se muestran 9 posts por página hace un cálculo simple y serían 11 páginas: 10 páginas completas y 1 sólo con 5 posts.
Ahora ya sabes como paginar crack! aplícalo en tus proyectos 😉🔥 y recuerda compartir esta entrada por supuesto!💪
Este proyecto completo está disponible en mis repositorios de GitHub, sígueme! 😉 https://github.com/GeaSmart/AutomotivePagingAPI
Compártela en tus redes sociales, eso ayuda un montón a todos (incluído yo 😊)
Buenas tardes Gerson, recibe un cordial saludo desde el norte de México, creo que encontré tu blog atravez de un video de youtube y llamó mi atención esto: https://bravedeveloper.com/2020/12/19/ruta-de-aprendizaje-para-net-y-net-core/
He intentado aprender programación por mi cuenta (pdfs y youtube, un curso en udemy en ingles que no me dejo convencido, mayoritariamente en C#) desde hace 3 años y es la primera vez que encuentro una infografía de una ruta de aprendizaje de programación, he hecho pequeños proyectos muy básicos IoT con C# y ESP32 (Arduino) que cumplen su función pero me gustaría hacer la ruta completa que propones y no tomar atajos, me voy dando cuenta que esta página esta en formato de blog personal, mi pregunta es la siguiente:
¿Todo el contenido especificado en la ruta de aprendizaje que mencionas está disponible en este sitio (ya vi que esta los fundamentos de POO muy bien hecho) ? ¿O es más una referencia?
Muchas gracias por compartir tu conocimiento! Te deseo mucho éxito!
Carlos Ibarra
Hola Carlos, mucho gusto. En primer lugar muchas gracias por visitar mi blog!
La programación es una forma de ganarse la vida, la cual tiene muchas ventajas, es muy probable que te gustará así que te animo a seguir aprendiendo.
Respondiendo a tus preguntas:
– No todo el contenido que planteo en la ruta de aprendizaje de .NET está desarrollado en este blog, por ejemplo Programación orientada a objetos sí esta completo, luego las demás ramas no están del todo contempladas, sin embargo algunas otras sí como por ejemplo MVC, Patrones, Git, Scrum, linq, entity framework son algunas de las que sí toqué el tema. Como ya son 100 artículos a día de hoy en mi blog 😁, te ayudará buscar el tema con el buscador que aparece en la sección lateral derecha de la página Blog.
– Otro punto importante es que la mayoría de los artículos que sí he tocado son eminentemente prácticos, pero te recomiendo bastante poder llevar en paralelo un curso en udemy por ejemplo, un profesor que recomiendo en temas de C# es Felipe Gavilán, puedes echar un vistazo a sus cursos, si sabes inglés entonces mucho mejor, te puedo recomendar otros cursos más en ese idioma.
– Ojo que no tienes que dominar toda la ruta de aprendizaje para dedicarte a esto en tecnología .NET, así que tranquilo, pero sí va a requerir de insistencia y aprendizaje por repetición y práctica.
– Puedes añadirme a linkedin si es que tienes uno para más consejos y compartir experiencias.
Éxitos en tus metas y que tengas una excelente semana crack.