How to create Number Paging in ASP.NET Core using Tag Helpers

How to create Number Paging in ASP.NET Core using Tag Helpers

Tag Helpers allows you to conditionally modify or add HTML elements from server-side code. I will use this Tag Helpers feature to create number based paging links in ASP.NET Core. I will be building this feature from scratch and once completed the paging links will work as shown by the below image:

asp.net core pagination video

In short, I will just add a div (shown below) on my View:

<div class="pagingDiv" page-model="@Model.pagingInfo" page-action="Index" page-classes-enabled="true" page-class="paging" page-class-selected="active"></div>

And I will automatically get a fully functional paging links:

Create Paging Class

Firstly, create a Paging class called PagingInfo.cs and keep it inside the Models folder. This class contains the information – like total Item, Items per page, current page and total pages and this will be used to create the paging links.

This class is given below:

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);
        }
    }
}

Create Tag Helper for Paging

Now coming to the main part which is the creation of a Custom Tag Helper through which the paging links will be created on the View. So create a class inside the Models folder and name it PageLinkTagHelper. Add the following code to it:

[HtmlTargetElement("div", Attributes = "page-model")]
public class PageLinkTagHelper : TagHelper
{
    private IUrlHelperFactory urlHelperFactory;

    public PageLinkTagHelper(IUrlHelperFactory helperFactory)
    {
        urlHelperFactory = helperFactory;
    }

    [ViewContext]
    [HtmlAttributeNotBound]
    public ViewContext ViewContext { get; set; }

    public PagingInfo PageModel { get; set; }

    public string PageAction { get; set; }

    /*Accepts all attributes that are page-other-* like page-other-category="@Model.allTotal" page-other-some="@Model.allTotal"*/
    [HtmlAttributeName(DictionaryAttributePrefix = "page-other-")]
    public Dictionary<string, object> PageOtherValues { get; set; } = new Dictionary<string, object>();

    public bool PageClassesEnabled { get; set; } = false;

    public string PageClass { get; set; }

    public string PageClassSelected { get; set; }

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        IUrlHelper urlHelper = urlHelperFactory.GetUrlHelper(ViewContext);
        TagBuilder result = new TagBuilder("div");
        string anchorInnerHtml = "";

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

            if (anchorInnerHtml == "..")
                tag.Attributes["href"] = "#";
            else if (PageOtherValues.Keys.Count != 0)
                tag.Attributes["href"] = urlHelper.Action(PageAction, AddDictionaryToQueryString(i));
            else
                tag.Attributes["href"] = urlHelper.Action(PageAction, new { id = i });

            if (PageClassesEnabled)
            {
                tag.AddCssClass(PageClass);
                tag.AddCssClass(i == PageModel.CurrentPage ? PageClassSelected : "");
            }
            tag.InnerHtml.Append(anchorInnerHtml);
            if (anchorInnerHtml != "")
                result.InnerHtml.AppendHtml(tag);
        }
        output.Content.AppendHtml(result.InnerHtml);
    }

    public IDictionary<string, object> AddDictionaryToQueryString(int i)
    {
        object routeValues = null;
        var dict = (routeValues != null) ? new RouteValueDictionary(routeValues) : new RouteValueDictionary();
        dict.Add("id", i);
        foreach (string key in PageOtherValues.Keys)
        {
            dict.Add(key, PageOtherValues[key]);
        }

        var expandoObject = new ExpandoObject();
        var expandoDictionary = (IDictionary<string, object>)expandoObject;
        foreach (var keyValuePair in dict)
        {
            expandoDictionary.Add(keyValuePair);
        }

        return expandoDictionary;
    }

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

Notice that in the above code I have created a Custom Tag Helper that will be applied to any div element in the View. I is having an attribute called page-model. This means if I add:

Then my custom Tag Helper will convert it to a number based paging links. Let me now explain you all this in bits by bits.

Explanation

This Custom Tag Helper is using IUrlHelperFactory interface for building paging URLs. I have declared a number of properties through which the tag helpers receives inputs from the view. These are:

  • PageModel: This property will receive an object of PagingInfo class from the view. This object will contain total Item, Items per page, current page and total pages which will be used to create the paging links in the tag helper.
  • PageAction: This property receives the current action method or View name. In my case it is the ‘Index’ view.
  • PageClassesEnabled: This property will be used to apply a CSS to the paging links. It can have either ‘true’ or ‘false’.
  • PageClass: It is used for applying CSS class on the div element that will contain the paging links. In my case it is named as ‘paging’.
  • PageClassSelected: used for applying a specific CSS class to the current page link.

