Add Simple Ajax Paging to Grids in ASP.NET MVC

Introduction

In standard ASP.NET it is very easy to implement a full featured data grid with paging. With the help of an UpdatePanel you can improve the user experience of paging data in grids by taking advantage of Ajax (Asynchromous JavaScript And XML). With the UpdatePanel you are isolating a small portion of the page to refresh, in this case only the Grid. In ASP.NET MVC (Model-View-Controller) the simpliest method of rendering only a portion of a page is through a partial view. Partial views can be extremly handy for rendering an area of a page similar to an Update Panel; however, out of the box they do not provide the JavaScript necessary to carry out the update themselves. Thus we need to take advantage of the MVC Ajax JavaScript class. In addtion to the ability to update the page we also need to render out the control the user will use to change pages displayed within the grid.

Before we dig into the partial view to house the grid we need to take a look at the paging mechanism we will be using. Ideally we would want to create a tool which is reusable across multiple grids within a site. One method for doing this is by creating a specific extension helper method to create a generic Pager. Since we are going to utilze Ajax for this we will want to create an Ajax Helper Method. To build the pager code itself we are going to need a couple inputs: current page, page size and total records. In addition, we will need to know the action to call and the target DIV tag which is used by the Ajax method to determine where to put the content returned from the action. From there we can create a simple extension method to generate a pager using the code below.

  public static MvcHtmlString Pager(this AjaxHelper helper, int CurrentPage, int TotalRecords, string TargetDiv, int PageSize = 10, string ActionName="Index")
  {
     if (TotalRecords > 0)
     {
        StringBuilder sb = new StringBuilder();
        int TotalPages = (int)Math.Ceiling(TotalRecords / (double)PageSize);
  
        //Build the Ajax Options
        AjaxOptions ao = new AjaxOptions();
        ao.UpdateTargetId = TargetDiv;
  
        if (CurrentPage > 0)
        {
           //Add the Back Links
           sb.Append(System.Web.Mvc.Ajax.AjaxExtensions.ActionLink(helper, "<<", ActionName, new { Page = 0 }, ao));
           sb.Append("  ");
           sb.Append(System.Web.Mvc.Ajax.AjaxExtensions.ActionLink(helper, "<", ActionName, new { Page = CurrentPage - 1 }, ao));
           sb.Append("  ");
        }
  
        //Add the Page Number
        sb.Append("Page " + (CurrentPage + 1).ToString() + " of " + (TotalPages).ToString());
  
        if (CurrentPage < (TotalPages - 1))
        {
           //Add the Next Links
           sb.Append("  ");
           sb.Append(System.Web.Mvc.Ajax.AjaxExtensions.ActionLink(helper, ">", ActionName, new { Page = CurrentPage + 1 }, ao));
           sb.Append("  ");
           sb.Append(System.Web.Mvc.Ajax.AjaxExtensions.ActionLink(helper, ">>", ActionName, new { Page = TotalPages }, ao));
        }
  
        return MvcHtmlString.Create(sb.ToString());
     }
     else
     {
        //Don't return anything for the pager if we do not have any records
        return MvcHtmlString.Create("");
     }
  } 

The above code, while a bit lengthly, performs a very simple task to generate a simple pager that looks like the following example: <<  <  Page # of ##  >  >>.  The method first determines if we actually have any records in the grid, if not do not generate it. Then we calculate the total number of pages for the given page size and the number of records. From there it populates a string builder with the necessary Ajax ActionLinks going to the desired Action with the specified Page number and returns the MvcHtmlString containing the necessary markup.

At this point, it is important to note that the Ajax ActionLinks drive what Action to call and where to put the resulting content based upon the parameters given. The AjaxOptions object specifies the Update Target Id as well as other options we are not using such as JavaScript methods to call when an update occurs. To start using the pager we need an action which accepts page as a parameter and returns a partial view. Next we can take a look at the controller action which can be called by the pager extension method.

public ActionResult Index(int Page = 0)
{
   int PageSize = 10;

   Models.Contacts c = new Models.Contacts();
   List cList = c.GetAll();

   ViewData["TotalRecords"] = cList.Count();
   ViewData["Contacts"] = cList.Skip(Page * PageSize).Take(PageSize).ToList();
   ViewData["CurrentPage"] = Page;

   return PartialView("Index");
}

As you can see this action accepts the page parameter with a default value of 0. The default value can come in handy later, allowing you to render the partial view without the need for a page number. From the Contacts data source the method grabs the total number of records, the current page number and returns a list of records as contacts. The list of records has been filtered to provide a single page of data using the Skip and Take LINQ (Language Integrated Query) methods. In this example we are using a simple generic list; however, the paging should be extended down to the model to return only those records needed and limit the amount of data read from the database.

Next we can jump into the Partial View which serves to connect the data from the Index Action as well as utilizing the pager extension method above. Shown below is the index partial view used in the index action.

  <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<dynamic>" %>
  <table>
   
     <tr>
        <th>FirstName</th>
        <th> LastName</th>
     </tr>
  
     <% foreach (var item in Ajax.ViewData["Contacts"] as List<MVCGridPagingExample.Models.ContactItem> ) { %>
     <tr>
        <td><%: item.FirstName %></td>
        <td><%: item.LastName %></td>
     </tr>
     <% } %>
  
  </table>
  <%: Ajax.Pager((int)Ajax.ViewData["CurrentPage"], (int)Ajax.ViewData["TotalRecords"], "ContactsDiv")%> 

This partial view, as you would guess generates a table/grid with the list of contacts; however, there is one disction that I should point out. Within the foreach loop we are using Ajax.ViewData instead of Html.ViewData. This allows us to use the ViewData with Ajax calls in addition to standard GET requests. The last line of the Partial View is where we insert the call to the pager extension method we created above. In the call we are passing in the items needed which we are receiving in the ViewData from the action method. You may have already noticed that the ContactsDiv specified in the last parameter doesn't exist anywhere in the partial view. The ContactsDiv wraps around where you want this partial view to be displayed as shown below.

  <div id="ContactsDiv">
     <% Html.RenderAction("Index", "Contacts"); %>
  </div> 

The Html.RenderAction is primarily used to start the process and initially call the index action created above. This will produce the partial view containing the grid as well as the pager. Then as the user clicks to change the page using the pager the Ajax ActionLinks will make the call to the index action and return the partial view containing the desired page. Finally, the AJAX ActionLink will replace the contents of the ContactsDiv with the returned data.

Conclusion

This simple technique illustrated above illustrates just how easy it is to create complex items within the MVC framework. You could very easily expand upon this to delivers clickable page numbers similar to many sites. While the scheme is a bit more complex within the pager helper method it is not a drastic change. You could also use this technique to produce the alphabet for searching through a contact list, as an example. Furthermore, this article really demonstrates the difference in thinking needed for creating the necessary components within a site with the MVC Framework versus the standard ASP.NET Framework.



About the Author

Chris Bennett

Chris Bennett is a Manager with Crowe Horwath LLP in the Indianapolis office. He can be reached at chris.bennett@crowehorwath.com.

Related Articles