To read the article online, visit

Displaying a Paged Grid of Data in ASP.NET MVC

By Scott Mitchell


This article demonstrates how to display a paged grid of data in an ASP.NET MVC application and builds upon the work done in two earlier articles: Displaying a Grid of Data in ASP.NET MVC and Sorting a Grid of Data in ASP.NET MVC. Displaying a Grid of Data in ASP.NET MVC started with creating a new ASP.NET MVC application in Visual Studio, then added the Northwind database to the project and showed how to use Microsoft's Linq-to-SQL tool to access data from the database. The article then looked at creating a Controller and View for displaying a list of product information (the Model).

Sorting a Grid of Data in ASP.NET MVC enhanced the application by adding a view-specific Model (ProductGridModel) that provided the View with the sorted collection of products to display along with sort-related information, such as the name of the database column the products were sorted by and whether the products were sorted in ascending or descending order. The Sorting a Grid of Data in ASP.NET MVC article also walked through creating a partial view to render the grid's header row so that each column header was a link that, when clicked, sorted the grid by that column.

In this article we enhance the view-specific Model (ProductGridModel) to include paging-related information to include the current page being viewed, how many records to show per page, and how many total records are being paged through. Next, we create an action in the Controller that efficiently retrieves the appropriate subset of records to display and then complete the exercise by building a View that displays the subset of records and includes a paging interface that allows the user to step to the next or previous page, or to jump to a particular page number, we create and use a partial view that displays a numeric paging interface

Like with its predecessors, this article offers step-by-step instructions and includes a complete, working demo available for download at the end of the article. Read on to learn more!

Step 0: A Brief Roadmap

This article walks through displaying a paged grid of data. It is presumed that you have already read and worked through the following two articles:
  • Displaying a Grid of Data in ASP.NET MVC, in which we created the ASP.NET MVC demo application and saw how to display a grid of data without any features like sorting or paging, and
  • Sorting a Grid of Data in ASP.NET MVC, in which we created a view-specific Model (ProductGridModel) and partial view to display a sorted grid of data and to allow the user to sort the grid by a column of their choice in either ascending or descending order.

Recall that in Sorting a Grid of Data in ASP.NET MVC the demo was available at the URL Visiting this URL displayed the grid using its default sorting characteristics, which was to sort the grid by the ProductName column in ascending order. To have the data sorted by an alternate column or sorting direction, you'd pass in the column name and sort direction through the querystring like so:|false. For example, the URL /Products/Sortable?sortBy=UnitPrice&ascending=false would display the products sorted by the UnitPrice column in descending order.

The paged grid we will create in this article will work in much the same manner. Namely, there will be two optional querystring parameters:

  • page - specifies the index of the page of data to display; note that this index starts at 1, meaning to view page 2 you'd specify a page parameter of 2. If this value is omitted it defaults to showing the first page of data (that is, it has a default value of 1).
  • pageSize - indicates how many records to show per page. Defaults to 10.
Visiting the URL without specifying any querystring parameters would display the first 10 products, whereas visiting would display the second page of products where each page was 15 records (in other words, it would display products 16-30).

One final note before we get started: the code/demo in this article focuses on paging only. The grid displays the products sorted by their ProductID values in ascending order. A future article will examine how to create a sortable, paged grid.

Step 1: Updating ProductGridModel, the View-Specific Model

