Pagination in .NET Core / 5 / 6 MVC with Entity Framework Core

Posted by Gal Ratner on 10/20/2021

Pagination is useful with large results sets. There are plenty of solutions out there to create next and previous buttons, but I couldn’t find anything that creates a nice Bootstrap paginator so I just wrote my own:


To use it first add a The PaginatedList class to your code. This is a Modified version of the one found on MSDN.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
 
namespace Messages.Utilities
{
    public class PaginatedList<T> : List<T>
    {
        public int PageIndex { get; private set; }
        public int TotalPages { get; private set; }
 
        public PaginatedList(List<T> items, int count, int pageIndex, int pageSize)
        {
            PageIndex = pageIndex;
            TotalPages = (int)Math.Ceiling(count / (double)pageSize);
 
            this.AddRange(items);
        }
 
        public bool HasPreviousPage
        {
            get
            {
                return (PageIndex > 1);
            }
        }
 
        public bool HasNextPage
        {
            get
            {
                return (PageIndex < TotalPages);
            }
        }
 
        public static async Task<PaginatedList<T>> CreateAsync(
            IQueryable<T> source, int pageIndex, int pageSize)
        {
            var count = await source.CountAsync();
            var items = await source.Skip(
                (pageIndex - 1) * pageSize)
                .Take(pageSize).ToListAsync();
            return new PaginatedList<T>(items, count, pageIndex, pageSize);
        }
 
        public static PaginatedList<T> Create(
            List<T> source, int pageIndex, int pageSize)
        {
            var count = source.Count();
            var items = source.Skip(
                (pageIndex - 1) * pageSize)
                .Take(pageSize).ToList();
            return new PaginatedList<T>(items, count, pageIndex, pageSize);
        }
    }
}

Next add the _Pagination partial view to your shared views folder

<nav aria-label="Blog Pages">
    <ul class="pagination justify-content-center">
        <li class="page-item @(Model.PageIndex <= 1 ? "disabled" : string.Empty)"><a class="page-link" asp-route-pageNumber="1">First</a></li>
        <li class="page-item @(!Model.HasPreviousPage ? "disabled" : string.Empty)"><a class="page-link" asp-route-pageNumber="@(Model.PageIndex - 1)">Previous</a></li>
 
        @for (int i = Model.PageIndex - 3; i <= Model.PageIndex - 1; i++)
        {
            @if (i < 1)
                continue;
            <li class="page-item"><a class="page-link" asp-route-pageNumber="@i">@i</a></li>
        }
 
        @for (int i = Model.PageIndex; i <= Model.PageIndex + 3; i++)
        {
            @if (i > Model.TotalPages)
                break;
            <li class="page-item @(Model.PageIndex == i ? "active" : string.Empty)"><a class="page-link" asp-route-pageNumber="@i">@i</a></li>
        }
 
        <li class="page-item @(!Model.HasNextPage ? "disabled" : string.Empty)"><a class="page-link" asp-route-pageNumber="@(Model.PageIndex + 1)">Next</a></li>
        <li class="page-item @(Model.PageIndex >= Model.TotalPages ? "disabled" : string.Empty)"><a class="page-link" asp-route-pageNumber="@(Model.TotalPages)">Last</a></li>
    </ul>
</nav>

Now you are ready to use the PaginatedList from your controller

[Route("Blog/")]
public async Task<IActionResult> Index(int? pageNumber)
{
     int pageSize = 20;
     return View(PaginatedList<BlogPost>.Create(await _cache.GetBlogPostsAync(), pageNumber ?? 1, pageSize));
}

And finally add the paginator to your view

<partial name="~/Views/Shared/_Paginator.cshtml" />

The result should be a nice Bootstrap paginatior with page numbers, next, previous, first and last buttons.


Did you find this post useful? Visit my software consulting firm Inverted Software


No Comments Yet. Join The Conversation