Cómo desarrollar un sistema de paginación en ASP.NET MVC

En entornos empresariales, trabajar con aplicaciones que manejan grandes volúmenes de datos es algo muy habitual. En este escenario de trabajo, realizar consultas sobre una base de datos que devuelvan una gran cantidad de registros, puede causar graves problemas de rendimiento en los servidores Web a la hora de tratar estos datos para ser enviados a los navegadores de los usuarios. Afortunadamente este problema se puede solucionar añadiendo un sistema de paginación de registros a las consultas realizadas sobre la base de datos.

A continuación veremos cómo desarrollar un sistema de paginación de registros en ASP.NET MVC 5, con Entity Framework como medio de acceso al origen de datos y Bootstrap 3.2 para dar formato y estilos a la Vista.

Paginación en ASP.NET MVC y Entity Framework

El modelo de datos

Para realizar este ejemplo, hemos utilizado el modelo de datos (tabla) Customers de la base de datos de pruebas Northwind de Microsoft (scripts disponibles para descargar al final del Post).

    public class Customer
    {
        [Key]
        [StringLength(5)]
        public string CustomerID { get; set; }

        [Required]
        [StringLength(40)]
        public string CompanyName { get; set; }

        [Required]
        [StringLength(30)]
        public string ContactName { get; set; }

        [StringLength(30)]
        public string ContactTitle { get; set; }

        [StringLength(100)]
        public string Address { get; set; }

        [StringLength(15)]
        public string City { get; set; }

        [StringLength(15)]
        public string Region { get; set; }

        [StringLength(10)]
        public string PostalCode { get; set; }

        [Required]
        [StringLength(15)]
        public string Country { get; set; }

        [StringLength(24)]
        public string Phone { get; set; }

        [StringLength(24)]
        public string Fax { get; set; }

        [Required]
        [StringLength(100)]
        [DataType(DataType.EmailAddress)]
        public string Email { get; set; }
    }

Como no es el objetivo de este Post explicar como enlazar Entity Framework a una base de datos, supondremos que ya hemos definido el DbContext de la aplicación con el correspondiente DbSet del modelo de datos Customers.

public class AppDbContext : DbContext
{
   public AppDbContext() : base("DataModelConnectionString")
   {

   }
   public DbSet<Customer> Customers { get; set; }
}

La Clase de paginación

Para poder mantener el estado de la paginación durante las peticiones de consulta a la aplicación, necesitamos implementar una clase auxiliar que nos permita almacenar, tanto los parámetros de paginación cómo los resultados devueltos por la búsqueda. 

Esta clase auxiliar de paginación, la definiremos como Clase genérica para que pueda ser utilizada para paginar cualquier tipo de modelo de datos:

    public class PaginadorGenerico<T> where T:class
    {
        public int PaginaActual { get; set; }
        public int RegistrosPorPagina { get; set; }
        public int TotalRegistros { get; set; }
        public int TotalPaginas { get; set; }
        public IEnumerable<T> Resultado { get; set; }
    }

El Controlador

En nuestra aplicación MVC creamos el Controlador CustomersController. A continuación, en la Acción por defecto Index() del Controlador, implementaremos el código para obtener de la base de datos los registros de la tabla Customers.

Hay que tener en cuenta que en un sistema de consultas paginadas, debemos de indicar en todo momento la página de registros que queremos recuperar desde el origen de datos. Esto lo haremos mediante un parámetro de entrada en la Acción Index(int pagina = 1), por defecto recuperamos siempre la primera página de registros.

    public class CustomersController : Controller
    {
        private readonly int _RegistrosPorPagina = 10;

        private AppDbContext _DbContext;        
        private List<Customer> _Customers;
        private PaginadorGenerico<Customer> _PaginadorCustomers;

        // GET: Customers
        public ActionResult Index(int pagina = 1)
        {            
            int _TotalRegistros = 0;
            using (_DbContext = new AppDbContext())
            {
                // Número total de registros de la tabla Customers
                _TotalRegistros = _DbContext.Customers.Count();
                // Obtenemos la 'página de registros' de la tabla Customers
                _Customers = _DbContext.Customers.OrderBy(x => x.ContactName)
                                                 .Skip((pagina - 1) * _RegistrosPorPagina)
                                                 .Take(_RegistrosPorPagina)
                                                 .ToList();
                // Número total de páginas de la tabla Customers
                var _TotalPaginas = (int)Math.Ceiling((double)_TotalRegistros/_RegistrosPorPagina);
               // Instanciamos la 'Clase de paginación' y asignamos los nuevos valores
                _PaginadorCustomers = new PaginadorGenerico<Customer>()
                {
                    RegistrosPorPagina = _RegistrosPorPagina,
                    TotalRegistros = _TotalRegistros,
                    TotalPaginas = _TotalPaginas,
                    PaginaActual = pagina,
                    Resultado = _Customers
                };
                // Enviamos a la Vista la 'Clase de paginación'
                return View(_PaginadorCustomers);
            }            
        }
    }