The demo we created in Displaying a Grid of Data in ASP.NET MVC used a collection of Product objects as its model (namely, the NorthwindDataContext object's Products property). While this worked fine for the simple grid, a more nuanced approach was needed for the sortable grid demo created in Sorting a Grid of Data in ASP.NET MVC. In particular, to render a sortable grid the View needed to know what column the data was sorted by and whether it was sorted in ascending or descending order. To supply this extra information to the View we created a new class named ProductGridModel that served as a Model for the Sortable action's View. (Such classes are referred to as view-specific Models, as they are Models created for a specific View.)

For the paged grid we need a view-specific Model, as well. In particular, the Paged action's View needs to know the following bits of information in order to correctly render the grid's paging interface:

  • The subset of records to display - the View should not be responsible for determining what records to display. Rather, the Controller should determine the precise subset of records to show for a given page of data and provide those records to the View via the Model.
  • The current page index
  • The number of records to display per page
  • The total number of pages being paged through - this number, plus the number of records to display per page, are needed to determine how many pages there are in total.
In addition to the above data points, there may be additional bits of information that the View needs depending on the grid's paging interface. If you are familiar with ASP.NET's WebForms GridView control then you know there are two paging interface modes: numeric pages and next/previous links. With numeric pages, the grid's paging interface displays a series of page numbers - 1, 2, 3, 4, ... - that allow the user to jump to a particular page of data. With next/previous links, the paging interface contains Next and Previous links that, when clicked, take the user to the previous or next pages. When displaying numeric pages the View needs to know the maximum number of numeric pages to display.

The paging interface for this demo is the same as the paging interface I presented in an earlier 4Guys article: Using ASP.NET 3.5's ListView and DataPager Controls: The Ultimate DataPager Interface. This paging interface presents both numeric pages and previous and next links, as the screen shot below shows. In a nutshell, the Previous and Next links are always displayed (although the are disabled when viewing the first or last page, respectively).

The grid's paging interface when viewing page 12 of 15.

In the screen shot above, the user is viewing page 12 of 15. The paging interface shows the five page numbers preceding and proceeding the current page index. In this case, the five preceding pages are 7 through 11; because there are only 15 pages in total, however, there are only three proceeding page numbers: 13, 14, and 15. Regardless of what page is currently being displayed the first and last pages are always displayed, giving the user quick access to the first or last page of data. In the screen shot above you can see that pages 1 and 2 are displayed, followed by an ellipsis. The screen shot below shows the same paging interface, but when viewing page 5 of 15. Note how the last two pages - 14 and 15 - are displayed.

The grid's paging interface when viewing page 5 of 15.

We'll explore the markup and code used to render the paging interfaces in more detail in Step 4.

All of this discussion of the paging interface is just to say that in addition to the subset of records to display and the current page index, the number of records to display per page, and the total number of records being paged through, our view-specific Model must also specify the number of numeric links to display. (The above paging interface screen shots are configured to display at most consecutive page numbers.)

To implement this view-specific Model we could create add a new class to our project that included properties to capture this information. However, if we later decided we needed to create a sortable, paged grid, we'd need a new class that offered both the sorting-related properties created in the sorting grid view-specific Model (ProductGridModel) and the Model created specifically for paging. Rather than having to duplicate this work later, I decided to add the paging-specific properties directly to the ProductGridModel class. This way, the ProductGridModel class can be used as a view-specific Model for displaying product information in a paged grid, a sortable grid, or - as we'll see in a future installment - in a paged, sortable grid.

If you are following along at your computer, expand the Models folder and open the ProductGridModel.cs file. Update the class to include the following properties and constructor:

namespace Web.Models
   public class ProductGridModel
      // Constructor
      public ProductGridModel()
         // Define any default values here...
         this.PageSize = 10;
         this.NumericPageCount = 10;

      // Data properties (this property already exists - you don't need to add this!)
      public IEnumerable<Product> Products { get; set; }

      // Sorting-related properties

      // Paging-related properties
      public int CurrentPageIndex { get; set; }
      public int PageSize { get; set; }
      public int TotalRecordCount { get; set; }
      public int PageCount
            return this.TotalRecordCount / this.PageSize;
      public int NumericPageCount

The constructor defines default values for certain properties. In particular, it sets the PageSize and NumericPageCount properties to 10.

Step 2: Creating the Paged Action

In Sorting a Grid of Data in ASP.NET MVC we updated the ProductsController Controller to include an action named Sortable, which is the action that is executed when a request comes in for the URL The Sortable action accepted two input parameters - a string parameter named sortBy and a Boolean parameter named ascending. When a request arrives for the Sortable action, ASP.NET MVC automatically maps any of the request's parameters to the input parameters of the executed action. For instance, when a request arrives for the URL, ASP.NET MVC invokes the Sortable action and passed in the values UnitPrice and false to the action's sortBy and ascending input parameters.

We'll use the same functionality to implement the Paged action. Namely, our Paged action will accept two integer input parameters - page and pageSize. If the request for this action includes those values in the querystring then those querystring values will automatically be assigned to these input parameters. If they're omitted then the input parameter's default values will be used instead (1 and 10, respectively).

The Paged action is responsible for creating a ProductGridModel object and assigning its paging-related values. The following snippet shows the code for the Paged action. (Recall that in an earlier installment we added a DataContext property that to the ProductsController class that returns an instance of the Linq-to-Sql data context. The code this.DataContext.Products returns the collection of all products.)

public class ProductsController : Controller

   // GET: /Products/Paged?page=number&pageSize=number
   public ActionResult Paged(int page = 1, int pageSize = 10)
      var model = new ProductGridModel()
         CurrentPageIndex = page,
         PageSize = pageSize

      // Determine the total number of products being paged through (needed to compute PageCount)
      model.TotalRecordCount = this.DataContext.Products.Count();

      // Get the current page of products
      model.Products = this.DataContext.Products
                           .Skip((model.CurrentPageIndex - 1) * model.PageSize)

      return View(model);
   } }

