October 15 2018

ASP.NET Core Razor Pages - Pagination Example

Tutorial built with ASP.NET Core 2.1 Razor Pages

This is a quick tutorial to show how you can add pagination to your ASP.NET Core Razor Pages application.

The example app displays a paged list of dummy items and allows you to customise the total number of items being paged, the number of items per page, and the maximum number of pager links displayed. The pagination logic comes from the JW.Pager package which is available on NuGet, for more info about the pagination logic check out C# - Pure Pagination Logic in C# / ASP.NET.

The razor pages example project is available on GitHub at https://github.com/cornflourblue/aspnet-core-razor-pages-pagination

Tools required to run the ASP.NET Core Razor Pages Example Locally

To develop and run ASP.NET Core Razor Pages applications locally, download and install the following:

  • .NET Core SDK - includes the .NET Core runtime and command line tools
  • Visual Studio Code - code editor that runs on Windows, Mac and Linux
  • C# extension for Visual Studio Code - adds support to VS Code for developing .NET Core applications

Running the Razor Pages Pagination Example Locally

  1. Download or clone the tutorial project code from https://github.com/cornflourblue/aspnet-core-razor-pages-pagination
  2. Start the app by running dotnet run from the command line in the project root folder (where the RazorPagesPagination.csproj file is located), you should see the message Now listening on: http://localhost:5000
  3. Open your browser and navigate to http://localhost:5000

NOTE: You can also start the razor pages application in debug mode in VS Code by opening the project root folder in VS Code and pressing F5 or by selecting Debug -> Start Debugging from the top menu. Running in debug mode allows you to attach breakpoints to pause execution and step through the application code.


Razor Pages Pagination Project Structure

The example project doesn't contain much code on top of the basic ASP.NET Core Razor Pages structure, it has just one page + page model (Index.cshtml + Index.cshtml.cs) that contains all of the pagination stuff, but for completeness I've included descriptions and code for all the main project files below.

 

Razor Pages Layout

Path: /Pages/Shared/_Layout.cshtml

The layout contains the outer html for the example app that is used by all pages / views. The contents of each razor page is rendered where @RenderBody() is called.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>ASP.NET Core Razor Pages - Pagination Example</title>
    <link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
    <div class="container text-center">
        <div class="col">
            <h1>ASP.NET Core Razor Pages - Pagination Example</h1>
            @RenderBody()
        </div>
    </div>
</body>
</html>
 

Razor Pages View Imports

Path: /Pages/_ViewImports.cshtml

The view imports file is used to make razor directives available globally to all razor pages. In this case it makes all tag helpers from the Microsoft.AspNetCore.Mvc.TagHelpers assembly available to all pages by calling the razor directive @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers.

The example app uses the form tag helpers asp-for and asp-items to bind select lists in the index page (Index.cshtml) to properties in the index page model (Index.cshtml.cs).

For more info on asp.net core tag helpers see the official docs here, and documentation for the select tag helper used in the example is here.

@using RazorPagesPagination
@namespace RazorPagesPagination.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
 

Razor Pages View Start

Path: /Pages/_ViewStart.cshtml

The view start file contains code that is run before every page is loaded in an asp.net core razor pages application. In this case it sets the layout of all pages to _Layout.cshtml.

@{
    Layout = "_Layout";
}
 

Razor Pages Index Page

Path: /Pages/Index.cshtml

The index page contains the html for displaying the paged list of items and pagination controls. It contains 3 sections:

Pager parameter controls - this section contains controls that let you customise the total number of items being paged in the example, the number of items per page, and the maximum pager links displayed. These controls are just for the example so you can tinker with the customisation options available, they typically wouldn't be included in a production application.

Items being paged - this section renders the html for the current page of items by looping over the Model.Items list property of the index page model.

Pager - this section contains the pagination controls used for navigating between different pages, it uses data from the Model.Pager object property to render the pagination controls.

@page
@model RazorPagesPagination.Pages.IndexModel

<!-- pager parameter controls -->
<form method="post" class="container border text-left pt-2 mb-3">
    <div class="form-row form-group">
        <div class="col">
            <label asp-for="TotalItems">Total number of items</label>
            <select asp-for="TotalItems" asp-items="Model.TotalItemsList" class="form-control form-control-sm" onchange="this.form.submit()"></select>
        </div>
        <div class="col">
            <label asp-for="PageSize">Items per page</label>
            <select asp-for="PageSize" asp-items="Model.PageSizeList" class="form-control form-control-sm" onchange="this.form.submit()"></select>
        </div>
        <div class="col">
            <label asp-for="MaxPages">Max page links displayed</label>
            <select asp-for="MaxPages" asp-items="Model.MaxPagesList" class="form-control form-control-sm" onchange="this.form.submit()"></select>
        </div>
    </div>
