# Thursday, June 26, 2008

Binding to a GridView

ASP.Net data binding is a great technology for developers who want to create interactive web applications without writing a lot of code. For example, a developer can drop a GridView and a DataObject onto a web form, set a few properties and have a fully editable grid, allowing users to easily view and update data. 

Even sorting the grid data can be performed without writing any code.  Just set the GridView's AllowSorting property to "true" and set the SortExpression property of any "sortable" column. 

<asp:GridView
   ID="GridView1"
   runat="server"
   AllowSorting="true"

...

   <asp:BoundField
      DataField="Name"
      HeaderText="Product Name"
      SortExpression="Name" />

...

By default, the grid renders a link at the top of each sortable column.  Clicking this link toggles the sort order between 3 states: Ascending, Descending and No Sort. 


Sorting Limitations

But there is a "Gotcha".  The GridView can be bound to any set of data - that is, to any object that implements the System.Collections.IEnumerable interface.  This includes a DataTable, ArrayList, List and HashTable, among others.  However the automatic sorting only works when binding to a DataTable.

Personally, I prefer to work with and bind to generic Lists.  A List is more flexible than a DataTable and does a better job of abstracting the user interface from the back-end data source, making it easier to swap out one or the other.

But by binding to a List, I sacrifice the automatic sorting that happens when I bind to a DataTable.  Fortunately, it doesn't take a lot of code to implement sorting on a GridView bound to a generic List.

How to sort a GridView bound to a List

First, we set the GridView AllowSorting property and each column's SortExpression property as described above.  This provides "sort" links at the top of each column.  Unfortunately these links will not work properly - in fact, clicking them will generate an error.

To get this to work, you must do the following

  1. Add an extra "sortby" parameter of type string to your Select method.
  2. Add code to your sort method to sort the list before returning it (more on this later)
  3. Add a SortParameterName attribute to your ObjectDataSource.  The value of this parameter should be the same as the parameter you added to your Select method.

By setting the SortParameterName attribute, we are telling ASP.Net to pass sorting information to the Select method and which parameter to pass it to.  The Select method gets called when the grid loads or refreshes and whenever the user clicks the "sort" links at the top of each column.  Most of the time, the value passed to the sortby parameter is an empty string (indicating no sort order), but if the user clicks a "sort" link, this parameter will contain one of the following three values

  • "<SortField> ASC"
  • "<SortField> DESC"
  • ""

where <SortField> is the string specified in the SortExpression attribute of the column clicked.  With each click, the grid column cycles its sort between ascending order, descending order, and no sort.  The parameter value passed depends on which is currently the active sort. 


<asp:ObjectDataSource ID="ProductObjectDataSource" runat="server" 
      SelectMethod="GetProductsList"
      TypeName="DemoGridBusLayer.NWindBL"
      SortParameterName="sortBy">
</asp:ObjectDataSource>

Now, how do we sort a List?  More specifically, how do we sort a list when we don't know in advance on which column we are sorting or in which direction (ascending or descending)? 

A List has a Sort method, so we can call that.  But what does it mean to sort a list of objects?  An object has properties and we can sort on any one (or more) of those properties, as long as the property is of a type that can be sorted.  We need to specify on which object property we will be sorting.  To do this, we use an overload of the List.Sort method that accepts an IComparer object.  IComparer has a Compare method that tells the Sort method how to order each pair of objects in the list.  We can create a class that implements IComparer, override the Compare method and use reflection to determine at runtime on which property to sort.  The name of the property and the sort order (Ascending or Descending) can be passed into the class constructor. 

The code for this class (named GenericComparer) below:

using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.Reflection;

namespace DemoGridBusLayer
{
    /// <summary>
    /// This class is used to compare any
    /// type(property) of a class for sorting.
    /// This class automatically fetches the
    /// type of the property and compares.
    /// </summary>
    public sealed class GenericComparer<T> : IComparer<T>
    {
        public enum SortOrder { Ascending, Descending };

        #region Constructors
        public GenericComparer(string sortColumn, SortOrder sortingOrder)
        {
            this._sortColumn = sortColumn;
            this._sortingOrder = sortingOrder;
        }