The Paged action starts by creating a new ProductGridModel instance named model and assigning model's CurrentPageIndex and PageSize properties to the values of its page and pageSize input parameters. Next, the Paged action determines the total number of records being paged through - this.DataContext.Products.Count() - and assigns the resulting number to model's TotalRecordCount property. Next, the precise subset of products to display for the current page of data are assigned to the Products property. Finally, the action returns a strongly-typed View, passing in model.

If you are not familiar with LINQ you may be perplexed at how I get the precise subset of records and may be concerned about performance implications when paging through a large number of records. Let's deal with the syntax first and the performance concerns second.

The code this.DataContext.Products returns the entire set of products. Of course, we're not interested in all of the products, but rather just the subset of products for the requested page. Imagine that we are displaying 10 records per page and need to display the second page of data. In this case we do not want to include the first 10 records since those belong to page 1. The Skip method is used to skip over those records that are displayed in previous pages. Specifically, we skip over (CurrentPageIndex - 1) * PageSize number of records. Here, CurrentPageIndex is the index of the page of data we are displaying and PageSize is the number of records per page. So if we are viewing the first page of data then CurrentPageIndex equals 1 and we skip over zero records, but if we are viewing the second page of data then CurrentPageIndex equals 2 and so we skip over the first 10 records: (2 - 1) * 10 = 10. When viewing the third page of data we'd skip over the first 20 records: (3 - 1) * 10 = 20. And so on...

Once we skip over the first 0, 10, 20, ... records, we next need to get just the subsequent 10 records (or, more generally, the next PageNumber number of records). This is where the Take method comes in. Take returns just the starting specified number of records and discards the rest. Returning to our hypothetical scenario - displaying the second page of data when showing 10 records per page - we'd end up executing the following code: this.DataContext.Products.Skip(10).Take(10), which in English says, "From the set of all products, skip over the first 10 and give me the next 10," or, more plainly, "Give me products 11 through 20."

What about performance? There are two lines of code that may be worrying - the line of code that returns the count of products (this.DataContext.Products.Count()) and the line of code that returns the precise subset of records (the Skip/Take part). You may be worried that these lines of code return all of the products and then apply their counting or filtering operations. Such functionality would be costly if there are, say, tens of thousands, say, in the Products database table. Fortunately, Linq-to-Sql is smart enough to craft a more efficient SQL query. The line of code that returns the count of the number of products resolves into the SQL query SELECT COUNT(*) FROM Products, whereas the Skip and Take methods resolve into a SQL statement that uses SQL's ROW_NUMBER() function to efficiently return the appropriate subset of records. Long story short, even if you're paging through a very large table, the code presented here will be efficient.

Step 3: Creating the View

To create the view for the Paged action, right-click on the action name in the Controller class file and choose the Add View option. From the Add View dialog box, check the "Create a strongly-typed view" checkbox and then select the Web.Models.ProductGridModel option from the "View data class" drop-down (see the screen shot below). Click OK. This should close the dialog box and create (and open) the new view, Paged.aspx.

Add a strongly-typed View for the Paged action.

