[ASP.NET Core] Using X.PagedList

Masui Masanori - Mar 20 '21 - - Dev Community

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"
    }
}
Enter fullscreen mode Exit fullscreen mode

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();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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%;
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

How many times are queries executed?

From output log, the answer is 4 times in total.

  1. getting all "Author" rows
  2. getting "Book" rows to show result
  3. getting "Book" count for X.PagedList
  4. 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; } 
    }
}
Enter fullscreen mode Exit fullscreen mode

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");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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" }
    })
...
Enter fullscreen mode Exit fullscreen mode

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");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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" }
    })
...
Enter fullscreen mode Exit fullscreen mode
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player