ObjectHierarchicalDataSource sample code
Some time ago, I released the source code for my CompositeHierarchicalDataSource that answers the problem that there is no out-of-the-box way to declaratively bind a Menu or TreeView to a database in ASP.NET 2.0: only XML and SiteMap have hierarchical data sources. You could still do it programmatically, but declaratively is so much more fun...
It's also noticeable that while there is a very handy ObjectDataSource control in ASP.NET 2.0, it does not have a hierarchical equivalent. Basically, the idea would be to declaratively bind a hierarchical control to an arbitrary object graph.
Enter the ObjectHierarchicalDataSource, which transfers many of the nice ideas in ObjectDataSource to the hierarchical realm.
Here's how you can use it:
<my:ObjectHierarchicalDataSource runat=server ID=ObjectInstance1 TypeName="Categories">
<SelectMethods>
<my:SelectMethod TypeName="CatsCategory" Method="GetCats" />
<my:SelectMethod TypeName="Cat" PropertyNames="Color,Gender" />
</SelectMethods>
</my:ObjectHierarchicalDataSource>
<asp:TreeView Runat=Server ID=categoryTree DataSourceID=ObjectInstance1
ExpandDepth=0 PopulateNodesFromClient=true>
<DataBindings>
<asp:TreeNodeBinding TextField="#" ValueField="#" ToolTipField="#" PopulateOnDemand=true />
<asp:TreeNodeBinding DataMember="CatsCategory" TextField="Name" ValueField="Name"
ToolTipField="Name" PopulateOnDemand=true />
<asp:TreeNodeBinding DataMember="Cat" TextField="Name" ValueField="Name"
ToolTipField="Description" PopulateOnDemand=true />
<asp:TreeNodeBinding DataMember="Color" FormatString="Color: {0}" PopulateOnDemand="true"
SelectAction="None" TextField="Name" ValueField="Name" ToolTipField="#" />
<asp:TreeNodeBinding DataMember="Gender" PopulateOnDemand="true" SelectAction="None"
TextField="#" ValueField="#" ToolTipField="#" />
</DataBindings>
</asp:TreeView>
<SelectMethods>
<my:SelectMethod TypeName="CatsCategory" Method="GetCats" />
<my:SelectMethod TypeName="Cat" PropertyNames="Color,Gender" />
</SelectMethods>
</my:ObjectHierarchicalDataSource>
<asp:TreeView Runat=Server ID=categoryTree DataSourceID=ObjectInstance1
ExpandDepth=0 PopulateNodesFromClient=true>
<DataBindings>
<asp:TreeNodeBinding TextField="#" ValueField="#" ToolTipField="#" PopulateOnDemand=true />
<asp:TreeNodeBinding DataMember="CatsCategory" TextField="Name" ValueField="Name"
ToolTipField="Name" PopulateOnDemand=true />
<asp:TreeNodeBinding DataMember="Cat" TextField="Name" ValueField="Name"
ToolTipField="Description" PopulateOnDemand=true />
<asp:TreeNodeBinding DataMember="Color" FormatString="Color: {0}" PopulateOnDemand="true"
SelectAction="None" TextField="Name" ValueField="Name" ToolTipField="#" />
<asp:TreeNodeBinding DataMember="Gender" PopulateOnDemand="true" SelectAction="None"
TextField="#" ValueField="#" ToolTipField="#" />
</DataBindings>
</asp:TreeView>
We're giving the data source a type name that will be the root of the graph. This type must have a default parameterless constructor. We don't have to specify a select method on this type because it is enumerable and if you don't specify a select method on an enumerable object, the data source will just assume the enumeration is describing the next level.
CatsCategory, on the other hand, is not directly enumerable, so we must provide the name of a method that will get us the enumeration of objects in the next level: GetCats().
Finally, the third way to describe the children of an object is a list of properties. That's what we're doing for Cat: its children are simply the Color and Gender properties.
Now, all there is to do is to tell the TreeView that will consume the data source (Menu would work in just the same way) how to bind itself to the data. We're using the DataBindings property for that, and for each type (DataMember) that will be present in the object graph, we tell the tree which properties to use for the text, value, tooltip or format string. We also have a default binding for types that we don't want to special-case (such as string).
The "#" bindings express that the "field" we want is not a property but the return value of the ToString() method.
Notice how the tree will populate on demand even with this exotic data source (which could be an absolute must if your data source is infinitely deep).
One of the most interesting parts of the implementation is the way we wrap the objects of the graph in objects that know how to expose their children in the hierarchy in a standardized way but still show all the properties of the wrapped object under Reflection (which the data source of course uses intensively, this is the price of extreme flexibility and ease of declaration). The way it does that is by implementing ICustomTypeDescriptor, which is an amazingly useful interface. Have a look at the ObjectHierarchyData source code if you want more details.
The source code and sample page (which will auto-compile) can be found here: