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