Friday, November 3, 2006

ASP.NET 2.0 : Hierarchical datasources

While implementing a virtual file system for a customer I found myself in need of a custom hierarchical datasource. As usual, the MSDN website offered a good sample of such a class. Easy right? Anything but easy as it turns out. The sample code is all wrong. I ended up reading IL code to figure out how the SiteMapDataSource works. Reading IL is not exactly my idea of easy. Turns out the problem is in the way the HierarchicalDataSourceControl and HierarchicalDataSourceView interact. The DataSource control is supposed to be the workhorse of the two. The view is just a container for the results. In the sample however, the converse is true. That set me off in the wrong direction... The easiest thing to do is this:
  1. Create a node class that implement IHierarchyData
  2. Create a control derived from HierarchicalDataSourceControl.
  3. Create a specialized collection to hold your nodes and implement IHierarchicalEnumerable
  4. Implement a HierarchicalDataSourceView
  5. Override the GetHierarchicalView method in the HierarchicalDataSourceControl.

The first four steps are easy. Create the classes and implement the methods, pretty straight foreward. Using generics the collection just requires an implementation for IHierarchicalEnumerable.GetHierarchyData which can be as simple as a type cast since the node class implements IHierarchyData. I'll get back to implementing the nodes later. Implementing the HierarchicalDataSourceView is not that difficult either. It's just a wrapper around your collection. Store a copy of your collection class and return it in the Select method. That's all.

Overriding the GetHierarchicalView method
This is the method that does the actual work in the HierarchicalDataSource. The role of the viewPath parameter is the key to making this work; use it like a query. If you're loading objects from a database like me, the viewpath should be the unique key to the object. In the implementation use the viewPath to decide what to do:

  • If the viewPath is empty, use the root node of your hierarchy. If you want to include the root node in the hierarchy, return the root node itself. If you don't return it's children.
  • If viewPath is not empty, fetch the node pointed to by the viewPath and return it's children. This is crucial! Do not return the node itself, return it's children because when a view is requested for a node, it means the node was already returned in a previous call. If you return the node itself, the datasource will get stuck in an endless loop...

Implementing the nodes
The node implementation is nothing spectacular. Just make sure that the Path property returns something that you can process as a viewPath in your GetHierarchicalView implementation. If you want to bind a TreeView to your HierarchicalDataSourceControl you can use the Type property to hint the icon for the node. For example, when your hierarchy contains files and folders consider returning the mime type of the file so you can display the appropriate icon in the tree. I've compiled a simple ready-to-run sample (VS 2005) that uses a TreeView control to render the files in a given folder on disk.


Anonymous said...

Any chance of getting that sample code?

Marnix said...

No problem. I've updated the post to include a link to some sample code.

It's a stripped version of what I originally developed. That solution was using a virtual file system from a database. The sample uses regular files but the basic principles are the same. I've included some tips in key places for hooking up the datasource to a database.

Anonymous said...

Suppose to be able to use this Hierarchical datasource with the menu control. I was able to get the menu control to display properly, but would u happen to know where the url might need to exist so that the menu can navigate the browser to other pages?

Marnix said...

To allow automatic binding for navigation purposes you need to implement INavigateUIData on the type that is returned by the the datasource. In the sample code that would be on FileSystemNode.
This interface provides navigation controls like Menu with a url, a description and a name. This could also be accomplished through a data binding declaration on the control.

I've updated the sample code to include INavigateUIData. Note that in order for this to work, you need to keep the virtual path for the FileSystemNode as well. That can be constructed using the VirtualPathUtility as needed.
As an added bonus, security is improved because absolute paths are no longer visible on the client.

You can download the modified sample code here.

Anonymous said...

Nice Work!

You're like .Net Batman...but better.


Anonymous said...

I'm a little late joining this particular party, but excellent article.

I have developed a similar hierarchical datasource and am struggling over the design mode classes (HierarchicalDatasourceDesigner etc.) have you implemented these succesfully at all and do you have any samples?

Anonymous said...

I was wondering if you could shed some light on a problem I'm having with my GetHierarchicalView override (protected override HierarchicalDataSourceView GetHierarchicalView(string viewPath)

Basically I want an asp:menu control to be able to specify it's viewpath. I can't use the DataMember attribute on the asp:Menu node and when I add a MenuItemBinding node the viewPath does not come through either.

Marnix said...

Hi Mark,

You can find the viewpath of a node through the IHierarchyView.Path property.
If you specify that path on the datasource you can get the menu control to render any subtree in the hierarchy.
Note however that the viewpath passed in to GetHierarchicalView is used only to get the top node in the hierarchy. Below that the ASP.NET controls simply iterate over the child collections.

Marnix said...

Anonymous: Sorry, I haven't spent any time on creating designers for these controls.

Anonymous said...

Great article. It helped me a lot :)

Jason Timmins said...

Great article. Really helped me out. Cheers

Anonymous said...

I concur with your finding about the MSDN sample code. Thanks for the article and sample code, you saved the little hair i still have remaining