I have also defined a property called ‘ViewContext’ of type ViewContext class and applied 2 attributes to it, these are – [ViewContext] and [HtmlAttributeNotBound].

Check the code below:

[ViewContext]
[HtmlAttributeNotBound]
public ViewContext ViewContext { get; set; }

This property will provide me the rending information of the view where the paging links are placed. So I get access to things like the HttpContext, HttpRequest, HttpResponse and so on. A great way indeed to get access to such information in a tag helper.

Want to learn some of the very hard Model binding concepts then see Advanced Model Binding Concepts in ASP.NET Core

Next see the Process method of the tag helper where I have created the ‘IUrlHelper’ object from this ViewContext property like shown below:

IUrlHelper urlHelper = urlHelperFactory.GetUrlHelper(ViewContext);

I then used it to create paging anchors like shown below:

if (anchorInnerHtml == "..")
    tag.Attributes["href"] = "#";
else if (PageOtherValues.Keys.Count != 0)
    tag.Attributes["href"] = urlHelper.Action(PageAction, AddDictionaryToQueryString(i));
else
    tag.Attributes["href"] = urlHelper.Action(PageAction, new { id = i });
Note: [HtmlAttributeNotBound] attribute says that this property (to which it is applied) isn’t one that you intend to set via a tag helper attribute in the view.

Did you notice the PageOtherValues property? It accepts all attributes that are page-other-* like page-other-category="@Model.allTotal", page-other-some="@Model.allTotal", etc. These all get filled in the dictionary object of type <string, object>.

Check the below code:

[HtmlAttributeName(DictionaryAttributePrefix = "page-other-")]
public Dictionary<string, object> PageOtherValues { get; set; } = new Dictionary<string, object>();

So this means I can send some extra information from the View like sort & searchtext as:

page-other-searchtext="@Model.pagingInfo.SearchText" page-other-sort="@Model.pagingInfo.SortOrder"

And these are appended to the URL like:

https://localhost:44353/Home/Index/1?searchtext=5&sort=40

The function called AddDictionaryToQueryString() will return a dictionary of objects that need to be added to the routes. These are the page numbers and PageOtherValues property values. Check below:

public IDictionary<string, object> AddDictionaryToQueryString(int i)
{
//…
}

So in-short this means you can pass any number of information in the URL’s query string by using this feature.

Register the Tag Helper

Add the below code to the _ViewImports.cshtml file to register the tag helper you just made.

@addTagHelper PagingCore.Models.*, PagingCore

Note: ‘PagingCore’ is my project’s name which you need to change in your case.

Finally notice the AnchorInnerHtml() function which has just one work which is to create the href values of the page link anchors.

Adding the Paging to a View and testing

Let’s now check how it works. So I will create some data that will be held in a ‘Customer’ class. I will then show this data in a view along with paging.

So create 2 classes called ‘Customer’ & ‘CustomerList’ in the Models folder.

public class Customer
{
    [Required]
    public int Id { get; set; }

    [Required]
    public string Name { get; set; }

    [Required]
    public string City { get; set; }
}

public class CustomerList
{
    public IEnumerable<Customer> customer { get; set; }
    public PagingInfo pagingInfo { get; set; }
}

Next, go to your Controller and add the Index action method and a function called CreateDummyData() as shown below:

public IActionResult Index(int? id)
{
    CustomerList customerList = new CustomerList();
    customerList = CreateDummyData(Convert.ToInt32(id));
    return View(customerList);
}

