Paging Data

Explore approaches to page data in MVC views

Building a pager component doesn’t require a huge amount of work. A pager is a list of HTML links, each pointing to a URL. The URL represents the segment of data you want to display. Once you know the size of the data set you’re paging through, and the maximum size of the page you want to display, you’re pretty much done. Creating a pager component is as simple as calculating the right number of links, figuring out an adequate link text, and building the HTML elements.

The most critical part of the job is determining how the link will communicate information to the back end—in this case, the controller. The pager itself is a component distinct from the list of data it's expected to page through. The pager knows nothing about the data it pages; it simply emits a link that, if followed, takes users to a page of data.

Based on these facts, I was surprised when I discovered that neither ASP.NET MVC 1 nor the newer ASP.NET MVC 2 has a built-in pager component. Like many other developers, I built my own pager and used it in various projects. However, it was only recently that I faced the problem of integrating multiple pagers in the same view. My problem was even more complex; not only did I have two independent pagers in the view, but I also had multiple forms posting independently from one another. It turned out that developing a general solution for an HTML pager is much less trivial than it might seem at first. Based on my experience, I’d say that in ASP.NET MVC you probably want a made-to-measure pager for each view. You'll certainly be able to turn it into some reusable and testable code, but having a pager that you just plug in and it works, well, that’s not here yet. And it probably won't be here soon. If you've found (or written) such a component, please let me know.

HTML Pager Design

In this article, I’ll build a hot-pluggable HTML pager that works great if used alone in the view. Then, I’ll add code to make the pager work fine even when multiple instances are used in the same view. To make things even more sophisticated, I’ll use user controls extensively to build the view. Figure 1 shows the final result.

The sample view contains two grids to be paged independently. The view is rendered when you invoke the Index action on the Customer controller. Figure 2 shows the first segment of the controller’s code. Figure 3 shows the view model object.

The Index method grabs data from an internal cache or directly from the database and packs it into the view model object. As expected, the view model object is shaped after the structure of the view. It has a list of orders, the ID of the customer the orders refer to, the size of the page, and the index of the currently displayed page. What about the view?

The View Structure

The view is built by repeating horizontally the same user control. Figure 4 shows the user control's source code—essentially a table of data with a pager component. The pager component is inserted via a custom HTML helper.

As you can see, in this example I put the paginating code in a code block within the view. That is, in fact, the effect of the LINQ expression you see in the foreach code block in Figure 4. Needless to say, you can take this code out of the view and embed it in the controller’s method. However, this is an implementation detail. Let’s focus on the parameters being passed to the Html.Pager custom HTML helper.

The helper gets up to five arguments: the name of the pager, the size of the data set to page through, the maximum size of the page, the base URL for links, and the index of the current page. If you look at the pager's source code, you’ll discover a sixth argument—a dictionary of HTML attributes. Except for the base URL argument, the role of these arguments is fairly straightforward.

As mentioned, I envisioned the pager component as a table of links. For simplicity, I’m only considering a numeric pager; extensions are, of course, possible to make it support more sophisticated layouts. The pager's HTML structure is shown below:

 

<table>
  <tr>
    <td>
      <a href="...">Page #</a>
    </td>
  </tr>
</table>

 

The base URL argument indicates the layout of the URL to be associated with each paging link. In this example, I opted for a classic link to an explicit URL instead of resorting to route links. Assuming the ASP.NET MVC default URL scheme, the URL contains the controller name, the action name, an ID argument, and additional query string arguments related to paging. A sample base URL is shown below:

 

/customer/scroll/alfki? ...

 

I used different actions to serve the page and to update tables of data via paging. When I first worked out this solution, my goal was to obtain an AJAX pager. With AJAX involved, having a separate action method in the controller that you use only to page puts you in a much better position. The Scroll method's code (Figure 5) is similar to the Index method shown in Figure 2.

The method renders a view that shows the specified page of the table of data. The method knows all the details of the data to page through, such as whether the data is cached and where to fetch it if it's not cached. The method may need some parameters to prepare the query—that’s the role of the string parameter. The structure of the URL associated with the link contains an id parameter—from the default URL scheme. That value is bound to the medthod's id parameter. Let’s examine the pager.

