Allen4Tech
Allen4Tech

Reputation: 2184

How to implement pagination in ASP.NET Core Razor Pages

I'm learning ASP.NET Core Razor Pages with Entity Framework, and I want to implement pagination with my table. I have checked this tutorial:

https://learn.microsoft.com/en-us/aspnet/core/data/ef-rp/sort-filter-page?view=aspnetcore-2.1

but it only supports previous and next pages, with no specific page options.

All the solutions I've found to date are MVC implementations, but I'm using Razor Pages; there's no controller in my project.

This is the effect what I want to implement

screenshot of a pagination list with previous and next buttons, but also with several specific-page options, too

This is my .cshtml page code:

<form method="get" asp-page="./Index">
    <nav aria-label="Page navigation">
        <ul class="pagination">
            <li>
                <a href="#" aria-label="Previous">
                    <span aria-hidden="true">&laquo;</span>
                </a>
            </li>
            @{
                var totalPages = Model.Products.Count % 2 == 0 ? Model.Products.Count / 2 : Model.Products.Count / 2 + 1;
            }
            @for (int i = 1; i <= totalPages; i++)
            {
                <li><a asp-page="./Index" asp-route-id="@i">@i</a></li>
            }
            <li>
                <a href="#" aria-label="Next">
                    <span aria-hidden="true">&raquo;</span>
                </a>
            </li>
        </ul>
    </nav>
</form>

And my cshtml.cs code-behind:

public async Task OnGetAsync(string sortOrder, string searchString, string shopString, string statusString, int page) 
{}

Upvotes: 8

Views: 40303

Answers (4)

lolol
lolol

Reputation: 4390

I made this implementation mixing together a few answers on the subject using Entity Framework Core:

Add a PagedResultBase class (that you can extend adding other properties you need):

public abstract class PagedResultBase
{
    public int CurrentPage { get; set; }
    public int PageCount { get; set; }
    public int PageSize { get; set; }
    public int RowCount { get; set; }
}

Add a PagedResult class:

public class PagedResult<T> : PagedResultBase where T : class
{
    public ICollection<T> Results { get; set; }

    public PagedResult()
    {
        Results = new List<T>();
    }
}

Add a IQueryableExtensions with a GetPagedResult extension:

public static class IQueryableExtensions
{
    public async static Task<PagedResult<T>> GetPagedResultAsync<T>(this IQueryable<T> query, int currentPage, int pageSize) where T : class
    {
        var skip = (currentPage - 1) * pageSize;
        var take = pageSize;

        var rowCount = await query.CountAsync();
        var results = await query.Skip(skip).Take(take).ToListAsync();

        var pagedResult = new PagedResult<T> {
            CurrentPage = currentPage,
            PageCount = (int)Math.Ceiling(decimal.Divide(rowCount, pageSize)),
            PageSize = pageSize,
            RowCount = rowCount,
            Results = results
        };

        return pagedResult;
    }
}

You are done:

var pagedResult = await MyContext.Posts.Where(p => p.Featured == true).GetPagedResultAsync(1, 10);

Upvotes: 3

Chris Pratt
Chris Pratt

Reputation: 239200

There are libraries available to do pagination for you, but I've started to find them more trouble than they're worth.

To implement pagination yourself, you need three pieces of information from the request (or set to default values):

  1. Page number (defaults to 1)
  2. Page size (typically defaults to 10, but whatever you want)
  3. Sort (not strictly necessary, but you should at least order by something to keep the results consistent across pages)

The page number and size give you your "skip" and "take" values:

var skip = (page - 1) * size;
var take = size;

You can then fetch the results via:

var pageOfResults = await query.Skip(skip).Take(take).ToListAsync();

where query is an IQueryable - either your DbSet directly or the DbSet with a Where clause, OrderBy, etc. applied.

Then, you just need to get the total number of items to figure the pages:

var count = await query.CountAsync();

Pro Tip, you can parallelize the two queries (results and total count) by doing:

var resultsTask = query.Skip(skip).Take(take).ToListAsync();
var countTask = query.CountAsync();

var results = await resultsTask;
var count = await countTask;

Tasks return hot, or already started. The await keyword simply holds the continuation of the rest of the code until the task completes. As a result, if you await each line, they'll complete in serial, but if you start both first, and then await each, they'll process in parallel.