</form>

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

Razor Pages Index Page Model

Path: /Pages/Index.cshtml.cs

The index page model generates a sample list of items to be paged (dummyItems) using the values from the pager parameter controls. It then uses the Pager class from the JW.Pager package to get all the pagination data for the current page of dummyItems, and then uses the pagination data to extract the current page of items from the dummyItems list and assign it to the Items list property.

The OnPost method is run when the pager parameters are updated and the form posted from the index page. Pager parameters are stored in the current user session so they persist between page requests.

using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using JW;

namespace RazorPagesPagination.Pages
{
    public class IndexModel : PageModel
    {
        public IEnumerable<string> Items { get; set; }
        public Pager Pager { get; set; }
        public SelectList TotalItemsList { get; set; }
        public int TotalItems { get; set; }
        public SelectList PageSizeList { get; set; }
        public int PageSize { get; set; }
        public SelectList MaxPagesList { get; set; }
        public int MaxPages { get; set; }

        public void OnGet(int p = 1)
        {
            // properties for pager parameter controls
            TotalItemsList = new SelectList(new []{ 10, 150, 500, 1000, 5000, 10000, 50000, 100000, 1000000 });
            TotalItems = HttpContext.Session.GetInt32("TotalItems") ?? 150;
            PageSizeList = new SelectList(new []{ 1, 5, 10, 20, 50, 100, 200, 500, 1000 });
            PageSize = HttpContext.Session.GetInt32("PageSize") ?? 10;
            MaxPagesList = new SelectList(new []{ 1, 5, 10, 20, 50, 100, 200, 500 });
            MaxPages = HttpContext.Session.GetInt32("MaxPages") ?? 10;

            // generate list of sample items to be paged
            var dummyItems = Enumerable.Range(1, TotalItems).Select(x => "Item " + x);

            // get pagination info for the current page
            Pager = new Pager(dummyItems.Count(), p, PageSize, MaxPages);

            // assign the current page of items to the Items property
            Items = dummyItems.Skip((Pager.CurrentPage - 1) * Pager.PageSize).Take(Pager.PageSize);
        }

        public IActionResult OnPost(int totalItems, int pageSize, int maxPages)
        {
            // update pager parameters for session and redirect back to 'OnGet'
            HttpContext.Session.SetInt32("TotalItems", totalItems);
            HttpContext.Session.SetInt32("PageSize", pageSize);
            HttpContext.Session.SetInt32("MaxPages", maxPages);
            return Redirect("/");
        }
    }
}
 

ASP.NET Core App Settings (Development)

Path: /appsettings.Development.json

Configuration file with application settings that are specific to the development environment.

{
  "Logging": {
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}
 

ASP.NET Core App Settings

Path: /appsettings.json

Root configuration file containing application settings for all environments.

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}
 

ASP.NET Core Program

Path: /Program.cs

The program class is a console app that is the main entry point to start the application, it configures and launches the asp.net core razor pages host and web server using an instance of WebHostBuilder. ASP.NET Core Razor Pages applications require a host in which to execute.

Kestrel is the web server used in the example, it's a new cross-platform web server for ASP.NET Core that's included in new project templates by default. Kestrel is fine to use on it's own for internal applications and development, but for public facing websites and applications it should sit behind a more mature reverse proxy web server (e.g. IIS, Apache, Nginx etc) that will receive HTTP requests from the internet and forward them to Kestrel after initial handling and security checks.

using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;

namespace RazorPagesPagination
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateWebHostBuilder(args).Build().Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();
    }
}
 

Razor Pages Project File

Path: /RazorPagesPagination.csproj

The csproj (C# project) is an MSBuild based file that contains target framework and NuGet package dependency information for the application.

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="JW.Pager" Version="1.0.0" />
    <PackageReference Include="Microsoft.AspNetCore.App" />
  </ItemGroup>

</Project>
 

ASP.NET Core Startup

Path: /Startup.cs

The startup class configures the request pipeline of the application and how all requests are handled.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace RazorPagesPagination
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSession();
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseSession();
            app.UseMvc();
        }
    }
}

 

Web Development Sydney

Feel free to contact me if you're looking for a web developer in Sydney, I also provide remote contracting services for clients outside Sydney.

 


Sponsored by