La Vista

Por último crearemos la Vista (Index.cshtml) asociada a la Acción Index(int pagina = 1) del Controlador Customers. La Vista recibirá como Modelo de datos un objeto de la clase PaginadorGenerico<Customer>, y contendrá una tabla HTML para representar los datos y el paginador de registros.

@model PaginadorGenerico<Customer>
@{
    ViewBag.Title = "Customers";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

@*CÓDIGO PARA LA TABLA DE DATOS*@
<table class="table table-bordered">
    <thead>
        <tr>
            <th>
                Contact Name
            </th>
            <th>
                Company Name
            </th>
            <th>
                Email
            </th>
        </tr>
    </thead>
    @foreach (var item in Model.Resultado)
    {
        <tbody>
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => item.ContactName)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.CompanyName)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Email)
                </td>
            </tr>
        </tbody>
    }
</table>

@*CÓDIGO PARA EL PAGINADOR DE REGISTROS*@
@if (Model.Resultado.Count() > 0)
{
    <span>
        <strong>@Model.TotalRegistros</strong> registros encontrados
    </span>
    <span>&nbsp;|&nbsp;</span>
    <span>
        Página <strong>@(Model.PaginaActual)</strong> de 
        <strong>@Model.TotalPaginas</strong>
    </span>
    <span>&nbsp;|&nbsp;</span>
}
else
{
    <span>No hay resultados para esta búsqueda</span>
    <span>&nbsp;|&nbsp;</span>
}

@if (Model.PaginaActual > 1)
{
    @Html.ActionLink("<<", "Index", new { pagina = 1 },
                    new { @class = "btn btn-sm btn-default" })
    <span></span>
    @Html.ActionLink("Anterior", "Index", new { pagina = Model.PaginaActual - 1 },
                    new { @class = "btn btn-sm btn-default" })
}
else
{
    @Html.ActionLink("<<", "Index", new { pagina = 1 },
                    new { @class = "btn btn-sm btn-default disabled" })
    <span></span>
    @Html.ActionLink("Anterior", "Index", new { pagina = 1 },
                    new { @class = "btn btn-sm btn-default disabled" })
}
<span></span>
@if (Model.PaginaActual < Model.TotalPaginas)
{
    @Html.ActionLink("Siguiente", "Index", new { pagina = Model.PaginaActual + 1 },
                    new { @class = "btn btn-sm btn-default" })
    <span></span>
    @Html.ActionLink(">>", "Index", new { pagina = Model.TotalPaginas },
                    new { @class = "btn btn-sm btn-default" })
}
else
{
    @Html.ActionLink("Siguiente", "Index", new { pagina = Model.TotalPaginas - 1 },
                    new { @class = "btn btn-sm btn-default disabled" })
    <span></span>
    @Html.ActionLink(">>", "Index", new { pagina = Model.TotalPaginas },
                    new { @class = "btn btn-sm btn-default disabled" })
}

Descargas

Script Tabla- dbo.Customers.sql
Datos de prueba - dbo.Customers.data.sql

   EtiquetasASP.NET .NET MVC C# Entity Framework

  Compartir


  Nuevo comentario

El campo Comentario es obligatorio.
El campo Nombre es obligatorio.

Enviando ...

  Comentarios

jhonnatan zevallos jhonnatan zevallos

Buenas tardes, tengo un problema al querer generar la vista, el modelo que indicas @model PaginadorGenerico<Customer> me lo toma como error, lo que intente usar como es @model WebApplication3.Models.ModeloPaginacion<Customer> donde WebApplication3 es el nombre de mi solucion, pero de este modo tampoco me lo toma bien sino me tira un error y me indica que no se encontro el nombre Customer. Espero que me puedas ayudar con esto, Muchas gracias.
Rafael Acosta Administrador Rafael Acosta