Anyway, once you have the count:

var totalPages = (int)Math.Ceil(Decimal.Divide(count, size));
var firstPage = 1;
var lastPage = totalPages;
var prevPage = Math.Max(page - 1, firstPage);
var nextPage = Math.Min(page + 1, lastPage);

Note: you can determine whether to show first/previous and last/next buttons based on whether they equal firstPage or lastPage, respectively.

Then, build a model with this information, and you can send that to the view to render the results and generate the paging HTML.

Upvotes: 25

LazZiya
LazZiya

Reputation: 5719

I have created a paging tag helper for .NET Core Razor Pages; it can be configured within HTML tags or the appsettings.json file to show/hide prev-next, first-last buttons, a number of max displayed pages, and more.

Install the NuGet package:

Install-Package LazZiya.TagHelpers

Add the tag helper to the _ViewImports.cshtml file:

@addTagHelper *, LazZiya.TagHelpers

Finally, add the paging control to the view:

<paging 
    total-records="Model.TotalRecords" 
    page-no="Model.PageNo"
    query-string-value="@(Request.QueryString.Value)">
</paging>

Starting from v3.1.0, the query-string-value no longer needs to be passed. Additionally all options is turned on by default.

<paging 
    total-records="Model.TotalRecords" 
    page-no="Model.PageNo">
</paging>

PagingTagHelper

Using appsettings.json for paging configurations will help to have cleaner HTML code, and gives the ability to change paging settings on all or some paging tag helpers at once in the application.

See a live demo for all settings: https://demo.ziyad.info/en/paging

Related article:
https://ziyad.info/en/articles/21-Paging_TagHelper_for_ASP_NET_Core

Upvotes: 10

Jason Watmore
Jason Watmore

Reputation: 4701

You can use the JW.Pager NuGet package (https://www.nuget.org/packages/JW.Pager/)

Here's an example razor pages page model that paginates a list of 150 items:

using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc.RazorPages;
using JW;

namespace RazorPagesPagination.Pages
{
    public class IndexModel : PageModel
    {
        public IEnumerable<string> Items { get; set; }
        public Pager Pager { get; set; }

        public void OnGet(int p = 1)
        {
            // generate list of sample items to be paged
            var dummyItems = Enumerable.Range(1, 150).Select(x => "Item " + x);

            // get pagination info for the current page
            Pager = new Pager(dummyItems.Count(), p);

            // assign the current page of items to the Items property
            Items = dummyItems.Skip((Pager.CurrentPage - 1) * Pager.PageSize).Take(Pager.PageSize);
        }
    }
}

And here's the razor pages page containing the html for the paged list and pager controls:

@page
@model RazorPagesPagination.Pages.IndexModel

<!-- items being paged -->
<table class="table table-sm table-striped table-bordered">
    @foreach (var item in Model.Items)
    {
        <tr>
            <td>@item</td>
        </tr>
    }
</table>                

<!-- pager -->
@if (Model.Pager.Pages.Any())
{
    <nav class="table-responsive">
        <ul class="pagination justify-content-center d-flex flex-wrap">
            @if (Model.Pager.CurrentPage > 1)
            {
                <li class="page-item">
                    <a class="page-link" href="/">First</a>
                </li>
                <li class="page-item">
                    <a class="page-link" href="/?p=@(Model.Pager.CurrentPage - 1)">Previous</a>
                </li>
            }

            @foreach (var p in Model.Pager.Pages)
            {
                <li class="page-item @(p == Model.Pager.CurrentPage ? "active" : "")">
                    <a class="page-link" href="/?p=@p">@p</a>
                </li>
            }

            @if (Model.Pager.CurrentPage < Model.Pager.TotalPages)
            {
                <li class="page-item">
                    <a class="page-link" href="/?p=@(Model.Pager.CurrentPage + 1)">Next</a>
                </li>
                <li class="page-item">
                    <a class="page-link" href="/?p=@(Model.Pager.TotalPages)">Last</a>
                </li>
            }
        </ul>
    </nav>
}

For more details I posted a full tutorial with example project at http://jasonwatmore.com/post/2018/10/15/aspnet-core-razor-pages-pagination-example

Upvotes: 5

Related Questions