Displaying a Paged Grid of Data in ASP.NET MVCBy Scott Mitchell
|A Multipart Series on Grids in ASP.NET MVC|
Displaying a grid of data is one of the most common tasks faced by web developers. This article series shows how to display grids of
data in an ASP.NET MVC application and walks through a myriad of common grid scenarios, including paging, sorting, filtering, and client-side
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
www.yoursite.com/Products/Sortable. 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:
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
pageparameter 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.
www.yoursite.com/Products/Pagedwithout specifying any querystring parameters would display the first 10 products, whereas visiting
www.yoursite.com/Products/Paged?page=2&pageSize=15would 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
Productobjects as its model (namely, the
Productsproperty). 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
ProductGridModelthat served as a Model for the
Sortableaction'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.
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).
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.
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
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:
The constructor defines default values for certain properties. In particular, it sets the
NumericPageCount properties to 10.
Step 2: Creating the
In Sorting a Grid of Data in ASP.NET MVC we updated the
ProductsControllerController to include an action named
Sortable, which is the action that is executed when a request comes in for the URL
Sortableaction accepted two input parameters - a string parameter named
sortByand a Boolean parameter named
ascending. When a request arrives for the
Sortableaction, 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
www.yoursite.com/Products/Sortable?sortBy=UnitPrice&ascending=false, ASP.NET MVC invokes the
Sortableaction and passed in the values
falseto the action's
We'll use the same functionality to implement the
Paged action. Namely, our
Paged action will accept two integer input parameters -
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,
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
class that returns an instance of the Linq-to-Sql data context. The code
this.DataContext.Products returns the collection of all products.)
Paged action starts by creating a new
ProductGridModel instance named
model and assigning
PageSize properties to the values of its
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
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
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.
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
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
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 (
and the line of code that returns the precise subset of records (the
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
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
Pagedaction, 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.ProductGridModeloption 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,
Next, add the following markup and server-side code to the View in the Content control for the
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.
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
www.yoursite.com/Products/Paged?page=2 you see the second
page of data (products 11 through 20).
www.yoursite.com/Products/Paged?page=2 prompts the ASP.NET MVC framework to execute the
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
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:
Note how the Model type selected in the Add View dialog box (
Web.Models.ProductGridModel) is specified in the
Inherits attribute. Like with
Sortable View, we can access the strongly-typed Model using the
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:
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
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
property, while the
pageSize parameter should use the value specified by the Model's
PageSize property. (Recall that the
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
defined in the
Html.ActionLink method is used to generate a link to the current action and controller (in this case,
respectively) with the specified route data and HTML attributes. The markup generated by the
Html.ActionLink method is emitted via a call to
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
PagerLinkpartial view directly from the
PagerView 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
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,
- The Model - since both the
PagerLinkpartial views use the same strongly-typed Model (
Pagerpartial view can simply pass its strongly-typed Model object -
Model- as the Model for
- The ViewData information - Here we pass in a
ViewDataobject with four name/value pairs:
Text: "<< Previous"
Model.CurrentPageIndex - 1, which is one less than the current page being viewed... namely, the previous page index,
false, the previous link can never be the selected link, only pager links can be "selected," and
trueonly we are viewing the first page.
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
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.
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.
Displaying Page Number Paging Links
With the code to display Next and Previous links in place we are now ready to augment the
Pagerpartial 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
ProductGridModelclass has a
NumericPageCountproperty 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:
The starting index (
startPageIndex) is the maximum of 1 or the current page index less half of the
NumericPageCount value. Presuming you
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
A concrete example may be in order. Imagine that there are 15 pages of data to display. Here are how the values of
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
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
endPageIndex we instantiate
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:
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.
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!