web 2.0

Sorting Data in ASP.NET MVC

In the old days of WebForm development we took for granted all the little things that went on behind the scenes. For example, when you put a DataGridView on a WebForm you could set a few properties, and BOOM, you had sorting, paging and the works.

MVC has kind of brought web development back to its roots. This means that you have to do a little more work to get features like paging and sorting implemented. In order to simplify the sorting problem I made my own extension method using the HtmlHelper class to render the column headers. My code creates the links for the column headers and draws the sort indicators (without using any images!). The method utilizes two hard-coded variable names which are sidx and sord. The sidx variable is the name of the column to be sorted and the sord variable is the sort order (ascending or descending).

public static string SortableColumn(this HtmlHelper htmlHelper, string linkText, string columnName, object routeValues) {
//automatically determine the current action
System.Web.Routing.RouteData data = htmlHelper.ViewContext.Controller.ControllerContext.RouteData;
string actionName = data.GetRequiredString("action");

StringBuilder sb = new StringBuilder();
var vals = new RouteValueDictionary(routeValues);

string sidx = String.Empty;
if (System.Web.HttpContext.Current.Request["sidx"] != null) {
sidx = System.Web.HttpContext.Current.Request["sidx"].ToString();
}

//modify the sidx
if (vals.ContainsKey("sidx") == false) {
vals.Add("sidx", columnName);
}
else {
vals["sidx"] = columnName;
}

//get the sort order from the request variable if it exists
string sord = String.Empty;
if (System.Web.HttpContext.Current.Request["sord"] != null) {
sord = System.Web.HttpContext.Current.Request["sord"].ToString();
}

//add the sord key if needed
if (vals.ContainsKey("sord") == false) {
vals.Add("sord", String.Empty );
}

//if column matches
if (sidx.Equals(columnName, StringComparison.CurrentCultureIgnoreCase) == true) {
if (sord.Equals("asc", StringComparison.CurrentCultureIgnoreCase) == true) {
//draw the ascending sort indicator using the wingdings font.
sb.Append(" <font face='Wingdings 3'>&#112;</font>");
vals["sord"] = "desc";
}
else {
sb.Append(" <font face='Wingdings 3'>&#113;</font>");
vals["sord"] = "asc";
}
}
else {
//if the column does not match then force the next sort to ascending order
vals["sord"] = "asc";
}

//Use the ActionLink to build the link and insert it into the string
sb.Insert(0,System.Web.Mvc.Html.LinkExtensions.ActionLink(htmlHelper, linkText, actionName, vals));
return sb.ToString();
}

Next we need to utilize the method in the markup. The "helper" method really cleans up the code and is very simple to understand and use. Once the header is rendered, and you mouse over the link you should see a href that looks something like this:

/Instance/Assignments?dbaid=109&sidx=SecondaryDBA&sord=desc

<% object routeValues = new { dbaid = Request["dbaid"] }; %>

...

<table id="grid">
<tr>
<th><%=Html.SortableColumn("Instance", "InstanceName", routeValues )%></th>
<th><%=Html.SortableColumn("Primary DBA", "PrimaryDBA", routeValues )%></th>
<th><%=Html.SortableColumn("Secondary DBA", "SecondaryDBA", routeValues)%></th>
<th><%=Html.SortableColumn("Version", "Version", routeValues)%></th>
<th><%=Html.SortableColumn("RDBMS", "RDBMSTypeCD", routeValues)%></th>
<th><%=Html.SortableColumn("Bus. Group", "BusinessGroupCD", routeValues)%></th>
</tr>
<% foreach (var item in Model) { %>
<tr>
<td><%= Html.Encode(item.InstanceName) %></td>
<td><%= Html.Encode(item.PrimaryDBA) %></td>
<td><%= Html.Encode(item.SecondaryDBA) %></td>
<td><%= Html.Encode(item.Version) %></td>
<td><%= Html.Encode(item.RDBMSTypeCD) %></td>
<td><%= Html.Encode(item.BusinessGroupCD) %></td>
</tr>
<% } %>
</table>

