Blazor Number Paging and Sorting

Blazor Number Paging and Sorting

In this ASP.NET Core Blazor 7.0 Tutorial I will create a Number based Paging for displaying records. There will also be the feature to Sort the records in ascending and descending manner. Once this feature is completed it will work as shown by the below video:

Blazor number based Paging

If you have read my previous tutorial on CRUD Operations in Blazor with Entity Framework Core where I displayed Student’s records in a grid manner. This grid was created in HTML table. Now I will update this feature to show the student’s records in this grid in Paging manner. Users will be able to move from one page to another by clicking the paging links.

There are just 2 things we need to create which are:

  • A C# class called PagingInfo.cs which keeps track of paging.
  • A Razor Component called Paging.Razor where this Blazor Paging will be applied.

So, let’s start creating this paging feature in Blazor.

Adding Paging Class

First, in the Models folder of your app create a new class called PagingInfo.cs. This class will keep track of total items, items per page, current page and a property called TotalPages that will calculate the total number of pages. The code for this class is given below:

namespace BlazorForms.Models
{
    public class PagingInfo
    {
        public int TotalItems { get; set; }
        public int ItemsPerPage { get; set; }
        public int CurrentPage { get; set; }
        public int TotalPages
        {
            get
            {
                return (int)Math.Ceiling((decimal)TotalItems /
                    ItemsPerPage);
            }
        }
    }
}

Adding Razor Component for creating Paging

Next, in your Pages folder of your app, add a new Razor Component called Paging.razor that will create a div element and this div will have paging links (anchor tags) that will be generated with the help of PagingInfo class.

The code of Paging.razor is given below:

@using Microsoft.AspNetCore.Mvc.Rendering;

