This article builds upon the code started in GridView and ObjectDataSource with custom objects.
An ObjectDataSource really only works with two types of data, DataTables and object that implement IEnumerable. If you pass it any other type of object, it is converted into an IEnumerable object. DataTables have sorting abilities built-in, custom objects are required to use the SortParameterName of the ObjectDataSource.
The idea is pretty simple, taking our ObjectDataSource tag from our previous version we add SortParameterName, and enable sorting on the GridView:
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
DataObjectTypeName="Person" TypeName="PersonAdapter"
DeleteMethod="DeletePerson" InsertMethod="InsertPerson"
SelectMethod="GetAll" UpdateMethod="UpdatePerson"
SortParameterName="sortExpression" />
<asp:GridView ID="GridView1" runat="server" AllowPaging="True"
DataKeyNames="Name" DataSourceID="ObjectDataSource1"
AutoGenerateDeleteButton="True" AutoGenerateEditButton="True"
AutoGenerateColumns="True" AllowSorting="true" />
Next, we add a method to our TableAdapter to handle this parameter:
public PersonList GetAll(String sortExpression) {
PersonList people = GetAll();
switch (sortExpression) {
case "Name":
case "Name ASC":
people.Sort(PersonComparer.CompareByName);
break;
case "Name DESC":
people.Sort(PersonComparer.CompareByNameDesc);
break;
case "Title":
case "Title ASC":
people.Sort(PersonComparer.CompareByTitle);
break;
case "Title DESC":
people.Sort(PersonComparer.CompareByTitleDesc);
break;
}
return people;
}
The SortParameterName property is passed a string of comma-separated field names, optionally followed by "ASC" or "DESC" to indicate direction. For this example, I'm not handling multiple field sorts. Also, notice I did not add an Attribute to this method as I did in the previous methods - this is because I do not want this method listed in the ObjectDataSource configure wizard, and the sortExpression will be passed in automatically anytime SortParameterName is set.
The PersonComparer class is a helper class I created to manage the sort logic. Since my PersonList is really a List<>, I can use the Sort method to do most the work, I just need to tell it how to compare objects. Here is the code for the class:
using System;
using System.Collections.Generic;
sealed class PersonComparer
{
private static IComparer<Person> _compareByName = new _sortName(false);
public static IComparer<Person> CompareByName { get { return _compareByName; } }
private static IComparer<Person> _compareByNameDesc = new _sortName(true);
public static IComparer<Person> CompareByNameDesc { get { return _compareByNameDesc; } }
private class _sortName : IComparer<Person> {
bool _reverse;
public _sortName(bool reverse) {
this._reverse = reverse;
}
#region IComparer<Person> Members
public int Compare(Person x, Person y) {
if (_reverse) return y.Name.CompareTo(x.Name);
else return x.Name.CompareTo(y.Name);
}
#endregion
}
private static IComparer<Person> _compareByTitle = new _sortTitle(false);
public static IComparer<Person> CompareByTitle { get { return _compareByTitle; } }
private static IComparer<Person> _compareByTitleDesc = new _sortTitle(true);
public static IComparer<Person> CompareByTitleDesc { get { return _compareByTitleDesc; } }
private class _sortTitle : IComparer<Person> {
bool _reverse;
public _sortTitle(bool reverse) {
this._reverse = reverse;
}
#region IComparer<Person> Members
public int Compare(Person x, Person y) {
if (_reverse) return y.Title.CompareTo(x.Title);
else return x.Title.CompareTo(y.Title);
}
#endregion
}
}
The class is sealed just to be on the safe side - no one should be deriving anything from this class. In fact, everything is static so there is no need to create an instance of the class to use it. The List.Sort method has an overload to take an object that supports IComparer<>, and this is used for the actual comparison. A boolean is used to control the sort direction, but to hide this implementation detail from the user a set of public static members are exposed.
If you think this is starting to add up to a good deal of code, well I agree. Some of this could be trimmed down using reflection, but I tend to shy away from reflection given it's reputation for performance problems. If I'm working with a small set of fields (like managing role providers), this is my preferred method, but when it's time to scale up there is another option: DataTables. DataTables aren't tied down to XSD's and TableAdapters, they are free to date other people, and in the next article (in what is quickly becoming a series) we will look at moving this code from a List generic to a DataTable.
Update: If you are curious to see another approach, Dylan has started a set of posts on GridView sorting using reflection. Well worth the read.