Most importantly, the method in your controller that populates the grid must utilize the variables sidx (column to be sorted) and sord (sort order). Without this, you will not get any sorting...

public ActionResult Assignments( int? page, int? dbaid, string sidx, string sord ) {
try {
var query = InstanceRepository.GetAssignments(dbaid);

//sort the data
string sortExpression = (sidx ?? "InstanceName") + " " + (sord ?? "asc");
query = query.OrderBy(sortExpression);

//page the data
return View(query.ToPagedList(page ?? 1, MySession.PageSize));
}
catch {
return View("Error");
}
}

The data is sorted with the help of the Dynamic Linq library. I originally discovered this library after reading one of Scott Gu's posts. I also utilized the famous PagedList class to add pagination to the grid. Finally, here is the result:

Comments

rlovesg India, on 1/5/2010 8:17:17 AM Said:

rlovesg

Article is good but couldn't understand the code in the controller action method. Please provide more information regarding the code in the action method.

mikeceranski United States, on 1/5/2010 8:56:26 AM Said:

mikeceranski

The important thing in the action is that the sort is applied using the Dynamic Linq library. So if you have a method that returns an IQueryable list you can apply a sort to it using the OrderBy method of the Dynamic Linq library. The sort expression is past as parameters to the action via the query string.

Does this clarify it?

rlovesg India, on 1/6/2010 2:08:56 AM Said:

rlovesg

Yes. I was clear now. It was good explanation Mike. Thank you.

DotNetKicks.com , on 1/7/2010 11:23:26 AM Said:

trackback

Sorting Data in ASP.NET MVC

You've been kicked (a good thing) - Trackback from DotNetKicks.com

adriano Australia, on 1/10/2010 9:56:33 PM Said:

adriano

I think there's a mistake for the input parameter which is sortExpression that is passed into the OrderBy extension method of the query variable int in the Assignments action method. The order by method takes in a func or lambda expression as a input parameter. You seem to be passing in a string type instead. Correct me if I am wrong.

Michael Ceranski United States, on 1/10/2010 10:42:22 PM Said:

Michael Ceranski

This is working code. I am using it in an application. The sort order and the sort field are being passed as string variables on the query url and I am applying them by concatenating a string on the server side and returning the results.

adriano Australia, on 1/10/2010 11:06:01 PM Said:

adriano

Ok what you said made sense. Is your orderby method a custom extension method that you have made on your own then? I am guessing you are doing some substrings to extract out values from sidx and sord and then use those values in a switch of if statement to order the results by the appropriate property name on the object collection? If it is a custom extension method, may you be generous to reveal what you have done to extract the values out manually?

Michael Ceranski United States, on 1/11/2010 8:05:26 AM Said:

Michael Ceranski

There is no switch statement or underlying magic. The Dynamic Linq library handles all of this for me: msdn.microsoft.com/en-us/vcsharp/bb894665.aspx

I pass the sidx and sord variables on the query string so when you mouse over a column header you see the following URL: http://someurl/Index?sidx=Column&sord=asc

Then I just read the Request variables and apply them to the IQueryable list that was returned from my LINQ query. The Dynamic LINQ library essentially modifies the existing SQL query and applies the order by to the results.

adriano Australia, on 1/11/2010 5:52:16 PM Said:

adriano

Yes I have got it working already. Thanks for reminding me to look into those csharp samples. There are extension methods to the OrderBy and OrderByDescending method which takes in an IQueryable source and a property name of a string type, in which linq can apply the appropriate sorting mechanism using some reflection. Thanks Mike!

sfuelberth United States, on 1/13/2010 8:00:56 PM Said:

sfuelberth

I love your solution.  It seems that it would be even better if you get the existing column names and sort orders from the route data as opposed to going to the request parameters.

Code Capers , on 4/19/2010 12:35:46 AM Said:

trackback

Advice for Aspiring ASP.NET Web Developers

Advice for Aspiring ASP.NET Web Developers

Comments are closed