Sorting a Grid of Data in ASP.NET MVC
By 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
enhancements.
|
Introduction
Last week's article, Displaying a Grid of Data in ASP.NET MVC, showed, step-by-step, how to display a grid of data in an ASP.NET MVC application. Last week's article 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).
This article builds on the demo application created in Displaying a Grid of Data in ASP.NET MVC, enhancing the grid to include bi-directional sorting. If you come from an ASP.NET WebForms background, you know that the GridView control makes implementing sorting as easy as ticking a checkbox. Unfortunately, implementing sorting in ASP.NET MVC involves a bit more work than simply checking a checkbox, but the quantity of work isn't significantly greater and with ASP.NET MVC we have more control over the grid and sorting interface's layout and markup, as well as the mechanism through which sorting is implemented. With the GridView control, sorting is handled through form postbacks with the sorting parameters - what column to sort by and whether to sort in ascending or descending order - being submitted as hidden form fields. In this article we'll use querystring parameters to indicate the sorting parameters, which means a particular sort order can be indexed by search engines, bookmarked, emailed to a colleague, and so on - things that are not possible with the GridView's built-in sorting capabilities.
Like with its predecessor, 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 grid of data that offers bi-directional sorting. It is presumed that you have read 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.
In Displaying a Grid of Data in ASP.NET MVC the simple grid demo was available at the URL www.yoursite.com/Products/Index
(which could be shortened to
www.yoursite.com/Products
, since Index
is the default action name). For this demo let's use the following general URL pattern for the sortable
grid: www.yoursite.com/Products/Sortable?sortBy=ColumnName&ascending=true|false
. Using this generalized pattern, here is what some concrete
URLs might look like:
/Products/Sortable
- this should display the products sorted in their default order. Let's define the default sort order to be sorted by product name alphabetically (e.g., in ascending order). In other words, if thesortBy
parameter is not supplied, sort byProductName
; if theascending
parameter is not supplied, sort in ascending order./Products/Sortable?sortBy=UnitPrice
- displays the products sorted by theUnitPrice
column in ascending order./Products/Sortable?sortBy=UnitPrice&ascending=false
- displays the products sorted by theUnitPrice
column in descending order (from most expensive to least).
www.yoursite.com/Products/Sortable?sortBy=UnitPrice&ascending=true
. Note that the column
name displayed in the grid ("Price," in this case), is not necessarily the column name that is passed in the sortBy
querystring parameter. The sortBy
querystring parameter provides the name of the database column by which the data should be sorted, which may not be the same as the text displayed in the grid column's
header.
Step 1: Creating a 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, for a sortable
grid the View needs to know a bit more than just the set of products to display in order to correctly render its user interface. In particular, the View needs to know
what column the data is sorted by and whether it's sorted in ascending or descending order. This information is needed for two reasons:
- If the user opts to sort the grid by a column that is not the one the grid is currently sorted by, then we will sort the grid by that column in ascending order. However, if the user opts to sort the grid by the column that the grid is already sorted by then we want to toggle the ascending parameter so that clicking a column once sorts the grid by that column in ascending order, clicking it twice sorts it in descending order, clicking it thrice sorts it in ascending order, and so on. To accomplish this we need to know what column the grid is sorted by and whether it's sorted in ascending or descending order.
- We also need to know what column the grid is sorted by (and whether it's sorted in ascending or descending order) if we want to provide the user a visual cue (such as an up or down arrow) to indicate what column the data is sorted by.
To supply this extra information to the View let's add a new class to our project that will serve as a Model for this particular View. Such classes are referred to as
view-specific Models, as they are Models created for a specific View. If you are following along at your computer, open the MVC application created in
Displaying a Grid of Data in ASP.NET MVC and add a new class in the Models
folder named ProductGridModel.cs
with the following code:
namespace Web.Models
|
The ProductGridModel
class defines a Products
property, which is the collection of Product
objects that are to be displayed in the
grid. There are also three sorting-related properties:
SortBy
- the name of the database column by which to sort the dataSortAscending
- a Boolean value that indicates whether to sort the data in ascending orderSortExpression
- a read-only property that returns a sorting string constructed from the values of theSortBy
andSortAscending
properties. For example, ifSortBy
is assigned "UnitPrice" andSortAscending
istrue
,SortExpression
returns "UnitPrice asc". IfSortBy
is set to "Discontinued" andSortAscending
isfalse
,SortExpression
returns "Discontinued desc". As we will see shortly, this property is used to apply the actual sorting logic in the Controller.
ProductGridModel
class has a number of additional properties not noted above. These additional properties will be
examined in a future installment.
Step 2: Creating the Sortable
Action
In Displaying a Grid of Data in ASP.NET MVC we created a Controller named
ProductsController
with a single action, Index
, and a helper
property, DataContext
, that returns a NorthwindDataContext
object. Let's add a new action to this Controller named Sortable
, which
is the action that will be executed when a request comes in for the URL www.yoursite.com/Products/Sortable
.
ASP.NET MVC automatically performs model binding, which is a mapping of the request's parameters to the input parameters of the executed action. For example, if
your action defines an input parameter named sortBy
, the MVC framework will search the incoming request's parameters to see if there is any parameter
with the same name. (Here, a parameter could be a submitted form field, a querystring parameter, or a route parameter.) If a match is found it is passed into the
action's input parameter.
If that doesn't quite make sense, perhaps a look at the code will help. Here's the complete code for the Sortable
action:
public class ProductsController : Controller
|
Note that the Sortable
action accepts two input parameters: sortBy
and ascending
. These input parameters will be assigned
the value of any matching parameter in the incoming request (such as a querystring parameter). If no such match is found - that is, if someone visits
www.yoursite.com/Products/Sortable
without specifying the querystring parameters - the default values will be used. Here we defined the default
values of sortBy
and ascending
to be "ProductName" and true
, respectively. (Note: support for specifying default values
for an input parameter in a method was added to C# version 4. See Optional
Parameters and Named Arguments in C# 4 for more details.)
The Sortable
action starts by creating a new ProductGridModel
instance named model
and assigning model
's
SortBy
and SortAscending
properties to the values of its sortBy
and ascending
input parameters. Next, the
Sortable
action gets the entire collection of Products
(this.DataContext.Products
), orders it by the
ProductGridModel
object's SortExpression
value, and assigns it to model
's Products
property. Finally, the action
returns a strongly-typed View, passing in model
.
If you are familiar with LINQ you may be perplexed at my use of the OrderBy
extension
method where I simply pass in a string by which to sort the Products
collection (such as "ProductName desc"). The OrderBy
extension method
you are likely familiar with accepts as input a lambda expression that indicates the property to sort on, such as:
this.DataContext.Products.OrderBy(p => p.ProductName)
.
The OrderBy
method I am using is not part of the standard .NET Framework, but is rather part of Microsoft's
Dynamic LINQ library, which is a library
that allows querying using string arguments. The demo project available for download includes a file in the HelperClasses
folder named
Dynamic.cs
, which contains the OrderBy
extension method signature that accepts a string as input. If you are following along at your computer,
either download the demo project and add the Dynamic.cs
class file to your project, or
download the Dynamic.cs
file from Microsoft. Also, take a moment to read
Scott Guthrie's blog entry, Dynamic
LINQ, as it gives a quick overview of Dynamic LINQ and how and why you might want to use it.
Step 3: Creating the View
To create the view for the
Sortable
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, Sortable.aspx
.

Next, add the following markup and server-side code to the View in the Content control for the MainContent
ContentPlaceHolder:
<table class="grid">
|
The above markup is nearly identical to what we added to the Index
View in Displaying a Grid
of Data in ASP.NET MVC). The only difference, in fact, is in the foreach
construct. In Displaying a Grid of Data in ASP.NET MVC the Index
View's Model was a collection of Product
objects, so our foreach loop looked like so: foreach(var item in Model) { ... }
. However, the
Sortable
View's Model is a ProductGridModel
instance whose Products
property contains the collection of Product
objects
to display. Consequently, the foreach
loop iterates over Model.Products
.
Take a moment to visit this View through a browser. If you visit Products/Sortable
you should see a grid showing all the products ordered by the default
ordering - namely, sorted by product name in alphabetical order.

Right now the grid's column heads are rendered as text. In a moment we'll update the markup to emit hyperlinks, but for now if you go to your browser's Address bar and
enter in the querystring parameters manually you can see the results. For instance, if you replace the current URL in your Address bar - say,
www.yoursite.com/Products/Sortable
- with the following - www.yoursite.com/Products/Sortable?sortBy=UnitPrice&ascending=false
- you should see the products
sorted by price, from the most expensive to the least.
When you visit www.yoursite.com/Products/Sortable?sortBy=UnitPrice&ascending=false
the ASP.NET MVC framework executes the ProductsController
class's Sortable
method, passing in the values "UnitPrice" and false
as the input parameters for the method's sortBy
and
ascending
input parameters. The Dynamic LINQ library is then used to order the set of all products using the sort expression "UnitPrice desc". This ordered
collection of Product
objects is then passed to the View, which displays them in a grid.
Step 4: Adding Sorting Links to the Grid's Header Row
At this point the user viewing the grid cannot sort it unless she types in the querystring parameters by hand in her browser's Address bar. To remedy this we need to have the grid's header row render hyperlinks in each of the header cells that point to the appropriate URL. At first blush this may seem like a straightforward task: the Product row's header would be a URL to
Products/Sortable?sortBy=ProductName&ascending=true
, the Price row's header would be a URL to
Products/Sortable?sortBy=UnitPrice&ascending=true
, and so on. This is what the URL should be for the non-sorted columns, but for the column that is
already sorted in ascending order, the URL should be Products/Sortable?sortBy=ColumnName&ascending=false
. Also, we should add some sort
of visual cue to the header row of the column the grid is sorted by so that the user can clearly see how the grid is sorted.
To accomplish this I decided to create a partial view. 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 SmartLink.ascx
by right-clicking on the Views/Shared
folder in the Solution Explorer and choosing Add -> Add View. Name the View SmartLink
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 (SmartLink.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.
We'll come back to write the markup and code for this partial view in a moment, but first let's look at how we'll use this partial view to the Sortable
View. To add a partial view to a "normal" View, use one of the following Html.RenderParial
method overrides:
<% Html.RenderPartial("partialViewName"); %>
|
The optional viewData parameter is a ViewDataDictionary
object. If supplied, these values can be accessed from the partial view using the syntax
ViewData["name"]
.
When rendering the SmartLink
partial view we need to pass in the Sortable
View's ProductGridModel
and some additional information.
Specifically, we need to supply the name of the column to show in the grid (such as "Product" or "Category") as well as the name of the database column that provides
that data (such as "ProductName" and "Categories.CategoryName"). This additional information is supplied through viewData.
The following code snippet shows how we'll render the partial view from the Sortable
View. In particular, this snippet shows the code for the
Product column. Note how the model that's passed into the partial view is the exact same Model
object passed to the Sortable
View.
And the viewData dictionary passed into partial view contains two entries named "ColumnName" and "DisplayName", which in this case are assigned the values
"ProductName" and "Product", respectively.
<% Html.RenderPartial("SmartLink", Model, new ViewDataDictionary { { "ColumnName", "ProductName" }, { "DisplayName", "Product" } }); %>
|
Take a moment to replace the existing header markup with the following content. Note that each column header's text has been replaced with the SmartLink
partial
view. The Html.RenderPartial
method call in each column header passes in the same partial view name and model, but differs by the values supplied in
the viewData's "ColumnName" and "DisplayName" entries.
<table class="grid">
|
Now that we've seen how the partial view will be used from the Sortable
View, we're ready to write its markup and code. Keep in mind that from this partial
view we can access any value of the Sortable View's Model via the Model
property; the values in the viewData collection are accessible via
ViewData["name"]
. Also remember that the purpose of the SmartLink
partial view is to generate a hyperlink for one particular column in
the grid.
First things first, we need to determine if the link being rendered needs to indicate that the data should be sorted in ascending or descending order. As we discussed earlier
in this article, the only time clicking a sorting link should display data in descending order is if the grid is already sorted by that column in ascending order.
The following line of code creates a variable named isDescending
that is set to true
only if the column that the grid is currently sorted by
(Model.SortBy
) is the same as the name of the database column this SmartLink
is generating a hyperlink for (ViewData["ColumnName"]
)
and the data is currently sorted in ascending order (Model.SortAscending
):
var isDescending = string.CompareOrdinal(Model.SortBy, ViewData["ColumnName"].ToString()) == 0 && Model.SortAscending;
|
It would be nice to provide the user some sort of visual cue indicating the column (and order) by which the data is currently sorted. The CSS class
CustomStyles.css
defines two CSS classes - sortAsc
and sortDesc
- that add an up or down arrow to the right of any HTML element
that specifies that class name. Therefore, when rendering a hyperlink the SmartLink
partial class needs to determine if the data is currently sorted
by its ViewData["ColumnName"]
value. If so, then it needs to set its hyperlink's class
attribute to either sortAsc
or
sortDesc
depending on whether the data is currently sorted in ascending or descending order.
The following code defines a dictionary of HTML attributes and adds the appropriate class
attribute value if column the data is currently sorted by
(Model.SortBy
) is the same as the database column name for the SmartLink
being rendered (ViewData["ColumnName"]
):
if (string.CompareOrdinal(Model.SortBy, ViewData["ColumnName"].ToString()) == 0)
|
Putting it all together we end up with the following code and markup in the SmartLink
partial view:
<%
|
The Html.ActionLink
method renders the HTML for a hyperlink. It takes in as its first parameter the text to display in the hyperlink - ViewData["DisplayName"]
.
Its second and third parameters specify the action and controller used to generate the link. Because we want these links to send the user back to the current action
(just with different querystring parameters), we use Html.ViewContext.RouteData.Values["action"].ToString()
and
Html.ViewContext.RouteData.Values["controller"].ToString()
to get the action and controller for the current request, respectively. (The reason I went with
this approach rather than hard-coding in "Sortable" and "Products" is so that this partial view can be used by other views.)
The Html.ActionLink
method's fourth input parameter specifies the route data, which is any additional routing information besides the controller and action.
This is where we supply the sortBy
and ascending
values, which are the values of ViewData["ColumnName"]
and the negation of
isDescending
, respectively.
The fifth and final input parameter to Html.ActionLink
is the set of HTML attributes. This will add the up or down arrow to the hyperlink the
SmartLink
is rendering for the column that the grid is already sorted by.
With the partial view implemented and used from the Sortable
View, take a moment to visit the page through a browser. When visiting Products/Sortable
you should see the products sorted by product name in alphabetical order with an up arrow next to the Product text in the header row.

Clicking the Product link in the header row takes you to Products/Sortable?sortBy=ProductName&ascending=False
, with the products ordered in reverse alphabetical
order and with a down arrow now next to the Product text.

Clicking on other column headers sorts the grid as you'd expect.
Conclusion and Looking Forward...
This article showed how to use a view-specific Model, a sprinkle of Dynamic LINQ, and a partial view to display a sortable grid of data. At this point, the grid still shows all products. In the next installment we'll see how to implement paging so that only a subset of records are displayed at a time.
Until then... Happy Programming!
Attachments:
Further Reading