Geeks With Blogs
Carl Zumbano
The Object Data Source control is one of the best new features of ASP.NET 2.0. The ability to bind collections of Business Objects saves time and produces more elegant, less code-intensive solutions. Unfortunately, if your Business Objects contain child classes binding to Data Controls becomes more difficult. This article demonstrates a technique for sorting a GridView bound to a collection of Objects with a child class. You can download the complete solution here.
I created two objects for this example, Employee and Person. The Employee class contains a Person child class. XMLElement properties are defined because the project uses XML Serialization for persistance.
Figure 1: Person Class
    public class Person
    {
        #region Properties

        
private string _firstName;

        [
XmlElement("FirstName")]
        
public string FirstName
        {
            
get
            {
                
return _firstName;
            }
            
set
            {
                _firstName =
value;
            }
        }

        
private string _lastName;

        [
XmlElement("LastName")]
        
public string LastName
        {
            
get { return _lastName; }
            
set { _lastName = value; }
        }

        #endregion
    }
Figure 2: Employee Class
    [XmlRoot("Employee")]
    
public class Employee
    {
        #region Properties

        
private string _jobTitle;

        [
XmlElement("Title")]
        
public string JobTitle
        {
            
get { return _jobTitle; }
            
set { _jobTitle = value; }
        }

        
private int _yearsOfExperience;

        [
XmlElement("Experience")]
        
public int YearsOfExperience
        {
            
get { return _yearsOfExperience; }
            
set { _yearsOfExperience = value; }
        }

        
private Person _person = new Person();

        [
XmlElement("Person")]
        
public Person Person
        {
            
get { return _person; }
            
set { _person = value; }
        }

        #endregion
    }
I would like to display properties from both the Employee and Person class in a GridView control and allow sorting on all columns. For properties in Employee class we can bind using the normal asp:BoundField. For properties in the child class, we need to use a TemplateField and use the Eval() method. I added a SortExpression to each column and set AllowSorting to true on the GridView to endable sorting.
Figure 3: The GridView
<html xmlns="http://www.w3.org/1999/xhtml" >
<
head runat="server">
    <title>Sorting &amp; Object Binding</title>
</
head>
<
body>
    <form id="form1" runat="server">
    
        
<div style="padding:10px">
            <asp:Label ID="TitleLabel" runat="server" Font-Bold="True" Font-Size="Larger"
                        
Text="Sorting with an Object data source"></asp:Label>
        </div>
        
        
<div style="padding:10px">
            <asp:GridView ID="EmployeeGridView" runat="server" DataSourceID="EmployeeObjectDataSource"
                    
AutoGenerateColumns="False" AllowSorting="True" BackColor="White" BorderColor="#999999"
                    
BorderStyle="None" BorderWidth="1px" CellPadding="3" GridLines="Vertical">
                <Columns>
                    <asp:BoundField DataField="JobTitle" HeaderText="Title" SortExpression="JobTitle" />
                    <asp:TemplateField HeaderText="Last Name" SortExpression="Person.LastName">
                        <ItemTemplate>
                            <asp:Label ID="LastNameLabel" runat="server" Text='<%# Eval("Person.LastName") %>'></asp:Label>
                        </ItemTemplate>
                    </asp:TemplateField>
                    <asp:TemplateField HeaderText="First Name" SortExpression="Person.FirstName">
                        <ItemTemplate>
                            <asp:Label ID="FirstNameLabel" runat="server" Text='<%# Eval("Person.FirstName") %>'></asp:Label>
                        </ItemTemplate>
                    </asp:TemplateField>
                    <asp:BoundField DataField="YearsOfExperience" HeaderText="Experience" SortExpression="YearsOfExperience" />
                </Columns>
                <FooterStyle BackColor="#CCCCCC" ForeColor="Black" />
                <RowStyle BackColor="#EEEEEE" ForeColor="Black" />
                <SelectedRowStyle BackColor="#008A8C" Font-Bold="True" ForeColor="White" />
                <PagerStyle BackColor="#999999" ForeColor="Black" HorizontalAlign="Center" />
                <HeaderStyle BackColor="#000084" Font-Bold="True" ForeColor="White" />
                <AlternatingRowStyle BackColor="#DCDCDC" />
            </asp:GridView>
        </div>
        
        
<asp:ObjectDataSource ID="EmployeeObjectDataSource" runat="server" SelectMethod="GetEmployees"
            TypeName="Crz.BindingExample.DataObjects.DataSource" SortParameterName="SortExpression">
            <SelectParameters>
                <asp:Parameter DefaultValue="" Name="xmlPath" Type="String" />
            </SelectParameters>
        </asp:ObjectDataSource>
        
    