        /// <summary>
        /// Constructor when passing in a sort expression containing both the Sort Column and the Sort Order
        /// e.g., "BPCode ASC".
        /// </summary>
        /// <param name="sortExpression"></param>
        /// <remarks>
        /// This constructor is useful when using this with the ASP.NET ObjectDataSource,
        /// which passes the SortParameterName in this format
        /// </remarks>
        public GenericComparer(string sortExpression)
        {
            string[] sortExprArray = sortExpression.Split(" ".ToCharArray());
            string sortColumn = sortExprArray[0];
            SortOrder sortingOrder;
            sortingOrder = SortOrder.Ascending;
            if (sortExprArray.Length > 1)
            {
                if (sortExprArray[1].ToUpper() == "DESC")
                {
                    sortingOrder = SortOrder.Descending;
                }
            }

            this._sortColumn = sortColumn;
            this._sortingOrder = sortingOrder;
        }

        #endregion



        #region public properties
        /// <summary>
        /// Column Name(public property of the class) to be sorted.
        /// </summary>
        private string _sortColumn;
        public string SortColumn
        {
            get { return _sortColumn; }
        }

        /// <summary>
        /// Sorting order (ASC OR DESC)
        /// </summary>
        private SortOrder _sortingOrder;
        public SortOrder SortingOrder
        {
            get { return _sortingOrder; }
        }

        #endregion

        /// <summary>
        /// Compare two objects of the same class,
        /// based on the value of a given property
        /// </summary>
        /// <param name="x">First Object</param>
        /// <param name="y">Second Object</param>
        /// <returns>int</returns>
        public int Compare(T x, T y)
        {

            // User reflection to get the property
            PropertyInfo propertyInfo = typeof(T).GetProperty(_sortColumn);

            // Cast the property to IComparable, so we can use the built-in compare.
            IComparable obj1 = (IComparable)propertyInfo.GetValue(x, null);
            IComparable obj2 = (IComparable)propertyInfo.GetValue(y, null);

            // Order depends on Asc vs Desc.
            if (_sortingOrder == SortOrder.Ascending)
            {
                return (obj1.CompareTo(obj2));
            }
            else
            {
                return (obj2.CompareTo(obj1));
            }
        }
    }
}


Within our select method, we can call the List's Sort method and pass in an instance of the GenericComparer class, specifying on which column and in which direction to sort the list.  The SelectMethod is shown below.  (The details of querying a database and storing results into a List of objects is omitted.)

        /// <summary>
        /// Get a sorted list of all products
        /// </summary>
        /// <param name="sortBy"></param>
        /// <returns></returns>
        public static List<Product> GetProductsList(string sortBy)
        {
            // Get a list of Business Processes
            List<Product> prodList = GetProductsList();

            // Sort list
            if (sortBy != "")
            {
                GenericComparer<Product> cmp = new GenericComparer<Product>(sortBy);
                prodList.Sort(cmp);
            }
            return prodList;

        }

    }
}

With a small amount of code, we can enable sorting of a List of objects bound to a GridView in the same way that sorting is enabled for a DataTable bound to a GridView.

Demo

You can download the entire sample described in this article here: DemoGridSort.zip (39.63 KB).  You will need the AdventureWorks sample SQL Server database for this demo.

Thursday, June 26, 2008 5:05:33 PM (GMT Daylight Time, UTC+01:00)
# Tuesday, June 24, 2008

Did you know that every time an Ajax control performs a partial postpack, every event in the life cycle of the control's containing page or pages fires?

To me, this seems counterintuitive - There is no refresh of the containing page or of the master page, yet the Page_Load of both events fire.

I ran into it when I witnessed some unexpected behavior in a colleague's Ajax control.  After some investigation I saw the behavior was caused by code in the Page_Load event handler.  I thought this was a bug until I learned it was by design.  So we ended up bracketing some of the Page_Load code, testing the value of Page.IsPostBack to prevent code from running when it should not.

Tuesday, June 24, 2008 3:51:57 PM (GMT Daylight Time, UTC+01:00)
# Sunday, June 22, 2008

I had a terrific time yesterday at the Lansing Day of .Net yesterday. 

This was the last in an ambitious string of community-sponsored events in Michigan and Ohio under the "Day of .Net" branding.  In the past two months, DODN events have been held in Wilmington, OH, Grand Rapids, MI, Cleveland, OH, and Lansing, MI.  I managed to make the two Michigan events but family commitments kept me from the ones in Ohio.