@jhonnatan zevallos:
Gracias por tu comentario y encantado de ayudarte.
Tu Vista no está reconociendo la clase 'Customer' ya que no puede resolver su ubicación en el proyecto (en parte es culpa mía por no indicarlo en el Post).
Tienes 2 alternativas para solucionar este problema:
1- Puedes indicar el 'namespace' y la carpeta donde se encuentran las clases 'Customer' y 'PaginadorGenerico' en la directiva @model de la Vista de la siguiente manera:
@model WebApplication3.Models.PaginadorGenerico. (Siempre y cuando las clases estén en la carpeta 'Models').
2 - La otra alternativa es importar el espacio de nombres de las clases 'Customer' y 'PaginadorGenerico', a través de la directiva @using al principio del código de la Vista :
@using WebApplication3.Models;
@model PaginadorGenerico
Un saludo.
Javier Javier

Gracias por el ejemplo, funciona muy bien. ¿Cómo haríamos para incluir un buscador y ordenación por columnas?
Rafael Acosta Administrador Rafael Acosta

@Javier:
Gracias a ti por tu comentario,
En cuanto a "incluir un buscador", supongo que te refieres a un filtro de búsqueda.
En el Post ( http://www.rafaelacosta.net/Blog/2018/5/28/cómo-desarrollar-un-filtro-de-búsqueda-por-texto-en-aspnet-mvc ), podrás ver cómo implementar sobre este mismo ejemplo, un filtro de búsqueda por texto con resultado paginado.
Efectivamente, para que el ejemplo quede completo, además de la paginación y la búsqueda faltaría la ordenación.
El Post sobre la ordenación por columnas, es algo que tenía pendiente y te agradezco me lo hayas recordado. En breve tengo previsto escribirlo y publicarlo, así que sigue pendiente al Blog y a las nuevas actualizaciones.
Un saludo.
Javier Javier

Exactamente, me refería al filtro por búsqueda.
Perfecto, muchas gracias por la información!
Saludos.
Emmanuel Emmanuel

Buenas tardes, tendrás algún ejemplo pero en vez de utilizar entity utilices SqlConection
Rafael Acosta Administrador Rafael Acosta

@Emmanuel:

Supongo que te refieres a utilizar ADO.NET en vez de Entity Framework. En el artículo de Microsoft Paginar un resultado de consulta , tienes un ejemplo bastante claro de cómo obtener un DatSet paginado.


Aun así, en el ejemplo de este artículo, la funcionalidad de paginación la ofrece LINQ mediante los métodos Skip() y Take() en conjunción con Entity Framework. De la misma manera, podrías utilizar LINQ con ADO.NET para paginar tus resultados.


 


Esau Mtz Esau Mtz

Hola, yo tengo un problema y es que al tiempo que pasa la viste el modelo viene null pero cuando pasa por el controlador este esta pasando los datos bien peor llega la vista y el controlador llega null
Rafael Acosta Administrador Rafael Acosta

@Esau Mtz: Hola, sinceramente no entiendo cual es el problema que estás teniendo. Si puedes explicarlo con mas claridad, incluso indicando en que parte del código tienes el error, estaré encantado de contestarte.


Un saludo.


Paula Paula

Excelente blog, el ejercicio expuesto esta claramente programado. Además en los comentarios se agrega el buscador. Gracias.
Rafael Acosta Administrador Rafael Acosta

@Paula:

Gracias por tu comentario, Compartir los artículos en redes sociales y foros de programación, es fundamental para que este Blog siga ofreciendo publicaciones de calidad y en español.


AFI AFI

WUAUUU genial!! muchisimas gracias!!! :D
german german

hola, he visto porque no usaste una interface y u implementacion? he visto otros ejemplos en ingles y casi todos realizan eso, que beneficioso es usar la interface?
Euclides Gonz&#225;lez Euclides González

Excelente!.. Me ayudó bastante para un proyecto que estoy realizando!..
Le añadiría un combo para seleccionar la cantidad de registros por página y un textbox para hacer una búsqueda en la tabla.
Muchos éxitos!

Perfil para Rafael Acosta en Stack Overflow en español, preguntas y respuestas para programadores y profesionales de la informática.

  Etiquetas

.NET Core .NET Framework .NET MVC .NET Standard AJAX ASP.NET ASP.NET Core ASP.NET MVC Bootstrap Buenas prácticas C# Cookies Entity Framework Gráficos JavaScript jQuery JSON JWT PDF Pruebas Unitarias Seguridad SEO SOAP Sql Server SqLite Swagger Validación Web API Web Forms Web Services WYSIWYG

  Nuevos


  Populares















Utilizamos cookies propias y de terceros para mejorar nuestros servicios y ofrecerle una mejor experiencia de navegación. Si continúa navegando consideramos que acepta su uso. Más información   Acepto