Sorting a GridView Using ObjectDataSource, custom classes, and reflection, part 1

April 18, 2007

First off, I wrote this post because Mike just posted a series of articles on the same subject: GridView and ObjectDataSource with custom objects and Sorting a GridView with custom objects. He went about it a different way, namely not using reflection, which is obviously a lot better for performance. It was interesting because this was one of my earlier challenges when using ASP.Net.

As I’ve mentioned before, I had to figure out early on the cleanest way of populating a GridView, and my choices were down to using TableAdapters/DataTables and business objects. While writing your own classes seems (to me, at least) to be the nicer way of structuring your program–if done right, it forces you to put all of the rules for handling data in its own tier–it gets a bit ugly when you have to hook up your custom classes to some of .NET’s built-in controls. Specifically, it starts getting to be a hassle once you realize that all the automagic goodness of GridView is a product of using the DataTable, and that sorting a list of objects on one member requires writing a whole new custom class per member. Maybe it’s the two and a half years of Python programming talking, but it seemed like there had to be a better way.

So my solution was to extend the GridView class to handle sorting specifically for ObjectDataSources that provide lists. It’s a bit inflexible and probably not coded as well as it could be, but for my uses it’s just fine. I don’t think it’s the solution, but it is a solution. I also don’t claim to be an expert with .NET reflection, so I’m sure there’s a lot of runtime errors just waiting to happen if this code were used for something really complex. (Although, again, having used Python for so long, I tend to take the concept of reflection for granted.)

So, as part 1, here’s the heart of the ODSSortableGridView, the SortableComparer class:

using System;
using System.Collections;
using System.Reflection;

namespace ODSSortableGridView
{

    /// <summary>
    /// An implementation of IComparer that allows for dynamic sorting of ArrayLists 
    /// </summary>
    public class SortableComparer : IComparer
    {

        private string fieldName;
        private bool reverseSort;

        /// <summary>
        /// Creates a SortableComparer for the specified field and sorting direction.
        /// </summary>
        /// <param name="field"></param>
        /// <param name="reverse"></param>
        public SortableComparer(string field, bool reverse)
        {
            fieldName = field;
            reverseSort = reverse;
        }

        /// <summary>
        /// Returns a value from an object based on a property name.
        /// </summary>
        /// <param name="obj">The source object.</param>
        /// <param name="propReference">The name of the property (or series of properties separated by periods) to return.</param>
        /// <returns>If the reference is valid, the value is returned; otherwise, null is returned.</returns>
        protected object GetPropertyReference(object obj, string propReference)
        {
            object currentValue = obj;
            string[] props = propReference.Split('.');
            foreach (string propName in props)
            {
                try
                {
                    Type objectType = currentValue.GetType();
                    currentValue = objectType.InvokeMember(propName, BindingFlags.GetProperty | BindingFlags.GetField, null, currentValue, new object[0]);
                } catch (Exception exc) {
                    return null;
                }
            }
            return currentValue;
        }

        /// <summary>
        /// Compares two objects based on the field and sort order specified for this object. The
        /// objects can be of different types, but require the specified fieldName to exist as
        /// a property to function correctly.
        /// </summary>
        /// <param name="a">An object to compare.</param>
        /// <param name="b">An object to compare.</param>
        /// <returns>Returns the result of CompareTo from comparing the two object's properties.</returns>
        public int Compare(object a, object b)
        {
            int returnValue = 0;

            object valueA = GetPropertyReference(a, fieldName);
            object valueB = GetPropertyReference(b, fieldName);

            Type valueTypeA = (valueA != null) ? valueA.GetType() : null;
            Type valueTypeB = (valueB != null) ? valueB.GetType() : null;

            // If both values are null, we can't tell one way or the other how to sort
            if (valueA == null && valueB == null) { returnValue = 0; }
            // If one value is null, sort it down the list
            else if (valueA == null) { returnValue = 1; }
            else if (valueB == null) { returnValue = -1; }
            // If the values are of different types, then we can't sort them
            else if (valueTypeA != valueTypeB && !valueTypeA.IsSubclassOf(valueTypeB) && !valueTypeB.IsSubclassOf(valueTypeA)) { returnValue = 0; }
            // If the values are comparable, then return the results of their CompareTo method
            else if (valueA is IComparable && valueB is IComparable)
            {
                returnValue = (int)valueTypeA.InvokeMember("CompareTo", BindingFlags.InvokeMethod,
                                                            null, valueA, new object[] { valueB });
            }
            // Otherwise, we'll end up returning 0, which means no sorting will take place

            if (reverseSort) { returnValue = returnValue * -1; }
            return returnValue;
        }
    }

}

As it implements IComparer, this class can be passed to the ArrayList Sort method. The Sort method will then use it to determine how objects in the list are compared and thus sorted, based on the sort expression and sort direction provided in the constructor. The sort expression can be the name of any property in the class. In fact, thanks to the logic in GetPropertyReference, it can be a sort of “path” to a property somewhere in a hierarchy of objects. For example, you could pass in CurrentRevision.PageTitle, and it would find the CurrentRevision property of each object, then find that object’s PageTitle property. If the sort expression doesn’t actually resolve to anything, then it returns null.

And while it can technically be used to compare any two objects provided they have a public property of the same name, obviously that’s not going to work for every object. In general, if the sort expression doesn’t apply to one of the objects, that object gets sorted down in the list. If it doesn’t apply to either object, then the objects stay in their original order. If the sort expression returns objects of two different types or objects that can’t be compared, then it leaves those two objects in their original order as well.

So, part two, which I’ll try to post tomorrow, will cover how to actually use this class to extend a GridView control bound to an ObjectDataSource to use this comparer automatically. In fact, it’s easy enough to compile the control to a DLL and then drop it in whatever web project you’re working on.

And, again, I don’t claim this is the best solution around. Reflection is tricky because you don’t get the benefit of strong typing to alert you to any mistakes at compile time. So, all this code is very much open to comments/suggestions.

Also, as an aside, I wanted to get the nice Visual Studio-style syntax highlighting on code displayed on my blog, but don’t use Windows Live Writer as Mike suggested. However, I found a C# Code Formatter that will do it for you.

EDIT: Part 2 is now up.

×