Saturday, July 12, 2008

Sorting a grid with ObjectDataSource

ObjectDataSource is a lazy way to power a grid control (let's say a GridView for example). Surprisingly, the Microsoft team didn't give good thought to any Data Source controls except SqlDataSource.

The "It just works" approach just doesn't work with ObjectDataSource sorting. I like using generic lists a lot but ObjectDataSource can not sort them: "The data source 'mySource' does not support sorting with IEnumerable data. Automatic sorting is only supported with DataView, DataTable, and DataSet". If they were aware of this flaw - why didn't they just fix it and provide nice sorting support?

Thus the hack is up to us.

The first step would be supplying the Comparer for our custom class. And if you're rightfully lazy to write a full set of Comparers for each property, then reflection is to the rescue:

public class ToyComparer : IComparer<Toy>
{
private readonly string _property;

public ToyComparer (string propertyName)
{
_property = propertyName;
}

public int Compare(Toy x, Toy y)
{
PropertyInfo property = x.GetType().GetProperty(_property);
if (property == null)
throw new ApplicationException("Invalid property " +_property);
return Comparer.DefaultInvariant.Compare
(property.GetValue(x, null), property.GetValue(y, null));
}
}

The class's List Select Method uses Comparer to sort the output list:


private static List<Toy> GetToys(string propertyName)
{
//list creation is omitted ...
if (!string.IsNullOrEmpty(propertyName))
{
list.Sort(new ToyComparer(propertyName,order));
}
return list;
}

The second step is to add a Select parameter to the ObjectDataSource:


<asp:ObjectDataSource ID="mySource" SelectMethod="GetToys" TypeName="Toy"...>
<SelectParameters>
<asp:Parameter Name="propertyName" Direction="Input" Type="String"/>
</SelectParameters>
</asp:ObjectDataSource>

And last but not the least - we need to set the property of the ObjectDataSource and cancel the grid Sorting event to prevent the exception:


protected void gridMaster_Sorting(object sender, GridViewSortEventArgs e)
{
mySource.SelectParameters["propertyName"].DefaultValue = e.SortExpression;
e.Cancel = true;
}

If necessary we can pass the sorting order the same way.

6 comments:

Anonymous said...

Can anyone translate this to VB?
I'm having the most trouble with:

Comparer.DefaultInvariant.Compare(
property.GetValue(x, null),
property.GetValue(y, null)
);

Will this do the same thing?

dim nullArray(0) as object
nullarray(0) = DBNull.Value
Comparer.DefaultInvariant.Compare(
prop.GetValue(x, nullArray),
prop.GetValue(y, nullArray)
)

I had to change property to prop because VB wouldn't allow a variable called property.

Anonymous said...

And where does the order parameter come from in:

list.Sort(new ToyComparer(propertyName,order));

Michael Goldobin said...

I didn't test it but I would expect that this shoul do:
Comparer.DefaultInvariant.Compare(
prop.GetValue(x, nothing),
prop.GetValue(y, nothing)
)
"property" is just a name I happened to pick - call it "field", for example.

About the "order" paramter: sorry, my bad. Of course it should be injected in the method:
private static List<Toy> GetToys(string propertyName, string order)
{
//list creation is omitted ...
if (!string.IsNullOrEmpty(propertyName))
{
list.Sort(new ToyComparer(propertyName,order));
}
return list;
}
Inside the ToyComparer constructor you make a decision about proper "ordrer" decoding, e.g. it is equal ASC or DESC.
public class ToyComparer : IComparer<Toy>
{
private readonly string _property;
private readonly string _order;
public ToyComparer (string propertyName, string order)
{
_property = propertyName;
_order=(string.IsNullOrEmpty(order) || (!order.Equals("ASC") && !order.Equals("DESC")))?"ASC":order;
}

public int Compare(Toy x, Toy y)
{
PropertyInfo property = x.GetType().GetProperty(_property);
if (property == null)
throw new ApplicationException("Invalid property " +_property);
if (_order.Equals("ASC"))
return Comparer.DefaultInvariant.Compare
(property.GetValue(x, null), property.GetValue(y, null));
else
return Comparer.DefaultInvariant.Compare
(property.GetValue(y, null), property.GetValue(x, null));
}
}

I split Comparer method return statement for better readability, otherwise I would use "?:" statement.

Ryan said...

I found this post very useful, thankyou very much.

Anonymous - if you would like to name a variable "property" in VB, you can do so with the use of square brackets like so [Property]. Hope this helps.

Anh Pham said...

Helped me today, thanks!

Anonymous said...

Thanx mate, works like a charm.


© 2008-2013 Michael Goldobin. All rights reserved