Next, add the following markup and server-side code to the View in the Content control for the MainContent ContentPlaceHolder:

   <i>You are viewing page <%: Model.CurrentPageIndex %> of <%: Model.PageCount %>...</i>
<table class="grid" style="width: 90%">
      <th style="width: 35%">Product</th>
      <th style="width: 25%">Category</th>
      <th style="width: 25%">Qty/Unit</th>
      <th style="width: 15%">Price</th>
      <th style="width: 5%">Discontinued</th>

<% foreach (var item in Model.Products)
   { %>
      <td class="left"><%: item.ProductName%></td>
      <td class="left"><%: item.Category.CategoryName%></td>
      <td class="left"><%: item.QuantityPerUnit%></td>
      <td class="right"><%: String.Format("{0:C}", item.UnitPrice)%></td>
         <% if (item.Discontinued)
            { %>
            <img src="<%=Url.Content("~/Content/cancel.png") %>" alt="Discontinued" title="Discontinued" />
         <% } %>
<% } %>

The above markup is nearly identical to what we added to the Sortable View in Sorting a Grid of Data in ASP.NET MVC). The only differences are the message above the grid that says, "You are viewing page x of y" and the explicitly-specified widths for the grid's columns defined in the header rows. Take a moment to visit this View through a browser. If you visit Products/Paged you should see a grid showing the first 10 products.

Only the first 10 products are displayed.

The grid shows only the first 10 products. Right now there is no way for the user to move to the next page or to jump to a specific page number short of specifying these values by typing them directly into their browser's Address bar. For example, when you visit you see the second page of data (products 11 through 20).

The second page - products 11 through 20 - are now displayed.

Visiting prompts the ASP.NET MVC framework to execute the ProductsController class's Paged method, passing in the value 2 to its page input parameter. (Because a value for pageSize was not specified, it uses its default value, 10.) The code in the Paged action uses these input parameter values and returns the second page of products to the View.

Step 4: Adding the Paging Interface

At this point the user viewing the grid cannot move to another page unless she types in the querystring parameters by hand in her browser's Address bar. To remedy this we need to add a paging interface to the View. The paging interface will be implemented as a series of links that, when clicked, send the user to the same URL (/Products/Paged), but with the appropriate querystring parameters and values.

Creating the Paging Links

One of the challenges of rendering a paging interface is that certain paging links may be disabled, depending on the page being viewed. For example, when viewing the first page of data the Previous link should not be rendered as a link, but rather as text. Because the style and output for a link depends on multiple factors, I decided to create a partial view to handle nothing but paging link rendering. In short, this partial view would be used to render a particular paging interface link, such as the Previous link or the Next link or a particular page number.

As discussed in Sorting a Grid of Data in ASP.NET MVC, a partial view is akin to a User Control in the WebForms model. In short, it's a View that you can use (and reuse) in a View. If you are following along at your computer, create a new partial view named PagerLink.ascx by right-clicking on the Views/Shared folder in the Solution Explorer and choosing Add -> Add View. Name the View PagerLink and check the "Create a partial view (.ascx)" checkbox. Also check the "Create a strongly-typed view" checkbox and then choose the Web.Models.ProductGridModel type from the drop-down.

After clicking OK, Visual Studio will create a new partial view file (PagerLink.ascx) in the Views/Shared folder. At this point the partial view contains only the following markup:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Web.Models.ProductGridModel>" %>

Note how the Model type selected in the Add View dialog box (Web.Models.ProductGridModel) is specified in the Inherits attribute. Like with the Sortable View, we can access the strongly-typed Model using the Model property.

In addition its Model property, partial views (and regular Views, for that matter) also have access to a ViewData property, which is a loosely-typed collection of information. The PagerLink partial view needs some additional information not included in the ProductGridModel, such as the text to display in the pager link and whether the pager link is inactive or selected. (An inactive link is one that should be rendered as text rather than as a hyperlink, such as the Previous link when viewing the first page of data; a selected link is the page number that is currently being viewed. A selected link has a different CSS class applied that styles the link differently than the non-selected ones.)

