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

April 27, 2007

I really have no excuse as to why it took me so long to write part 2. Well, I have things I can use as an excuse, but it comes down to the fact that I’m basically lazy.

Anyway, in part 1, I covered how to create an IComparer class that uses reflection to sort a list of objects based on a specified property name. While that’s the underlying mechanics that powers our GridView sorting solution, we still need a convenient way to hook it up to a GridView. And so, I extended the GridView class to create the ODSSortableGridView class that does just this.

Here’s the code:

using System;
using System.Collections;
using System.ComponentModel;
using System.Web;
using System.Web.UI.WebControls;

namespace ODSSortableGridView
{
    ///<summary>
    /// Extends GridView to allow dynamic sorting of GridViews that use IList ObjectDataSources.
    ///</summary>
    public class ODSSortableGridView : GridView
    {
        #region Properties
        private string defaultSortExpression;
        private bool defaultSortReverse = false;

        [
        Description("Sets the default sort expression for this GridView."),
        Category("Behavior"),
        DefaultValue(null)
        ]
        public string DefaultSortExpression
        {
            get { return defaultSortExpression; }
            set { defaultSortExpression = value; }
        }

        [
        Description("Sets whether the sort direction should be reversed for the default field in this GridView."),
        Category("Behavior"),
        DefaultValue(false)
        ]
        public bool DefaultSortReverse
        {
            get { return defaultSortReverse; }
            set { defaultSortReverse = value; }
        }
        #endregion

        #region ViewState
        /// <summary>
        /// Returns the current sort expression for this GridView saved in the ViewState.
        /// </summary>
        public string odsSortExpression
        {
            get { return (ViewState["sortExpression"] != null) ? (string)ViewState["sortExpression"] : null; }
            set { ViewState["sortExpression"] = value; }
        }

        /// <summary>
        /// Returns the current sort direction (reversed or not) for this GridView saved in the ViewState.
        /// </summary>
        public bool odsSortReverse
        {
            get { return (ViewState["sortReverse"] != null) ? (bool)ViewState["sortReverse"] : false; }
            set { ViewState["sortReverse"] = value; }
        }
        #endregion

        #region ObjectDataSource Interaction
        public void DataSource_Selected(object sender, ObjectDataSourceStatusEventArgs e)
        {
            // If no sorting specified, try defaults
            if (String.IsNullOrEmpty(odsSortExpression))
            {
                odsSortExpression = DefaultSortExpression;
                odsSortReverse = DefaultSortReverse;
            }

            // If sorting is specified or defaults are specified, sort the list returned by
            // the Select operation in place.
            if (!String.IsNullOrEmpty(odsSortExpression))
            {
                SortableComparer s = new SortableComparer(odsSortExpression, odsSortReverse);
                ArrayList.Adapter((IList)e.ReturnValue).Sort(s);
            }
            return;
        }

        /// <summary>
        /// Get the DataSource object referenced by this object.
        /// </summary>
        /// <returns>The ObjectDataSource used by this GridView.</returns>
        private ObjectDataSource GetDataSource()
        {
            if (this.DataSource != null)
            {
                return (ObjectDataSource)this.DataSource;
            }
            if (this.DataSourceID != null && this.Parent.FindControl(this.DataSourceID) != null)
            {
                return (ObjectDataSource)this.Parent.FindControl(this.DataSourceID);
            }
            return null;
        }

        private void AttachDataSourceEvent()
        {
            ObjectDataSource ds = GetDataSource();
            if (ds != null) { ds.Selected += new ObjectDataSourceStatusEventHandler(this.DataSource_Selected); }
        }
        #endregion

        #region Life Cycle
        protected override void OnSorting(GridViewSortEventArgs e)
        {
            // If the sort expression is the same as the current one, then reverse the sort order.
            if (odsSortExpression == e.SortExpression)
            {
                odsSortReverse = !odsSortReverse;
            }
            // Otherwise, use ascending sort order, and move to the first page.
            else
            {
                odsSortReverse = false;
                this.PageIndex = 0;
            }
            odsSortExpression = e.SortExpression;
            this.DataBind();
            e.Cancel = true;
        }

        protected override void OnLoad(EventArgs e)
        {
            AttachDataSourceEvent();
            base.OnLoad(e);
        }
        #endregion

    }
   
}

Now, this is where the cracks in the solution definitely begin to form: I’m much stronger in C# than I am in ASP.Net. I’m sure there’s things that I’m doing here that could be better done with the built-in GridView functionality.

That aside, let’s look at how it works. The big picture view of what’s happening here is this: every time the ObjectDataSource bound to this control returns a list, we catch it before it’s actually bound, and then we sort it using our SortableComparer function.

Let’s take a look at how this works. When the control first loads, we get its data source (whether it’s specified by the DataSource property or the DataSourceID). Then, we add a handler for the ObjectDataSource’s Selected event.

When data is bound to the control, it calls the ObjectDataSource’s Select method. This method returns a list and then fires the Selected event, calling the DataSource_Selection event handler that we’ve just added.

DataSource_Selection gets the list that’s returned by the ObjectDataSource. Then, it takes the current sort expression and sort direction and sorts the list using the SortableComparer. Since we’re actually modifying the object that is returned to the GridView, our sorting is reflected when the data is actually bound.

The mechanics of changing sort order are fairly simple. We’re using the ViewState to store our current sort expression and sort direction. Whenever the GridView’s OnSorting event is fired, we take whatever SortExpression is provided and store it in the ViewState. If it’s the same as the current SortExpression, we just flip the sort order around. It’s also possible to provide a default sort expression and sort order using the DefaultSortExpression and DefaultSortReverse properties.

If you combine the ODSSortableGridView and SortableComparer into a single project, it’s possible to compile them into a DLL which you can then reference in your project. Usage is identical to the usage of a GridView except, of course, you can only use ObjectDataSources with this new GridView class.

I have run into one problem with this control: while it works just like a GridView, Intellisense doesn’t provide a list of tags that are valid inside the <asp:ODSSortableGridView> tag like it does for the <asp:GridView> tag. My workaround for this has just been to create the GridView as an <asp:GridView> tag and convert it into an ODSSortableGridView when I’m done.

×