A few posts ago, I wrote about how one could implement a custom GridView field type to help minimize the markup bloat which is often the result of too many TemplateFields. Here's another great example of how easy it is to implement a custom field type. Assume we have the following objects:
As you can see, we have a Person class, which (among a few others) has an Address property of type Address. In Windows Forms, if you wanted to list a set of such Persons in a grid and display the City, PostCode and Street fields in columns, you'd have to implement the interface ITypedList on the collection you would databind against, supplying a custom schema. I might make a post about that later, but for now we're focusing on how to solve this in ASP.NET. You would be forgiven for thinking that just passing "Address.City" as the DataField in a BoundField should work:
<asp:GridView
ID="_grid"
runat="server"
AutoGenerateColumns="false">
<Columns>
<asp:BoundField HeaderText="City" DataField="Address.City" />
</Columns>
</asp:GridView>
After all, DataBinder.Eval would allow this. The BoundField will complain that there is no field or property on Person called "Address.City" however; instead of using the DataBinder internally it has its own more naive property value lookup algorithm, and doesn't realise that you want it to do a recursive lookup. The quick fix? Just use DataBinder.Eval:
<asp:GridView
ID="_grid"
runat="server"
AutoGenerateColumns="false">
<Columns>
<asp:TemplateField HeaderText="City">
<ItemTemplate>
<%# Eval("Address.City") %>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
But if you're like me, all that extra markup will get tedious really fast - why not just "fix" BoundField instead? Lets create our own extention of it which we'll call CompositeBoundField, and implement it the way the ASP.NET team should have, had they reused their own code instead of reinventing the wheel ;)
public class CompositeBoundField : BoundField
{
protected override object GetValue(Control controlContainer)
{
object item = DataBinder.GetDataItem(controlContainer);
return DataBinder.Eval(item, this.DataField);
}
}
That is merely two lines of code - which does more than the original BoundField.GetValue implementation did, allowing us to clean up the markup again:
<asp:GridView
ID="_grid"
runat="server"
AutoGenerateColumns="false">
<Columns>
<cc:CompositeBoundField HeaderText="City" DataField="Address.City" />
</Columns>
</asp:GridView>
And not only does it support things like "Address.City", but the DataBinder.Eval function will even evaluate expressions indexing into arrays - you could for example set the DataField to "Addresses[0].City", if you had an array of Address objects. Pretty neat! :)