Add the following code to the PagerLink partial view:

   if ((bool)ViewData["Inactive"])
      Response.Write(string.Format("<span class=\"{0}\">{1}</span>", "pagerButtonDisabled", ViewData["Text"]));
      var routeData = new RouteValueDictionary { { "page", ViewData["PageIndex"].ToString() }, { "pageSize", Model.PageSize } };
      var htmlAttributes = new Dictionary<string, object>();
      if ((bool)ViewData["Selected"])
         htmlAttributes.Add("class", "pagerButtonCurrentPage");
         htmlAttributes.Add("class", "pagerButton");

               ViewData["Text"].ToString(),                                // Link Text
               Html.ViewContext.RouteData.Values["action"].ToString(),     // Action
               Html.ViewContext.RouteData.Values["controller"].ToString(), // Controller
               routeData,                                                  // Route data
               htmlAttributes                                              // HTML attributes to apply to hyperlink

The first line of code determines if the Inactive flag has been set. If so, the partial view renders the "link" as text - namely as a <span> element with the CSS class pagerButtonDisabled containing the text specified by the link's Text value. (Again, these values - Inactive and Text - are defined in the ViewData collection. We'll see how these parameters get set shortly.)

If the link is not inactive then a link is generated using the Html.ActionLink method. But first the route data is constructed. The route data specifies the values that will appear in the querystring. Here we indicate that the page querystring parameter should use the value specified by the PageIndex property, while the pageSize parameter should use the value specified by the Model's PageSize property. (Recall that the ProductGridModel class has a PageSize property that indicates how many records to show per page.)

Next, a Dictionary is created to house the HTML attributes that will apply to this link. If the link is flagged as Selected then the link will use the CSS class pagerButtonCurrentPage, otherwise it uses the CSS class pagerButton. (These CSS classes, as well as pagerButtonDisabled, are defined in the ~/Content/CustomStyles.css file.)

Finally, the Html.ActionLink method is used to generate a link to the current action and controller (in this case, Paged and Products, respectively) with the specified route data and HTML attributes. The markup generated by the Html.ActionLink method is emitted via a call to Response.Write.

Displaying Next and Previous Paging Links

At this point we have created a partial view (PagerLinks.ascx) that is responsible for rendering a single paging link. We're now ready to use this partial view to display the paging links in the grid. Rather than reference the PagerLink partial view directly from the Pager View I decided to instead create another partial view. The reasoning here was that we might want to use this paging interface in other Views; having it in a partial view will simplify this task later.

To follow along, add another partial view to the Views/Shared folder named Pager.ascx. Like with PagerLink, having this partial view also be implemented as a strongly-typed view that uses the Web.Models.ProductGridModel type. Add the following markup and code to the Pager.ascx file:

<div class="pager">
   // Create Previous link
   Html.RenderPartial("PagerLink", Model, new ViewDataDictionary { { "Text", "&lt;&lt; Previous" }, { "PageIndex", Model.CurrentPageIndex - 1 }, { "Selected", false }, { "Inactive", Model.CurrentPageIndex == 1 } });

   // Create Next link
   Html.RenderPartial("PagerLink", Model, new ViewDataDictionary { { "Text", "Next &gt;&gt;" }, { "PageIndex", Model.CurrentPageIndex + 1 }, { "Selected", false }, { "Inactive", Model.CurrentPageIndex == Model.PageCount } });

The above markup renders two paging links (using the PagerLink partial view): << Previous and Next >>. The syntax can be a bit intimidating at first, but if you format it a bit it becomes clearer. Let's look at the Previous link. This is created by using the Html.RenderParial method and specifying three bits of information:

  • The partial view to use - in this case, PagerLink,
  • The Model - since both the Pager and PagerLink partial views use the same strongly-typed Model (ProductGridModel), the Pager partial view can simply pass its strongly-typed Model object - Model - as the Model for PagerLink.
  • The ViewData information - Here we pass in a ViewData object with four name/value pairs:
    • Text: "&lt;&lt; Previous"
    • PageIndex: Model.CurrentPageIndex - 1, which is one less than the current page being viewed... namely, the previous page index,
    • Selected: false, the previous link can never be the selected link, only pager links can be "selected," and
    • Inactive: true only we are viewing the first page.
