ASP.NET Core MVC Numbered Pagination

Published on
-
4 min read

This is a relatively simple pagination that will only be shown if there are enough items of data to paginate through. The user will have the ability to paginate by either clicking on the "Previous" and "Next" links as well as clicking on the individual page numbers from within the pagination.

I created a PaginationHelper.CreatePagination() method that carries out all the paging calculations and outputs the pagination as an unordered list. The method requires the following parameters:

  • currentPage - the current page number being viewed.
  • totalNumberOfRecords - the total count of records from your dataset in order to determine how many pages should be displayed.
  • pageRequest - the current request from by passing in "HttpContext.Request" to get the page URL.
  • noOfPageLinks - the number of page numbers that should be shown. For example "1, 2, 3, 4".
  • pageSize - the number of items will be shown per page.
using Microsoft.AspNetCore.Http;
using System;
using System.Text;

namespace MyProject.Helpers
{
    public static class PaginationHelper
    {
        /// <summary>
        /// Renders pagination used in listing pages.
        /// </summary>
        /// <param name="currentPage"></param>
        /// <param name="totalNumberOfRecords"></param>
        /// <param name="pageRequest">Current page request used to get the URL path of the page.</param>
        /// <param name="noOfPagesLinks">Number of pagination numbers to show.</param>
        /// <param name="pageSize"></param>
        /// <returns></returns>
        public static string CreatePagination(int currentPage, int totalNumberOfRecords, HttpRequest pageRequest, int noOfPagesLinks = 5, int pageSize = 10)
        {
            StringBuilder paginationHtml = new StringBuilder();

            // Only render the pagination markup if the total number of records is more than our page size.
            if (totalNumberOfRecords > pageSize)
            {
                #region Pagination Calculations

                int amountOfPages = (int)(Math.Ceiling(totalNumberOfRecords / Convert.ToDecimal(pageSize)));

                int startPage = currentPage;

                if (startPage == 1 || startPage == 2 || amountOfPages < noOfPagesLinks)
                    startPage = 1;
                else
                    startPage -= 2;

                int maxPage = startPage + noOfPagesLinks;

                if (amountOfPages < maxPage)
                    maxPage = Convert.ToInt32(amountOfPages) + 1;

                if (maxPage - startPage != noOfPagesLinks && maxPage > noOfPagesLinks)
                    startPage = maxPage - noOfPagesLinks;

                int previousPage = currentPage - 1;
                if (previousPage < 1)
                    previousPage = 1;

                int nextPage = currentPage + 1;

                #endregion

                #region Get Current Path

                // Get current path.
                string path = pageRequest.Path.ToString();

                int pos = path.LastIndexOf("/") + 1;

                // Get last route value.
                string lastRouteValue = path.Substring(pos, path.Length - pos).ToLower();

                // Removes page number from end of path if path contains a page number.
                if (lastRouteValue.StartsWith("page"))
                    path = path.Substring(0, path.LastIndexOf('/'));

                #endregion

                paginationHtml.Append("<ul>");

                if (currentPage > 1)
                    paginationHtml.Append($"<li><a href=\"{path}/Page{previousPage}\"><span>Previous page</span></a></li>");

                for (int i = startPage; i < maxPage; i++)
                {
                    // If the current page equals one of the pagination numbers, set active state.
                    if (i == currentPage)
                        paginationHtml.Append($"<li><a href=\"{path}/Page{i}\" class=\"is-active\"><span>{i}</span></a></li>");
                    else
                        paginationHtml.Append($"<li><a href=\"{path}/Page{i}\"><span>{i}</span></a></li>");
                }

                if (startPage + noOfPagesLinks < amountOfPages && maxPage > noOfPagesLinks || currentPage < amountOfPages)
                    paginationHtml.Append($"<li><a href=\"{path}/Page{nextPage}\"><span>Next page</span></a></li>");

                paginationHtml.Append("</ul>");

                return paginationHtml.ToString();
            }
            else
            {
                return string.Empty;
            }
        }
    }
}

The PaginationHelper.CreatePagination() method can then be used inside a controller where you would like to list your data as well as render the pagination. A simple example of this would be as follows:

/// <summary>
/// List all news articles.
/// </summary>
/// <param name="page"></param> 
/// <param name="pageSize"></param>
/// <returns></returns>
[Route("/Articles")]
[Route("/Articles/Page{page}")]
public ActionResult Index(int page = 1, int pageSize = 10)
{
    // Number of articles to skip.
    int skip = 0;
    if (page != 1)
        skip = (page - 1) * pageSize;

    // Get list of articles from my datasource.
    List<NewsArticle> articles = MyData.GetArticles().Skip(skip).Take(pageSize).ToList();

    //Render Pagination.
    ViewBag.PaginationHtml = PaginationHelper.CreatePagination(page, articles.Count, HttpContext.Request, pageSize: pageSize);

    return View(articles);
}

The pagination will be output to a ViewBag that can be called from within your view. I could have gone down a different route and developed Partial View along with the appropriate model. But for my use the method approach offers most flexibility, as I could have the option to either use this from within a controller or view.

Before you go...

If you've found this post helpful, you can buy me a coffee. It's certainly not necessary but much appreciated!

Buy Me A Coffee

Leave A Comment

If you have any questions or suggestions, feel free to leave a comment. I do get inundated with messages regarding my posts via LinkedIn and leaving a comment below is a better place to have an open discussion. Your comment will not only help others, but also myself.