<style>
    .pagingDiv {
        background: #f2f2f2;
        display: flex;
    }

        .pagingDiv > a {
            display: inline-block;
            padding: 0px 9px;
            margin-right: 4px;
            border-radius: 3px;
            border: solid 1px #c0c0c0;
            background: #e9e9e9;
            box-shadow: inset 0px 1px 0px rgba(255,255,255, .8), 0px 1px 3px rgba(0,0,0, .1);
            font-size: .875em;
            font-weight: bold;
            text-decoration: none;
            color: #717171;
            text-shadow: 0px 1px 0px rgba(255,255,255, 1);
        }

            .pagingDiv > a:hover {
                background: #fefefe;
                background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#FEFEFE), to(#f0f0f0));
                background: -moz-linear-gradient(0% 0% 270deg,#FEFEFE, #f0f0f0);
            }

            .pagingDiv > a.active {
                border: none;
                background: #616161;
                box-shadow: inset 0px 0px 8px rgba(0,0,0, .5), 0px 1px 0px rgba(255,255,255, .8);
                color: #f0f0f0;
                text-shadow: 0px 0px 3px rgba(0,0,0, .5);
            }
</style>

<div class="pagingDiv">@((MarkupString)CreatePaging())</div>

@code {
    [Parameter]
    public PagingInfo PI { get; set; }

    [Parameter]
    public bool PageClassesEnabled { get; set; } = false;

    [Parameter]
    public string PageClass { get; set; }

    [Parameter]
    public string PageClassSelected { get; set; }

    public string CreatePaging()
    {
        TagBuilder result = new TagBuilder("div");
        string anchorInnerHtml = "";

        for (int i = 1; i <= PI.TotalPages; i++)
        {
            TagBuilder tag = new TagBuilder("a");
            anchorInnerHtml = AnchorInnerHtml(i, PI);

            if (anchorInnerHtml == "..")
                tag.Attributes["href"] = "#";
            else if (i == 1)
                tag.Attributes["href"] = "/ReadStudentPS";
            else
                tag.Attributes["href"] = "/ReadStudentPS/" + i;

            if (PageClassesEnabled)
            {
                tag.AddCssClass(PageClass);
                tag.AddCssClass(i == PI.CurrentPage ? PageClassSelected : "");
            }

            tag.InnerHtml.Append(anchorInnerHtml);

            if (anchorInnerHtml != "")
                result.InnerHtml.AppendHtml(tag);
        }

        System.IO.StringWriter writer = new System.IO.StringWriter();
        result.InnerHtml.WriteTo(writer, System.Text.Encodings.Web.HtmlEncoder.Default);
        return writer.ToString();
    }

    public static string AnchorInnerHtml(int i, PagingInfo pagingInfo)
    {
        string anchorInnerHtml = "";
        if (pagingInfo.TotalPages <= 10)
            anchorInnerHtml = i.ToString();
        else
        {
            if (pagingInfo.CurrentPage <= 5)
            {
                if ((i <= 8) || (i == pagingInfo.TotalPages))
                    anchorInnerHtml = i.ToString();
                else if (i == pagingInfo.TotalPages - 1)
                    anchorInnerHtml = "..";
            }
            else if ((pagingInfo.CurrentPage > 5) && (pagingInfo.TotalPages - pagingInfo.CurrentPage >= 5))
            {
                if ((i == 1) || (i == pagingInfo.TotalPages) || ((pagingInfo.CurrentPage - i >= -3) && (pagingInfo.CurrentPage - i <= 3)))
                    anchorInnerHtml = i.ToString();
                else if ((i == pagingInfo.CurrentPage - 4) || (i == pagingInfo.CurrentPage + 4))
                    anchorInnerHtml = "..";
            }
            else if (pagingInfo.TotalPages - pagingInfo.CurrentPage < 5)
            {
                if ((i == 1) || (pagingInfo.TotalPages - i <= 7))
                    anchorInnerHtml = i.ToString();
                else if (pagingInfo.TotalPages - i == 8)
                    anchorInnerHtml = "..";
            }
        }
        return anchorInnerHtml;
    }
}

This component contains the CSS for the div element that contains the paging links. The div which displays these paging links is:

<div class="pagingDiv">@((MarkupString)CreatePaging())</div>

Next, you can see it has a number of component parameters which receives the information about pagination from the parent component. These are – an object of PagingInfo class for sending the information about paging links. PageClassesEnabled, PageClass and PageClassSelected for sending the CSS information about the paging links. These will be used in the CreatePaging() method.

[Parameter]
public PagingInfo PI { get; set; }

[Parameter]
public bool PageClassesEnabled { get; set; } = false;

[Parameter]
public string PageClass { get; set; }

[Parameter]
public string PageClassSelected { get; set; }

The CreatePaging() method does the actual generation of this number based paging. It loops through all the pages using for loop and generates anchor tags for these pages one by one.

for (int i = 1; i <= PI.TotalPages; i++)
{
    // generates anchor tags for links
}

Notice the if else block inside the for loop where I have specified the name of the Parent component which is ReadStudentPS in my case. You need to change it if you have a different one.

if (anchorInnerHtml == "..")
    tag.Attributes["href"] = "#";
else if (i == 1)
    tag.Attributes["href"] = "/ReadStudentPS";
else
    tag.Attributes["href"] = "/ReadStudentPS/" + i;

CreatePaging method also calls another method by the name of AnchorInnerHtml () whose work is the create the href attributes value for these anchor tags.

The CreatePaging() method is called by the paging div and the pagination links are shown inside it using the below code.

@((MarkupString)CreatePaging()) 

Keep in mind that the CreatePaging() method is called every time you click the paging link.

Displaying Records in Page-by-Page manner

With the Paging setup already completed let us show Students in Page-by-Page manner. So, create a new Razor Component called ReadStudentPS.razor inside the Pages folder of your app. Add the following code to it:

@page "/ReadStudentPS/{Page:int}"
@page "/ReadStudentPS"

<h1 class="bg-info text-white">Students (Paging & Sorting)</h1>
<table class="table table-sm table-bordered table-striped ">
    <thead>
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Age</th>
            <th>DOB</th>
            <th>Standard</th>
            <th>Sex</th>
            <th>Email</th>
            <th>Terms</th>
            <th>School</th>
            <th>Location</th>
        </tr>
    </thead>
    <tbody>
        @foreach (Student s in Students)
        {
            <tr>
                <td>@s.Id</td>
                <td>@s.Name</td>
                <td>@s.Age</td>
                <td>@s.DOB.ToString("dddd, dd MMMM yyyy")</td>
                <td>@s.Standard</td>
                <td>@s.Sex</td>
                <td>@s.Email</td>
                <td>@s.Terms</td>
                <td>@s.School_R.Name</td>
                <td>@s.Location_R.City, @s.Location_R.State</td>
            </tr>
        }
    </tbody>
    <tfoot>
        <tr>
			<td colspan="10">
                <Paging PI="pagingInfo" PageClassesEnabled="true" PageClass="Paging" PageClassSelected="active"></Paging>
            </td>
        </tr>
    </tfoot>
</table>

@code {
    [Inject]
    public DataContext Context { get; set; }

    public IEnumerable<Student> Students { get; set; } = Enumerable.Empty<Student>();

    [Parameter]
    public int page { get; set; }

    PagingInfo pagingInfo = new PagingInfo();

    protected override void OnParametersSet()
    {
        CreatePagingInfo();
    }

    public void CreatePagingInfo()
    {
        var AllStudents = Context.Student.Include(p => p.School_R).Include(p => p.Location_R);

        int PageSize = 3;
        pagingInfo = new PagingInfo();
        page = page == 0 ? 1 : page;
        pagingInfo.CurrentPage = page;
        pagingInfo.TotalItems = AllStudents.Count();
        pagingInfo.ItemsPerPage = PageSize;

        var skip = PageSize * (Convert.ToInt32(page) - 1);
        Students = AllStudents.Skip(skip).Take(PageSize).ToList();
    }
}

Explanation : This component gets the current Page number in the “page” variable, and it is sent in route.

[Parameter]
public int page { get; set; }

The route is defined at the top:

@page "/ReadStudentPS/{Page:int}"

Next on lifecycle event OnParametersSet, I call the CreatePagingInfo() method whose task is to create the PagingInfo class object. The code is shown below.

public void CreatePagingInfo()
{
    var AllStudents = Context.Student.Include(p => p.School_R).Include(p => p.Location_R);

    int PageSize = 3;
    pagingInfo = new PagingInfo();
    page = page == 0 ? 1 : page;
    pagingInfo.CurrentPage = page;
    pagingInfo.TotalItems = AllStudents.Count();
    pagingInfo.ItemsPerPage = PageSize;

    var skip = PageSize * (Convert.ToInt32(page) - 1);
    Students = AllStudents.Skip(skip).Take(PageSize).ToList();
}

Notice I have set page size as 3. The total student records are found by AllStudents.Count() method of LINQ and is set to the TotalItems property. The most important thing is to find out the student records of the current page by skipping the unwanted records from the whole lot. This is done by the below code:

var skip = PageSize * (Convert.ToInt32(page) - 1);
Students = AllStudents.Skip(skip).Take(PageSize).ToList();

These records are shown in the html table while the PagingInfo object is provided to the Paging.razor component for the generation of paging links.

<Paging PI="pagingInfo" PageClassesEnabled="true" PageClass="Paging" PageClassSelected="active"></Paging>

Now run your app and check the paging links which I have shown in the below video:

Blazor Number Paging video

If the paging becomes big then middle links are skipped and “…” signs are shown instead. Check the below image.

You should also note that in my previous tutorial on Blazor forms and validation, I did the project configuration for Entity Framework Core. If you want to learn it then make sure you read that tutorial also.

blazor pagination

Implementing Sorting in Blazor

Let us now implement the Blazor Sorting feature in the same razor component. With this feature you can sort the records by Name, Age and DOB, in both ascending and descending manner.

So, make the following changes which are highlighted as shown in the below code:

@page "/ReadStudentPS/{Page:int}"
@page "/ReadStudentPS"

<style>
    .curPointer:hover {
        cursor: pointer;
    }
</style>

<h1 class="bg-info text-white">Students (Paging & Sorting)</h1>
<table class="table table-sm table-bordered table-striped ">
    <thead>
        <tr>
            <th>ID</th>
            <th class="curPointer" @onclick="@(() => Sort("Name"))">Name@((MarkupString)NameArrow)</th>
            <th class="curPointer" @onclick="@(() => Sort("Age"))">Age@((MarkupString)AgeArrow)</th>
            <th class="curPointer" @onclick="@(() => Sort("DOB"))">DOB@((MarkupString)DOBArrow)</th>
            <th>Standard</th>
            <th>Sex</th>
            <th>Email</th>
            <th>Terms</th>
            <th>School</th>
            <th>Location</th>
        </tr>
    </thead>
    <tbody>
        @foreach (Student s in Students)
        {
            <tr>
                <td>@s.Id</td>
                <td>@s.Name</td>
                <td>@s.Age</td>
                <td>@s.DOB.ToString("dddd, dd MMMM yyyy")</td>
                <td>@s.Standard</td>
                <td>@s.Sex</td>
                <td>@s.Email</td>
                <td>@s.Terms</td>
                <td>@s.School_R.Name</td>
                <td>@s.Location_R.City, @s.Location_R.State</td>
            </tr>
        }
    </tbody>
    <tfoot>
        <tr>
            <td colspan="10">
                <Paging PI="pagingInfo" PageClassesEnabled="true" PageClass="Paging" PageClassSelected="active"></Paging>
            </td>
        </tr> 
    </tfoot>
</table>

@code {
    [Inject]
    public DataContext Context { get; set; }

    public IEnumerable<Student> Students { get; set; } = Enumerable.Empty<Student>();

    [Parameter]
    public int page { get; set; }

    PagingInfo pagingInfo = new PagingInfo();

    protected override void OnParametersSet()
    {
        //CreatePagingInfo();
        GetRecords(sortColumn, sortValue);
    }

    /*public void CreatePagingInfo()
    {
        var AllStudents = Context.Student.Include(p => p.School_R).Include(p => p.Location_R);

        int PageSize = 1;
        pagingInfo = new PagingInfo();
        page = page == 0 ? 1 : page;
        pagingInfo.CurrentPage = page;
        pagingInfo.TotalItems = AllStudents.Count();
        pagingInfo.ItemsPerPage = PageSize;

        var skip = PageSize * (Convert.ToInt32(page) - 1);
        Students = AllStudents.Skip(skip).Take(PageSize).ToList();
    }*/
    
    public string sortColumn = "", sortValue = "asc", NameArrow, AgeArrow, DOBArrow;

    void GetRecords(string sortColumn, string sortValue)
    {
        int pageSize = 3;

        pagingInfo = new PagingInfo();
        page = page == 0 ? 1 : page;
        pagingInfo.CurrentPage = page == 0 ? 1 : page;
        pagingInfo.TotalItems = Context.Student.Count();
        pagingInfo.ItemsPerPage = pageSize;

        var skip = pageSize * (Convert.ToInt32(page) - 1);

        var query = Context.Student.Include(p => p.School_R).Include(p => p.Location_R);

        if (sortColumn == "")
            Students = query.Skip(skip).Take(pageSize).ToList();
        else
        {
            if (sortValue == "asc")
            {
                switch (sortColumn)
                {
                    case "Id":
                        Students = query.OrderBy(s => s.Id).Skip(skip).Take(pageSize).ToList();
                        break;
                    case "Name":
                        Students = query.OrderBy(s => s.Name).Skip(skip).Take(pageSize).ToList();
                        break;
                    case "Age":
                        Students = query.OrderBy(s => s.Age).Skip(skip).Take(pageSize).ToList();
                        break;
                    default:
                        Students = query.OrderBy(s => s.Name).Skip(skip).Take(pageSize).ToList();
                        break;
                }
            }
            else
            {
                switch (sortColumn)
                {
                    case "Id":
                        Students = query.OrderByDescending(s => s.Id).Skip(skip).Take(pageSize).ToList();
                        break;
                    case "Name":
                        Students = query.OrderByDescending(s => s.Name).Skip(skip).Take(pageSize).ToList();
                        break;
                    case "Age":
                        Students = query.OrderByDescending(s => s.Age).Skip(skip).Take(pageSize).ToList();
                        break;
                    default:
                        Students = query.OrderByDescending(s => s.Name).Skip(skip).Take(pageSize).ToList();
                        break;
                }
            }
        }
    }

    public void Sort(string column)
    {
        string upArrow = "<img src=\"/Images/up.png\" />";
        string downArrow = "<img src=\"/Images/down.png\" />";

        if (column == sortColumn)
            sortValue = sortValue == "asc" ? "desc" : "asc";
        else
        {
            sortColumn = column;
            sortValue = "asc";
        }

        NameArrow = AgeArrow = DOBArrow = "";
        if (sortColumn == "Name" && sortValue == "asc")
            NameArrow = upArrow;
        else if (sortColumn == "Name" && sortValue == "desc")
            NameArrow = downArrow;

        if (sortColumn == "Age" && sortValue == "asc")
            AgeArrow = upArrow;
        else if (sortColumn == "Age" && sortValue == "desc")
            AgeArrow = downArrow;

        if (sortColumn == "DOB" && sortValue == "asc")
            DOBArrow = upArrow;
        else if (sortColumn == "DOB" && sortValue == "desc")
            DOBArrow = downArrow;

        page = 1;
        GetRecords(sortColumn, sortValue);
    }
}

Explanation : I have changed the 3 “th” elements for Name, Age and DOB fields.

<th class="curPointer" @onclick="@(() => Sort("Name"))">Name@((MarkupString)NameArrow)</th>
<th class="curPointer" @onclick="@(() => Sort("Age"))">Age@((MarkupString)AgeArrow)</th>
<th class="curPointer" @onclick="@(() => Sort("DOB"))">DOB@((MarkupString)DOBArrow)</th>

The changes include:

1. Adding CSS class curPointer which will show pointer icon when mouse is hovered on these 3 “th” elements. This CSS is added inside the style element.

<style>
    .curPointer:hover {
        cursor: pointer;
    }
</style>

2. Adding onclick events on these 3 “th” elements which calls a C# method Sort. The name of the column is passed to this method which will help to identify which “th” element is clicked.

3. A MarkupString code is added after the name of the column. This will contain an img tag that will show either up or down arrow, this will be specifying the sorting type (i.e. ascending sort or descending sort). The use of MarkupString is that it will render the img tag as an HTML. The up & down arrow images are kept inside the wwwroot/Images folder of the app. Download the project from the link given at the bottom and get these images along with the project.

Next, I have commented the CreatePagingInfo method which is no longer in use and called a new method GetRecords() inside the OnParametersSet method.

protected override void OnParametersSet()
{	
    //CreatePagingInfo(); 
    GetRecords(sortColumn, sortValue);
}

The GetRecords() method implements the sorting feature. It takes 2 parameters which are :

  1. sortcolumn – contains the column name that needs to be sorted. It can have any of these 3 values – Name, Age, DOB.
  2. sortValue – can contain either “asc” or “desc”. These values specify which type of sort to apply out of ascending or descending.

This method applies the sorting based on the values contained by the sortColumn or sortValue. The code which does this work is the if else block and the switch statement as shown below.

if (sortColumn == "")
    Students = query.Skip(skip).Take(pageSize).ToList();
else
{
    if (sortValue == "asc")
    {
        switch (sortColumn)
        {
            case "Id":
                Students = query.OrderBy(s => s.Id).Skip(skip).Take(pageSize).ToList();
                break;
            case "Name":
                Students = query.OrderBy(s => s.Name).Skip(skip).Take(pageSize).ToList();
                break;
            case "Age":
                Students = query.OrderBy(s => s.Age).Skip(skip).Take(pageSize).ToList();
                break;
            default:
                Students = query.OrderBy(s => s.Name).Skip(skip).Take(pageSize).ToList();
                break;
        }
    }
    else
    {
        switch (sortColumn)
        {
            case "Id":
                Students = query.OrderByDescending(s => s.Id).Skip(skip).Take(pageSize).ToList();
                break;
            case "Name":
                Students = query.OrderByDescending(s => s.Name).Skip(skip).Take(pageSize).ToList();
                break;
            case "Age":
                Students = query.OrderByDescending(s => s.Age).Skip(skip).Take(pageSize).ToList();
                break;
            default:
                Students = query.OrderByDescending(s => s.Name).Skip(skip).Take(pageSize).ToList();
                break;
        }
    }
}

After that there is a Sort() method which is called when any of the 3 ‘th’ element of the table is clicked. The work of this method is to set the values of sortColumn and sortValue variables.

public void Sort(string column)
{
    string upArrow = "<img src=\"/Images/up.png\" />";
    string downArrow = "<img src=\"/Images/down.png\" />";

    if (column == sortColumn)
        sortValue = sortValue == "asc" ? "desc" : "asc";
    else
    {
        sortColumn = column;
        sortValue = "asc";
    }

    NameArrow = AgeArrow = DOBArrow = "";
    if (sortColumn == "Name" && sortValue == "asc")
        NameArrow = upArrow;
    else if (sortColumn == "Name" && sortValue == "desc")
        NameArrow = downArrow;

    if (sortColumn == "Age" && sortValue == "asc")
        AgeArrow = upArrow;
    else if (sortColumn == "Age" && sortValue == "desc")
        AgeArrow = downArrow;

    if (sortColumn == "DOB" && sortValue == "asc")
        DOBArrow = upArrow;
    else if (sortColumn == "DOB" && sortValue == "desc")
        DOBArrow = downArrow;

    page = 1;
    GetRecords(sortColumn, sortValue);
}

It also applies the arrow type (i.e. up arrow or down arrow) to the 3 variables which are:

  • NameArrow
  • AgeArrow
  • DOBArrow

This is done so that the Up or Down arrow is shown next to the name of the th element. Now it’s time to run and check the functionality. I have shown it on the below video.

You can download the source codes:

Download

Conclusion

I hope you enjoyed creating the Blazor Paging and Sorting feature in from scratch. Download the codes and use it anywhere you like.

SHARE THIS ARTICLE

  • linkedin
  • reddit
yogihosting

ABOUT THE AUTHOR

I hope you enjoyed reading this tutorial. If it helped you then consider buying a cup of coffee for me. This will help me in writing more such good tutorials for the readers. Thank you. Buy Me A Coffee donate

Leave a Reply

Your email address will not be published. Required fields are marked *