The Next link behaves similarly, except that its PageIndex property is set to the next page index (Model.CurrentPageIndex + 1) and it is only inactive when the user is viewing the last page.

To see our current progress, return to the View and add the following markup right before the closing <table> tag.

<table class="grid" style="width: 90%">
      <td class="pager" colspan="5">
         <% Html.RenderPartial("Pager", Model); %>

The above markup tells ASP.NET MVC to render the Pager partial view, passing it the View's Model object into the partial view, which is what the partial view uses as its Model.

With the above addition in place, visit the Products/Paged View through a browser. As the screen shot below shows, you will now see Next and Previous links in the bottom row of the grid. In the screen shot below, the user is viewing the first page, so the Previous link is inactive.

Next and Previous buttons are displayed at the bottom of the grid.

Clicking the Next button whisks the user to /Products/Paged?page=2&pageSize=10. The second page of data (products 11 through 20) are displayed and both the Previous and Next buttons are active.

The second page of data is displayed.

Displaying Page Number Paging Links

With the code to display Next and Previous links in place we are now ready to augment the Pager partial view to also include the page number links. This is accomplished by first determining the starting and ending indexes for the page numbers we want to display. Recall that the ProductGridModel class has a NumericPageCount property that indicates how many numeric pages to display, and that this value has a default value of 10. This value, along with the current page index, is used to determine the starting and ending indexes:

// Create numeric links
var startPageIndex = Math.Max(1, Model.CurrentPageIndex - Model.NumericPageCount / 2);
var endPageIndex = Math.Min(Model.PageCount, Model.CurrentPageIndex + Model.NumericPageCount / 2);

The starting index (startPageIndex) is the maximum of 1 or the current page index less half of the NumericPageCount value. Presuming you leave the NumericPageCount property assigned to 10, this means that the starting page index will be the larger of 1 or the current page index minus 5. The ending index (endPageIndex) is the minimum of the last page (PageCount) and the current page index plus half of the NumericPageCount value.

A concrete example may be in order. Imagine that there are 15 pages of data to display. Here are how the values of startPageIndex and endPageIndex will vary as the user moves from page 1 through page 15 (presuming NumericPageCount equals 10):


The take away here is that as one moves through the 15 pages, the range of page numbers displayed in the paging interface will encompass at most NumericPageCount number of paging links - for example, when view pages 6 through 10 - but may contain fewer than NumericPageCount number of paging links - for example, when viewing pages 1 through 5 or 11 through 15.

The following loop adds the appropriate set of page numbers. Note how for each index value between startPageIndex and endPageIndex we instantiate the PagerLink partial view, passing in the page number as its Text and PageIndex value and indicating it is selected only if the index in the loop equals the current page index:

// Add in the numeric pages
for (var i = startPageIndex; i <= endPageIndex; i++)
   Html.RenderPartial("PagerLink", Model, new ViewDataDictionary { { "Text", i }, { "PageIndex", i }, { "Selected", i == Model.CurrentPageIndex }, { "Inactive", false } });

There is some additional code that adds in the 1, 2, and ... links (as well as the last couple of page number links) if needed. I encourage you to try to write this code on your own, but if you get stuck you can find it in the Pager partial view in the demo available for download at the end of this article.

With the Pager partial view completed, the paging interface is done! We now have a paged grid in an ASP.NET MVC application.

Conclusion and Looking Forward...

This article showed how to use a view-specific Model and a duo of partial views to display a paged grid of data. At this point, the grid is sorted by its default ordering (by ProductID). In a future installment we'll see how to display a grid that is both sortable and paged.

Until then... Happy Programming!

  • By Scott Mitchell


  • Download the Demo Code Used in this Article

    Further Reading

  • Displaying a Grid of Data in ASP.NET MVC
  • Sorting a Grid of Data in ASP.NET MVC
  • Article Information
    Article Title: Displaying a Paged Grid of Data in ASP.NET MVC
    Article Author: Scott Mitchell
    Published Date: January 5, 2011
    Article URL:

    Copyright 2021 QuinStreet Inc. All Rights Reserved.
    Legal Notices, Licensing, Permissions, Privacy Policy.
    Advertise | Newsletters | E-mail Offers