</form>
</
body>
</
html>
Notice the SortParameterName on the ObjectDataSource. The SelectMethod GetEmployees() must accept this parameter for sorting to work. The ObjectDataSource passes the SortExpression and ASC or DESC to the SelectMethod when the user clicks a column heading. The GetEmployees() method accepts this parameter and returns an array of Employee objects.
Figure 4: GetEmployees Method
public static Employee[] GetEmployees(string xmlPath, string sortExpression)
{
    
return GetCollection<Employee>(xmlPath, sortExpression);
}
The GetEmployees method in turn calls the Generic GetCollection method. This method can be used to return any collection of objects serialized to an XML docuemnt. The collection is first deserialized, then the Sort Field and Order are parsed, and the collection is sorted with the Sort(comparer) method.
Figure 5: GetCollection<> Method
        protected static T[] GetCollection<T>(string xmlPath, string sortExpression)
        {
            
//deserialize the collection with the xml document
            XmlSerializer s = new XmlSerializer(typeof(List<T>));
            
TextReader r = new StreamReader(xmlPath);
            
List<T> list = (List<T>)s.Deserialize(r);
            r.Close();

            
//if no sort expression was specified, return the unorderd list
            if (sortExpression == null || sortExpression == string.Empty)
                
return list.ToArray();

            
//split the sort expression w/c is in the form SORTITEM [ASC|DESC]
            string[] sortOptions = sortExpression.Split(' ');

            
//the field is in the first element of the array
            string field = sortOptions[0];

            
//the order is in the second element, if it was specified. Default to ASC
            string sortOrder = sortOptions.Length > 1 ? sortOptions[1] : "ASC";

            
//Sort the list with the Generic Comparer
            ObjectComparer<T> comparer = new ObjectComparer<T>(field, sortOrder.ToUpper());
            list.Sort(comparer);

            
return list.ToArray();
        }
So far, other than using a TemplateField for the child object properties, I haven't done too much out of the ordinary. The ObjectComparer class is what allows us sort the collection. The Compare() method is required by any class that implements IComparer. It takes two objects of the same type and returns a zero if they're equal, a negative int if the first is less than the second, and a positive int if the first is greater than the second. Because the field we're comparing may be a property of a child class, like Employee.Person.FirstName, we need to descend the classes to get the value. We do this with the GetPropertyValue method which uses reflection to return the value of a given property. In this example, we first get the value of the Person property, then we get the value of the FirstName property of the Person. After the values of the properties are compared, they are negated if the direction is descending.
Figure 6: ObjectComparer Class
    public class ObjectComparer<T> : IComparer<T>
    {
        #region Properties

        
private string _direction = "ASC";

        
public string Direction
        {
            
get { return _direction; }
            
set { _direction = value; }
        }

        
private string _compareField;

        
public string CompareField
        {
            
get { return _compareField; }
            
set { _compareField = value; }
        }

        #endregion

        #region
Constructors

        
public ObjectComparer(string compareField, string direction)
        {
            _compareField = compareField;
            _direction = direction;
        }

        #endregion

        #region
IComparer<T> Members

        
public int Compare(T x, T y)
        {
            
//Split the fields into an array. The field is divided by dots,
            //like Employee.Person.FirstName
            string[] fieldParts = _compareField.Split('.');

            
object compareValX = x;
            
object compareValY = y;

            
//get the value of each field in the list.
            foreach (string field in fieldParts)
            {
                compareValX = GetPropertyValue(compareValX, field);
                compareValY = GetPropertyValue(compareValY, field);
            }

            
//compare the values of the last fields in the list. Assumes the field's
            //type implements IComparable.
            int compareValue = ((IComparable)compareValX).CompareTo(compareValY);

            
//negate the result if dircection is descending
            if (_direction.ToUpper() == "DESC")
                compareValue = -1 * compareValue;

            
return compareValue;
        }

        
private object GetPropertyValue(object o, string property)
        {
            
//using reflection, load the type and return the value of
            //the given property. This will throw an exception if the
            //property cannot be found.
            PropertyInfo pi = o.GetType().GetProperty(property);
            
object val = pi.GetValue(o, null);
            
return val;
        }

        #endregion
    }
That's all we need to do to sort by any field in our class. We could even sort by something like Employee.Person.LastName.Length.
Screenshot: Sortable GridView
Posted on Monday, March 27, 2006 5:36 PM ASP.NET | Back to top


Comments on this post: Sorting with an Object Data Source

# re: Sorting with an Object Data Source
Requesting Gravatar...
It is really great to solve my problem
Left by Jacky on Aug 09, 2006 8:59 PM

# re: Sorting with an Object Data Source
Requesting Gravatar...
Thanks for the article. It was exactly what I needed.
Left by Brandon on Aug 17, 2006 7:49 AM

# re: Sorting with an Object Data Source
Requesting Gravatar...
Thanks for the article. It was exactly what I needed.
Left by Brandon on Aug 17, 2006 7:49 AM

