2
votes

I'm learning asp.net core razor pages with ef. I want to implement pagination with my table, I have check this tutorial

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

but it only support pre and next, I have researched for a long time, all of the solution are related to asp.net core mvc, but I'm using razor pages, there's no controller in my project, any ideas to implement?

This is the effect what I want to implement

enter image description here

 <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>

cshtml.cs

  public async Task OnGetAsync(string sortOrder, string searchString, string shopString, string statusString, int page)
        {}
4
@Epistaxis any documentation about it? Thanks - Allen4Tech
@Epistaxis I checked the code, but it looks like it need a controller, I have found lots of package to support it, but I have no controller, I use razor pages only. Thanks - Allen4Tech

4 Answers

19
votes

Pagination is relatively simple. There's libraries available to do it for you, but I've started to find them more trouble than they're worth.

You need three pieces of information from the request (or set to default values):

  1. Page number (default 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 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.

Anyways, 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, just build yourself a model with this information, and you can send that to the view to render the results and generate the paging HTML.

4
votes

I have created a paging taghelper for .net Core Razor Pages, it can be configured within html tags or appsettings.json to show/ hide prev-next, first-last buttons, number of max displayed pages and more customization settings are available,

Install the nuget package:

Install-Package LazZiya.TagHelpers

Add the taghelper to _ViewImports.cshtml

@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>

[Update]

Starting from v3.1.0 query-string-value is not necessary to be passed, additonally 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 more clean html code and it gives the ability to change paging settings on all or some paging taghelpers by once in the application.

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

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

3
votes

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

2
votes

I made this implementation mixing together a few answers on the subject, I hope it helps someone.

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);