Introduction
The GridView
control from ASP.NET 2.0 such as this one is great and widely used:
<asp:GridView runat="server" ID="gridPersons"
AutoGenerateColumns="false" AllowSorting="true"
OnSorting="gridPersons_Sorting">
<Columns>
<asp:BoundField HeaderText="First name"
DataField="FirstName" SortExpression="FirstName" />
<asp:BoundField HeaderText="Last name"
DataField="LastName" SortExpression="LastName" />
</Columns>
</asp:GridView>
protected void gridPersons_Sorting(object sender, GridViewSortEventArgs e)
{
string sortExpression = e.SortExpression;
}
But has one significant limitation: sorting event argument GridViewSortEventArg
is based on string values from markup. So it can't be directly used in programmatic sorting using LINQ,
This article describes how to convert a string argument into the sorting expression.
Background
Let's assume we have a sequence of strongly typed class, say Person
, and we want to bind it to a GridView
and then sort it by clicking on the appropriate column:
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
IEnumerable<Person> persons = GetPersons();
gridPersons.DataSource = persons.ToArray();
gridPersons.DataBind();
Generally speaking, we could hard-code both the sorting expression (i.e., property to sort by) and the direction (ascending, descending):
protected void gridPersons_Sorting(object sender, GridViewSortEventArgs e)
{
Func<Person, object> prop = null;
switch (e.SortExpression)
{
case "FirstName":
{
prop = p => p.FirstName;
break;
}
case "LastName":
{
prop = p => p.LastName;
break;
}
}
Func<IEnumerable<Person>, Func<Person, object>, IEnumerable<Person>> func = null;
switch (e.SortDirection)
{
case SortDirection.Ascending:
{
func = (c, p) => c.OrderBy(p);
break;
}
case SortDirection.Descending:
{
func = (c, p) => c.OrderByDescending(p);
break;
}
}
IEnumerable<Person> persons = GetPersons();
persons = func(persons, prop);
gridPersons.DataSource = persons.ToArray();
gridPersons.DataBind();
}
But such approach contains a number of disadvantages: all the object's properties are hard-coded, as well as the sorting directions. Much better would be to create all these dynamically.
Solution
Let's encapsulate the logic into a number of dedicated classes.
The first one converts SortDirection
to Func<IEnumerable<T>, Func<T, object>, IEnumerable<T>>
which is a function accepting a sequence and another function accepting object and returning one of its properties; and returning that sequence sorted by that object's property.
public static class SortExpressionConverter<T>
{
private static IDictionary<SortDirection, ISortExpression> expressions =
new Dictionary<SortDirection, ISortExpression>
{
{ SortDirection.Ascending, new OrderByAscendingSortExpression() },
{ SortDirection.Descending, new OrderByDescendingSortExpression() }
};
interface ISortExpression
{
Func<IEnumerable<T>, Func<T, object>, IEnumerable<T>> GetExpression();
}
class OrderByAscendingSortExpression : ISortExpression
{
public Func<IEnumerable<T>, Func<T, object>, IEnumerable<T>> GetExpression()
{
return (c, f) => c.OrderBy(f);
}
}
class OrderByDescendingSortExpression : ISortExpression
{
public Func<IEnumerable<T>, Func<T, object>, IEnumerable<T>> GetExpression()
{
return (c, f) => c.OrderByDescending(f);
}
}
public static Func<IEnumerable<T>, Func<T, object>, IEnumerable<T>>
Convert(SortDirection direction)
{
ISortExpression sortExpression = expressions[direction];
return sortExpression.GetExpression();
}
}
The second one builds a lambda expression Expression<Func<T, object>>
and returns underlying lambda Func<T, object>
:
public static class SortLambdaBuilder<T>
{
public static Func<T, object> Build(string columnName, SortDirection direction)
{
ParameterExpression param = Expression.Parameter(typeof(T), "x");
Expression property = columnName.Split('.')
.Aggregate<string, Expression>
(param, (c, m) => Expression.Property(c, m));
Expression<Func<T, object>> lambda = Expression.Lambda<Func<T, object>>(
Expression.Convert(property, typeof(object)),
param);
Func<T, object> func = lambda.Compile();
return func;
}
}
Wrap how these classes are used into an extension method:
public static class CollectionExtensions
{
public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> collection,
string columnName, SortDirection direction)
{
Func<IEnumerable<T>, Func<T, object>, IEnumerable<T>> expression =
SortExpressionConverter<T>.Convert(direction);
Func<T, object> lambda =
SortLambdaBuilder<T>.Build(columnName, direction);
IEnumerable<T> sorted = expression(collection, lambda);
return sorted;
}
}
So now we have a flexible, fully dynamic and generic solution:
protected void gridPersons_Sorting(object sender, GridViewSortEventArgs e)
{
IEnumerable<Person> persons = GetPersons();
persons = persons.OrderBy(e.SortExpression, e.SortDirection);
gridPersons.DataSource = persons.ToArray();
gridPersons.DataBind();
}
Points of Interest
Here are some discussions on StackOverflow related to the subject:
History of Changes
- 27/08/2011 - Initial publication
- 22/03/2012 -
OrderBy<T>()
extension method expanded to make it easier to read and understand - 17/07/2014 -
SortLambdaBuilder<T>
supports nested properties