The Pager Structure

Figure 6 shows the pager component's source code. The pager is a single-row HTML table in which each cell represents a link. For simplicity, the link text is “Page #.” The URL is based on the provided base URL parameter plus a query string argument named pageIndex. This argument indicates the page to display once the link is followed. The ASP.NET MVC default model binder will map the query string parameter to the action method parameter.

To style the pager's various components, you can simply decorate them with CSS classes to be defined on a per project basis. In this example, the pager inherits the style settings defined for the <table> element and its children. In addition, the cell that shows the current page is styled in a different manner and the text is not a hyperlink.

The Next Step

The implementation of the pager discussed so far is no different than what is proposed in many examples you can find in articles and posts. This probably means there’s no other way to build a pager. But the real issue is: What if you have two or more pagers in the same view? If you look at Figure 1, you'll see that unless you take some countermeasures, the clicked pager works correctly but you lose information about the current page of the other pager. This is a side effect of not having the viewstate in ASP.NET MVC.

In Web Forms, each control can save all the information it needs into the viewstate and recover it over the next postback. In addition, in Web Forms every request is a post and in an HTTP post command the content of hidden fields is regularly uploaded. In ASP.NET MVC, you have total control of the HTML but you lack some infrastructure that made certain features significantly easier to implement. At its core, the problem to solve is finding out how to track the current page selection on any pagers you have in the view.

The current selection of a pager is a piece of information that changes every time the user follows a link and navigates to another page. The tricky point is that the current selection on pager #1 must be tracked by pager #2 and all other pagers you might have. This requires updating the links of all other pagers when one is clicked: tricky, but ultimately not a mission-impossible task. What if pagers are inside a form and the form posts? In this case, you lose information about pagers. To remedy the situation, you must add a hidden field to each pager to store the information. I found a different solution that works regardless of the verb used. This solution stores pager information in the session state and the information is stored in the session state every time the controller method is invoked. The methods of the Pager class you have seen used in Figure 5 perform this task. The code below shows the actual implementation of the Pager class:

 

public class Pager
\\{
    public static void StorePosition(HttpContextBase context, String id, int? pageIndex)
    \\{
        context.Session\\[id\\] = pageIndex ?? 1;
    \\}
    public static int ReadPosition(HttpContextBase context, String id)
    \\{
        var index = (int?) context.Session\\[id\\];
        return index ?? 1;
    \\}
\\}


The lesson I’ve learned is that when an HTML pager is involved the main problem is not creating a paged list class to easily retrieve the few records you need. I deliberately neglected that aspect and opted for a trivial LINQ query in the view. Admittedly, that code probably belongs to the controller.

 

A More Complex UI for the Pager

If you intend to build a pager that works with very large data sets, you probably don’t want to show an endless list of page indexes. You want, instead, to show just a few indexes and add extra links to see more options. How would you handle those extra links? Most likely, you'll need a Pager controller, instead of an HTML helper, that knows how to produce a pager bar and how to deal with its tasks. In this case, though, you end up dealing with two controllers—one that produces the pager markup and one that is responsible for filling up the table of data with the records that fit into the current page.

In ASP.NET MVC 2, Microsoft introduced render actions that seem to be a good fit for this situation. An example of using render actions in the context of ASP.NET MVC paging is discussed in the article, "Add Paging Functionality to your ASP.NET MVC Site" at www.devproconnections.com/article/aspnet2/Add-Paging-Functionality-to-your-ASP-NET-MVC-Site.aspx.

An Elusive Solution

HTML pagers have never been a problem in ASP.NET Web Forms. The pager was automatically incorporated in many data-bound controls, and the POST-based architecture of Web Forms helped in the transmission of state information via hidden fields. In ASP.NET MVC, things are different.

The bottom line is that building HTML pagers in ASP.NET MVC is an easy job if you never have more than one pager in a view. When multiple pagers are expected to be in a view, a general solution based on a reusable component might be hard to find. This article discussed a solution, but don’t be too surprised if you find it to be not as general as you expected. If you find it hard to find a general, fully reusable solution, don’t waste your time—just work out some good handmade HTML that pays the bill.

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish