Whenever we deal with data we need some mechanism for paging it. It makes it easier for users to navigate through data and same time we don’t put too much load to our systems. This blog post shows you how to build pager for your MVC site using ViewComponent and PagedResult class.
PagedResult revisited
Back in times I wrote blog post Returning paged results from repositories using PagedResult where I show how to build generic paged results class that you can put to infrastructur layer or some widely used library in your system. As this class doesn’t have any dependencies coming in form another parts of your system you can use this class with practically all kinds of data.
Over times I have made some modifications to this class and refactored it but in big part it’s still the same.
public abstract class PagedResultBase
{
public int CurrentPage { get; set; }
public int PageCount { get; set; }
public int PageSize { get; set; }
public int RowCount { get; set; }
public int FirstRowOnPage
{
get { return (CurrentPage - 1) * PageSize + 1; }
}
public int LastRowOnPage
{
get { return Math.Min(CurrentPage * PageSize, RowCount); }
}
}
public class PagedResult: PagedResultBase where T : class
{
public IListResults { get; set; }
public PagedResult()
{
Results = new List();
}
}
I separated paging and data container to separate classes that are bound with each other through inheritance. The concept of PagedResult that is fully used by calling code is good because you can take this class to all flavours of .NET.
NB! To see some real code that uses PagedResult class please refer to my blog post Returning paged results from repositories using PagedResult. Although this post uses single class version of PagedResult it’s still valid in the context of this post.
Building pager view component
Now let’s build ASP.NET MVC view component to display paged results. View components are like replacement for child actions. They have their own classes and views and they support also dependency injection. Usually I put code part of view components to Extensions folder of my web application or web utilities project.
Here is the code part of pager view component.
public class PagerViewComponent : ViewComponent
{
public Task<IViewComponentResult> InvokeAsync(PagedResultBase result)
{
return Task.FromResult((IViewComponentResult)View("Default", result));
}
}
You can add Invoke() method instead of asynchronous one but remember one thing: you can’t have both methods for component at same time. As I’m writing this code currently on ASP.NET Core then I prefer asynchronous code.
Now let’s add ViewComponent view to display data. Views of view components should be placed to folder Views/Shared/Components/. For pager I have folder Views/Shared/Components/Pager. Let’s add view called Default.cshtml to this folder.
@model PagedResultBase
@{
var urlTemplate = Url.Action() + "?page={0}";
var request = ViewContext.HttpContext.Request;
foreach (var key in request.Query.Keys)
{
if (key == "page")
{
continue;
}
urlTemplate += "&" + key + "=" + request.Query[key];
}
var startIndex = Math.Max(Model.CurrentPage - 5, 1);
var finishIndex = Math.Min(Model.CurrentPage + 5, Model.PageCount);
}
<div class="row">
<div class="col-md-4 col-sm-4 items-info">
Items @Model.FirstRowOnPage to @Model.LastRowOnPage of @Model.RowCount total
</div>
<div class="col-md-8 col-sm-8">
@if (Model.PageCount > 1)
{
<ul class="pagination pull-right">
<li><a href="@urlTemplate.Replace("{0}", "1")">«</a></li>
@for (var i = startIndex; i <= finishIndex; i++)
{
@if (i == Model.CurrentPage)
{
<li><span>@i</span></li>
}
else
{
<li><a href="@urlTemplate.Replace("{0}", i.ToString())">@i</a></li>
}
}
<li><a href="@urlTemplate.Replace("{0}", Model.PageCount.ToString())">»</a></li>
</ul>
}
</div>
</div>
On my sample web project the pager looks like shown on the following screenshot.
It’s simple and looks nice and we didn’t wrote too much code to achieve output like this.
How it works?
Now you may ask how can be PagedResultBase enough for displaying pager with links? And you have right to be suspicious about the solution
Actually I use here one simple trick. All my paging scenarios follow the same simple pattern – paging is done always by same controller action. I mean search results are shown by same controller action no matter what is the results page index. Same goes for product lists, manufacturers lists etc.
As action for given pages data is always the same I can create URL template for pager. All I have to do is to omit parameter called “page” as this is the one that gets new value for every page link.
Calling pager from views
Suppose you have view where you display products by pages. As a model you use PagedResult. To display pager you add the following line to view.
@(await Component.InvokeAsync<PagerViewComponent>(Model))
This line displays pager using pager view component. Example of pager is on the bottom left corner of my beer store demo application screenshot.
Wrapping up
PagedResult is pretty good construct for supporting paging. It has no dependencies to other layers and therefore it is easy to use on all different .NET frameworks. Using view components it was easy to build generic pager component that can be used in all views that need paging. As pager mark-up is held in regular view file it is easy to change the output if needed.