Implementar paginación, filtros y búsqueda en una API .NET es una de las mejores prácticas para crear servicios escalables y eficientes. Cuando una aplicación comienza a manejar miles o incluso millones de registros, devolver toda la información en una sola petición deja de ser una opción. Una API bien diseñada debe permitir que el cliente solicite únicamente los datos que realmente necesita.
Implementando la paginación
La forma más común consiste en recibir dos parámetros: pageNumber y pageSize.
[HttpGet]
public async Task<IActionResult> GetProducts(
int pageNumber = 1,
int pageSize = 10)
{
var products = await _context.Products
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return Ok(products);
}
Skip() omite los registros anteriores y Take() limita la cantidad de resultados devueltos. Es recomendable establecer un tamaño máximo de página para evitar consultas excesivamente grandes.
pageSize = Math.Min(pageSize, 100);
De esta forma, aunque un cliente solicite 1.000 registros, la API solo devolverá el máximo permitido.
Agregando filtros
Los filtros permiten obtener únicamente la información relevante.
Por ejemplo:
- Categoría
- Precio mínimo y máximo
- Estado
- Fecha
- Disponibilidad
var query = _context.Products.AsQueryable();
if (!string.IsNullOrEmpty(category))
query = query.Where(x => x.Category == category);
if (minPrice.HasValue)
query = query.Where(x => x.Price >= minPrice);
if (maxPrice.HasValue)
query = query.Where(x => x.Price <= maxPrice);
Al construir la consulta de manera dinámica con IQueryable, Entity Framework genera una única consulta SQL optimizada.
Implementando búsqueda
La búsqueda normalmente se realiza sobre uno o varios campos de texto.
if (!string.IsNullOrWhiteSpace(search))
{
query = query.Where(x =>
x.Name.Contains(search) ||
x.Description.Contains(search));
}
En bases de datos grandes también pueden utilizarse índices o motores especializados como Elasticsearch o Azure AI Search, pero para la mayoría de proyectos Contains() resulta suficiente.
Combinando todo
Lo ideal es aplicar primero los filtros, después la búsqueda y finalmente la paginación.
var totalRecords = await query.CountAsync();
var data = await query
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
Además de los datos, es recomendable devolver información adicional:
{
"pageNumber": 2,
"pageSize": 10,
"totalRecords": 157,
"totalPages": 16,
"data": []
}
Esto facilita que el frontend pueda construir controles de navegación sin realizar consultas adicionales.
Utilizando un modelo de consulta
En lugar de recibir muchos parámetros separados, es más limpio crear una clase.
public class ProductQuery
{
public int PageNumber { get; set; } = 1;
public int PageSize { get; set; } = 10;
public string? Search { get; set; }
public string? Category { get; set; }
public decimal? MinPrice { get; set; }
public decimal? MaxPrice { get; set; }
}
Esto hace que el controlador sea mucho más fácil de mantener y ampliar.
Buenas prácticas
- Limita el tamaño máximo de página.
- Utiliza
IQueryablepara construir consultas dinámicas. - Aplica filtros antes de paginar.
- Devuelve el total de registros y páginas.
- Usa índices en los campos de búsqueda.
- Evita cargar datos innecesarios con
Select()cuando solo necesites algunas columnas.
La combinación de paginación, filtros y búsqueda convierte una API ASP.NET Core en un servicio mucho más rápido, escalable y profesional. Estas técnicas reducen el consumo de memoria, disminuyen el tráfico de red y mejoran significativamente la experiencia de los usuarios, especialmente cuando la cantidad de información crece con el tiempo. Una buena implementación desde el inicio hará que tu API esté preparada para soportar aplicaciones empresariales y de alto rendimiento sin necesidad de grandes cambios en el futuro.

