Intro
This time, I will try X.PagedList for pagenation for Razor.
Environments
- .NET ver.6.0.100-preview.2.21155.3
- Microsoft.EntityFrameworkCore ver.6.0.0-preview.2.21154.2
- Npgsql.EntityFrameworkCore.PostgreSQL ver.6.0.0-preview2
- NLog.Web.AspNetCore ver.4.11.0
- Microsoft.EntityFrameworkCore.Design ver.6.0.0-preview.2.21154.2
- X.PagedList ver.8.0.7
- X.PagedList.Mvc.Core ver.8.0.7
package.json (To use bootstrap)
{
"browserslist": [
"last 2 version"
],
"dependencies": {
"autoprefixer": "^10.2.5",
"bootstrap": "^4.6.0",
"postcss": "^8.2.8",
"postcss-cli": "^8.3.1",
"postcss-import": "^14.0.0"
},
"scripts": {
"css": "npx postcss postcss/*.css -c postcss.config.js -d wwwroot/css -w"
}
}
Base project
Except the package versions, I used this project.
Use X.PagedList
According to the Example, I just need 1.getting "IQueryable" items, 2.converting to IPagedList, and 3.setting into "@Html.PagedListPager".
BookService.cs
using System.Linq;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using BookStoreSample.Applications;
using BookStoreSample.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace BookStoreSample.Books
{
public class BookService: IBookService
{
...
public IQueryable<Book> GetBooks(int? authorId)
{
if(authorId != null && authorId > 0)
{
return context.Books.Include(b => b.Author)
.Include(b => b.Genre)
.Where(b => b.AuthorId == authorId);
}
return context.Books.Include(b => b.Author)
.Include(b => b.Genre);
}
public async Task<List<Author>> GetAuthorsAsync()
{
return await context.Authors.ToListAsync();
}
}
}
HomeController.cs
using System.Collections.Generic;
using System.Threading.Tasks;
using BookStoreSample.Applications;
using BookStoreSample.Books;
using BookStoreSample.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using X.PagedList;
namespace BookStoreSample.Controllers
{
public class HomeController: Controller
{
...
[Route("Pages/Razor")]
public IActionResult OpenRazorPage(int? Page)
{
// Get items as IQueryable<Book> and convert to IPagedList<Book>
// Page min value is 1.
ViewData["Books"] = books.GetBooks().ToPagedList(Page ?? 1, 5);
ViewData["Title"] = "Hello Pagenation";
return View("Views/RazorPagination.cshtml");
}
}
}
site.css
@import "../node_modules/bootstrap/dist/css/bootstrap.min.css";
#book_search_result {
height: 70vh;
width: 100%;
}
.book_search_result_row {
border: 1px solid black;
display: flex;
flex-direction: row;
align-items: center;
height: 20%;
width: 100%;
}
RazorPagination.cshtml
@using X.PagedList;
@using X.PagedList.Mvc.Core;
@using X.PagedList.Mvc.Core.Common;
@using BookStoreSample.Controllers;
@using BookStoreSample.Models;
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers;
@{
IPagedList<Book>? bookPages = ViewData["Books"] as IPagedList<Book>;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>@ViewData["Title"]</title>
<link rel="stylesheet" href="/css/site.css">
</head>
<body>
<div id="book_search_result">
@if(bookPages != null && bookPages.Count > 0)
{
@foreach (var item in bookPages)
{
<div class="book_search_result_row">
<div>@item.Id</div>
<div>@item.Name</div>
</div>
}
}
</div>
@Html.PagedListPager(bookPages, page => Url.Action("OpenRazorPage", "Home",
new { Page = page }),
new PagedListRenderOptions {
LiElementClasses = new string[] { "page-item" },
PageClasses = new string[] { "page-link" }
})
</body>
</html>
- Routing to controller actions in ASP.NET Core | Microsoft Docs
- PagedListRenderOptions does not exist for X.PagedList.Mvc.Core 2.2 · Issue #140 · dncuug/X.PagedList · GitHub
How many times are queries executed?
From output log, the answer is 4 times in total.
- getting all "Author" rows
- getting "Book" rows to show result
- getting "Book" count for X.PagedList
- getting "Book" rows for X.PagedList
2.and 4. are totally same.
When their execution times are so slow, I may have to choose another way for pagination.
Use ViewModel
How about cases what using "ViewModel"?
RazorPaginationViewModel.cs
using X.PagedList;
using BookStoreSample.Models;
namespace BookStoreSample.ViewModels
{
public record RazorPaginationViewModel
{
public int? Page { get; init; }
public int? AuthorId { get; init; }
public IPagedList<Book>? Books { get; init; }
}
}
First, I added "Model.AuthorId" into the anonimous class of @Html.PagedListPager.
HomeController.cs
...
namespace BookStoreSample.Controllers
{
public class HomeController: Controller
{
...
[Route("Pages/Razor")]
public IActionResult OpenRazorPage(RazorPaginationViewModel? viewModel)
{
ViewData["Books"] = books.GetBooks(viewModel?.AuthorId).ToPagedList(viewModel?.Page ?? 1, 5);
ViewData["Title"] = "Hello Pagenation";
return View("Views/RazorPagination.cshtml");
}
}
}
RazorPagination.cshtml (Failed)
@using X.PagedList;
@using X.PagedList.Mvc.Core;
@using X.PagedList.Mvc.Core.Common;
@using BookStoreSample.Books;
@using BookStoreSample.Controllers;
@using BookStoreSample.Models;
@using BookStoreSample.ViewModels;
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers;
@inject IBookService books;
@model RazorPaginationViewModel;
@{
IPagedList<Book>? bookPages = ViewData["Books"] as IPagedList<Book>;
List<Author> authors = await books.GetAuthorsAsync();
}
...
<div>
@Html.DropDownListFor(
model => Model!.AuthorId,
authors.Select(a => new SelectListItem{ Value = a.Id.ToString(), Text = a.Name }),
"Author",
new { @class = "form-control" })
</div>
...
<!-- Don't do this -->
@Html.PagedListPager(bookPages, page => Url.Action("OpenRazorPage", "Home",
new { Page = page,
AuthorId = Model?.AuthorId
}),
new PagedListRenderOptions {
LiElementClasses = new string[] { "page-item" },
PageClasses = new string[] { "page-link" }
})
...
Model is null?
But the link of page links were like "http://localhost:5000/Pages/Razor?Page=1".
Because "Model?.AuthorId" was always null.
I think the reason is creating page links before Model instance are set.
So I set the value through "ViewData".
HomeController.cs
...
namespace BookStoreSample.Controllers
{
public class HomeController: Controller
{
...
[Route("Pages/Razor")]
public IActionResult OpenRazorPage(RazorPaginationViewModel? viewModel)
{
ViewData["Books"] = books.GetBooks(viewModel?.AuthorId).ToPagedList(viewModel?.Page ?? 1, 5);
ViewData["AuthorId"] = viewModel?.AuthorId;
ViewData["Title"] = "Hello Pagenation";
return View("Views/RazorPagination.cshtml");
}
}
}
RazorPagination.cshtml (Failed)
...
@Html.PagedListPager(bookPages, page => Url.Action("OpenRazorPage", "Home",
new { Page = page,
AuthorId = ViewData["AuthorId"]
}),
new PagedListRenderOptions {
LiElementClasses = new string[] { "page-item" },
PageClasses = new string[] { "page-link" }
})
...
Top comments (2)
I have been looking for an article like this. I have been trying to use PageList & PagedList.Mvc in a ASP.NET Core application. I guess this will be a better solution for because I didn't know about X.PagedList. Thank you for this article.
I am getting this error
Severity Code Description Project File Line Suppression State
Error CS7069 Reference to type 'HtmlString' claims it is defined in 'System.Web', but it could not be found XXXXXX C:\XXXXXX\XXXXXX\XXXXXX\Views\Report\ListAllReports.cshtml 14 Active
I am on Visual Studio and a .net core 3.1 MVC web app