A Day of .Net event features numerous speakers (usually about 30) speaking on topics related to software development.  The primary focus is .Net development but peripheral topics are almost always included.  I heard a very good talk yesterday by Dan Rigsby on the agile methodology in which software was barely mentioned.

Prior to yesterday, I wondered if the Lansing event might be anticlimactic coming so soon after three similar events.  I worried for nothing.  In fact, the opposite was true.  They managed to attract an excellent group of speakers, a full slate of sponsors (meaning, among other things, many cool door prizes) one of the better facilities I've seen (Lansing Community College West Campus) and the mayor of Lansing.  People were generally excited about this event.  I've heard - but can't confirm - that the Day of .Net was covered by two TV stations.  Jeff McWhirter and his group did a great job putting this together.  I don't know who thought of inviting the mayor, but that was a good idea.

The best part of these events is interacting with the people in the community.  There was a lot of good discussions about various projects, the state of the industry, the role of the community and the various approaches to developing software. 

When it was over, many of us headed over to Jeff's house to celebrate into the night.  I left at around 11 and the place was still packed and the bonfire was blazing.  Mike Wood, an old friend from my Cincinnati days stayed at my house before heading home this morning.

I picked up some nice swag - a copy of Camtasia, a logo t-shirt, and a pint glass featuring the LDODN logo.  This morning, I noticed that the t-shirt includes the slogan "I was there" but the pint glass has a modified slogan "I think I was there".

Here are some photos of the day: Photos.

 

>Lansing Day of .Net, 21 June 2008 - I'll be there!
Sunday, June 22, 2008 5:13:40 PM (GMT Daylight Time, UTC+01:00)
# Tuesday, June 17, 2008

I've heard people suggest that developing web applications is easier than building Windows applications.  Common sense tells us that this is not true - The tools for Windows applications are far more powerful and give us access to the entire Windows API as well as APIs that access the Internet).  In addition, we typically don't need to deal with messy things like viewstate and session state when writing Windows apps.  Because Windows apps tend to be stateful, this stuff is far easier to design and code.

What most people mean when they say that Windows apps are easier is that user's expectations tend to be lower for web application.  Most users understand the limitations of building a web app and will not be as demanding when requesting features.

However the tools available are changing that.  With tools like Silverlight and Flash, we can now build web applications that have a look and feel similar to fat clients.  And the advances in databinding controls make it far easier to manage state in a web app than just a few years ago.

As a result, user expectations of web applications are increasing.  Why shouldn't they demand the same rich user experience they get from their desktop apps? 

Take a look at the Cooper Mini site (http://www.miniusa.com) where you can find a dealer or build a cooper with the options you want. 
This site was built using Flash.

Or view the memorabilia collection at HardRock.com (http://memorabilia.hardrock.com/) where the developers used Silverlight to provide a very cool way of viewing the collection.

The User interface of each of the above sites exceeds that of most Windows applications.

Each quarter, the line blurs more between web applications and desktop applications.  Soon users will no longer temper their expectations based on the platform on which you develop.

Will you be ready for these increased expectations?

Tuesday, June 17, 2008 2:26:34 PM (GMT Daylight Time, UTC+01:00)
# Sunday, June 15, 2008
 #
 

Today is Father's Day here in America.  I spent the day with my two sons.  We went to church, then to lunch, then back home, where my sons hung out with their friends and I stayed in the background, content in knowing that they are safe and happy.

I look back on the difficulties of my life the last few years - an unwelcome separation, move, and divorce - and I realize that the one good, stabilizing thing in my life is the love of my boys.  I don't know how I would have made it without them.  I try to give them as much as I can and I hope that someday they recognize that.  But I know that they have given to me something that I could never receive from any other source - a reason to keep going; a reason to believe; a reason to hope for a better future. 

I know my boys will never doubt the fact that I love them with all my heart; but I doubt they realize the positive impact they have just by being in my life.  I've tried to communicate this but words fall short.

Nick and Timmy, if you are reading this, I love you more each day.

And to all the fathers out there, I wish for you the ability to express to your children the love you feel.  May they never spend one second doubting that their father loves them.

Sunday, June 15, 2008 11:52:27 AM (GMT Daylight Time, UTC+01:00)