Reputation: 2184
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
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">«</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">»</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
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
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):
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
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>
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
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