Tag Helpers allow us to enhance HTML elements from server-side code. We will use this feature to create number based paging links in ASP.NET Core. We will be building this feature from scratch and once completed the paging links will work as shown by the below image:
In short, we will add a div, that is shown below, on a View:
<div class="pagingDiv" page-model="@Model.pagingInfo" page-action="Index" page-classes-enabled="true" page-class="paging" page-class-selected="active"></div>
And it will automatically be enhanced to a fully functional paging links.
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);
}
}
}
Now coming to the main part which is to create Custom Tag Helper for paging. So create a class inside the Models folder and name it PageLinkTagHelper.cs. 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;
}
}
We will also need to import the following namespaces in this class.
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using System.Dynamic;
In the above code we have created a Custom Tag Helper that will be applied to any div element given on a View file. It is having an attribute called page-model. This means if we add:
<div class="pagingDiv" page-model="@Model.pagingInfo"></div>
Then the custom Tag Helper will convert it to a number based paging links.
This Custom Tag Helper is using IUrlHelperFactory interface for building paging URLs. We have declared a number of properties through which the tag helpers receives values from the attributes of the div on the view. These are:
We 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 us the rendering information of the view where the paging links are placed. So we 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.
Next see the Process method of the tag helper where we 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 });
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 we 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 we can pass any number of information in the URL’s query string by using this feature.
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 if you have a different name for the project.
Finally notice the AnchorInnerHtml() function which has just one work which is to create the href values of the page link anchors.
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. In our project we are adding the code to the HomeController.
using Microsoft.AspNetCore.Mvc;
using PagingCore.Models;
namespace PagingCore.Controllers
{
public class HomeController : Controller
{
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 we return the CustomerList object which contains both the PageInfo and Customer’s data.
Now all remains is the View file where we put the div. The View code is given below. We are using the Index view of Home Controller.
@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 we have placed the div to be transformed by custom 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 how these paging links work.
Download the full source codes:
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);
}
Dear sir
Hi
I Read and open your code but some when i use it for my project , taghelpers for pagination does not apear .
I read some times and check several times but there are a problem that never seen pagination links.
please help me .
Nolehdan
Make sure you have have Register the Tag Helper in the _ViewImports.cshtml file by adding the code:
@addTagHelper PagingCore.Models.*, PagingCore
Moreover please try downloading the source code of this tutorial and check if you have missed on something.