CustomerList CreateDummyData(int page)
{
    int pageSize = 5;
    List<Customer> cL = new List<Customer>
        {
            new Customer {Id=1, Name = "Jacky", City = "Boston"},
            new Customer {Id=2, Name = "Jack", City = "New Jersey"},
            new Customer {Id=3, Name = "Sheena", City = "Ohio"},
            new Customer {Id=4, Name = "Rohit", City = "Albany"},
            new Customer {Id=5, Name = "Mila", City = "NYC"},
            new Customer {Id=6, Name = "Sharon", City = "San Francisco"},
            new Customer {Id=7, Name = "Mark", City = "NY"},
            new Customer {Id=8, Name = "Bill", City = "Boston"},
            new Customer {Id=9, Name = "Maggi", City = "Lucknow"},
            new Customer {Id=10, Name = "Jacobs", City = "Mumbai"},
            new Customer {Id=11, Name = "Amanda", City = "Sao Paulo"},
            new Customer {Id=12, Name = "Mak", City = "Nagpur"},
            new Customer {Id=13, Name = "Rick", City = "New Jersey"},
            new Customer {Id=14, Name = "Chump", City = "Shanghai"},
            new Customer {Id=15, Name = "Sajal", City = "Albany"},
            new Customer {Id=16, Name = "Sharon", City = "San Francisco"},
            new Customer {Id=17, Name = "Mark", City = "NY"},
            new Customer {Id=18, Name = "Bill", City = "Boston"},
            new Customer {Id=19, Name = "Maggi", City = "Lucknow"},
            new Customer {Id=20, Name = "Jacobs", City = "Mumbai"},
            new Customer {Id=21, Name = "Jacky", City = "Boston"},
            new Customer {Id=22, Name = "Jack", City = "New Jersey"},
            new Customer {Id=23, Name = "Sheena", City = "Ohio"},
            new Customer {Id=24, Name = "Rohit", City = "Albany"},
            new Customer {Id=25, Name = "Mila", City = "NYC"},
            new Customer {Id=26, Name = "Sharon", City = "San Francisco"},
            new Customer {Id=27, Name = "Mark", City = "NY"},
            new Customer {Id=28, Name = "Bill", City = "Boston"},
            new Customer {Id=29, Name = "Maggi", City = "Lucknow"},
            new Customer {Id=30, Name = "Jacobs", City = "Mumbai"},
            new Customer {Id=31, Name = "Amanda", City = "Sao Paulo"},
            new Customer {Id=32, Name = "Mak", City = "Nagpur"},
            new Customer {Id=33, Name = "Rick", City = "New Jersey"},
            new Customer {Id=34, Name = "Chump", City = "Shanghai"},
            new Customer {Id=35, Name = "Sajal", City = "Albany"},
            new Customer {Id=36, Name = "Sharon", City = "San Francisco"},
            new Customer {Id=37, Name = "Mark", City = "NY"},
            new Customer {Id=38, Name = "Bill", City = "Boston"},
            new Customer {Id=39, Name = "Maggi", City = "Lucknow"},
            new Customer {Id=40, Name = "Jacobs", City = "Mumbai"}
        };
    PagingInfo pagingInfo = new PagingInfo();
    pagingInfo.CurrentPage = page == 0 ? 1 : page;
    pagingInfo.TotalItems = cL.Count();
    pagingInfo.ItemsPerPage = pageSize;

    var skip = pageSize * (Convert.ToInt32(page) - 1);
    CustomerList customerList = new CustomerList();
    customerList.pagingInfo = pagingInfo;
    customerList.customer = cL.Skip(skip).Take(pageSize).ToList();

    return customerList;
}

The CreateDummyData function creates some dummy customer information and sets the paging info to the ‘PagInfo’ class.

The Index View receives the current page number in the ‘id’ parameter. The concept applied here is the Model Binding feature and then I return the CustomerList object which contains both the PageInfo and Customer’s data.

View

Now all remains is the View where you put your tag helper. The View code is given below:

@model CustomerList
<div id="viewContent">
    <table>
        <thead>
            <tr>
                <th>Name</th>
                <th>Status</th>
                <th>City</th>
            </tr>
        </thead>
        <tbody id="pageTbody">
            @foreach (var p in Model.customer)
            {
                <tr>
                    <td>@p.Id</td>
                    <td>@p.Name</td>
                    <td>@p.City</td>
                </tr>

            }
        </tbody>
        <tfoot>
            <tr>
                <td colspan="3">
                    <div class="pagingDiv" page-model="@Model.pagingInfo" page-action="Index" page-classes-enabled="true" page-class="paging" page-class-selected="active"></div>
                </td>
            </tr>
        </tfoot>
    </table>
</div>

The View accepts model of ‘CustomerList’ type and shows the customers data in a table. See the footer of the table where I have placed my tag helper:

<div class="pagingDiv" page-model="@Model.pagingInfo" page-action="Index" page-classes-enabled="true" page-class="paging" page-class-selected="active"></div>

Run your application and you will see the paging links working excellently.

Download the full source codes:

Download

Conclusion

This pagination feature is very powerful and easy to add to any of yours ASP.NET Core application. I have also created a CSS for the paging links:

.pagingDiv {
    background: #f2f2f2;
}

    .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);
        }

Share this article -

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

Leave a Reply

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