# re: Sorting with an Object Data Source
Requesting Gravatar...
You should look into the DynamicComparer stuff I've written. It does LCG and supports fields and properties (static or instance) and lets you specify multiple sort keys. Head over to http://musingmarc.blogspot.com/2006/02/dynamic-sorting-of-objects-using.html
Left by Marc Brooks on Aug 17, 2006 3:56 PM

# re: Sorting with an Object Data Source
Requesting Gravatar...
I'm unable to download the project..anyone can help me?
thx
Left by federico vigna on Jan 03, 2008 5:14 AM

# re: Sorting with an Object Data Source
Requesting Gravatar...
Gracias por el articulo, era exactamente lo que necesitaba.
Left by Eduardo Rivas on Apr 20, 2008 10:04 AM

# Bugfix for nullable properties, e.g. DateTime?
Requesting Gravatar...
The line containing ((IComparable)compareValX).CompareTo throws an exception if compareValX is null, e.g.

if it is a DateTime? containing a null value.
This conditional fixes the problem:

//compare the values of the last fields in the list. Assumes the field's
//type implements IComparable.
int compareValue;
if (compareValX != null)
{
compareValue = ((IComparable)compareValX).CompareTo(compareValY);
}
else
{
if (compareValY == null)
{
compareValue = 0; // both null -> equal
}
else
{
compareValue = -1; // x null, y inst -> x less than y
}
}

Left by tarnold on Aug 26, 2008 12:36 AM

# re: Sorting with an Object Data Source
Requesting Gravatar...
Thanks for such a great walkthrough. That helped a lot with understanding how the xml path attribute ties in with the databinding. This was key:

<asp:ObjectDataSource ID="EmployeeObjectDataSource" runat="server" SelectMethod="GetEmployees"
TypeName="Crz.BindingExample.DataObjects.DataSource" SortParameterName="SortExpression">
<SelectParameters>
<asp:Parameter DefaultValue="" Name="xmlPath" Type="String" />
</SelectParameters>
</asp:ObjectDataSource>

I am new still to databound controls but it is making sense. I also learned from this writing on basics of ObjectDataSource control:
http://stuartthompsontech.wordpress.com/2007/04/17/the-objectdatasource-web-control/

Much easier than using the classic ASP.
Left by Stalvan V. on Oct 17, 2008 9:21 AM

# re: Sorting with an Object Data Source
Requesting Gravatar...
The download link for your project seems to be broken (it times out for me). I would love to download the project and pore over its code.
Left by dave on Jan 25, 2009 12:29 PM

# re: Sorting with an Object Data Source
Requesting Gravatar...
I needed to be able to specify secondary, tertiary, etc. "tiebreaker" fields, so I modified the Compare method (include tarnold's fix for null). Using this Compare, in the page markup, you can specify a comma-delimited list (no spaces) of fields to use in the sort.

public int Compare(T x, T y)
{
int compareValue = 0;
//_compareField may have comma-separated list for primary sort, secondary sort, etc.
string[] sortFields = _compareField.Split(',');

//Loop through the sort fields until we find a difference or run out of fields.
foreach (string sortField in sortFields)
{
//Split the fields into an array. The field is divided by dots,
//like Employee.Person.FirstName
string[] fieldParts = sortField.Split('.');
object compareValX = x;
object compareValY = y;

//get the value of each field in the list.
foreach (string field in fieldParts)
{
compareValX = GetPropertyValue(compareValX, field);
compareValY = GetPropertyValue(compareValY, field);
}

//compare the values of the last fields in the list. Assumes the field's
//type implements IComparable.
//TODO: Fix nullpointer possibility here
if (compareValX != null)
{
compareValue = ((IComparable)compareValX).CompareTo(compareValY);
}
else
{
if (compareValY == null)
{
compareValue = 0; // both null -> equal
}
else
{
compareValue = -1; // x null, y inst -> x less than y
}
}

if (compareValue != 0)
{
//no need to continue looping, we have a difference.
break;
}
}
//negate the result if direction is descending
if (_direction.ToUpper() == "DESC")
{
compareValue = -1 * compareValue;
}

return compareValue;
}
Left by Tim on Mar 23, 2009 2:29 PM

# re: Sorting with an Object Data Source
Requesting Gravatar...
i got an error "404 page not found" for both the links you placed in the article
Left by vrunda on Dec 15, 2009 4:43 AM

# re: Sorting with an Object Data Source
Requesting Gravatar...
Works like a charm ! Thank you so much.
Left by Saurabh on Jun 06, 2011 5:59 PM

# re: Sorting with an Object Data Source
Requesting Gravatar...
Good article´╝ü
Left by wade on Sep 01, 2012 10:46 PM

Your comment:
 (will show your gravatar)


Copyright © Carl Zumbano | Powered by: GeeksWithBlogs.net