ASP.NET and Hierarchical Data
In addition to other new and interesting things ASP.NET also brought along controllers supporting representation of hierarchical data. This posting introduces IHierarchyData and IHierarchyEnumerable interfaces we can use to create wrappers for classes representing hierarchical data of the business logic layer.
Classes and interfaces supporting hierarchical data are located in the System.Web.UI namespace. There is nothing strange about this choice as these are tools directed to controls supporting hierarchical data. In principle they would not be right for the business logic layer as they are wrappers by their structure, not classes that should be extended by the classes of the business logic layer.
In some ways it is even good as one can hide pretty different hierarchy management mechanisms behind the wrappers. At that the controls supporting hierarchical data always retain the same interface they can use for binding data to them.
Getting Started
Let’s use the IHierarchyData and IHierarchyEnumerable interfaces as an example. Let there be a class named Section in the business logic layer, representing for example the sections’ arrangement of a content management system. The sections can contain subsections and thus every section also has a parent section located one step higher from it. This does not apply to the first level sections as for them the parent section is zero.
public class Section
{
private Int32 sectionId = 0;
private string title;
private string description;
private Section parent;
private IList subSections;
public Int32 Id
{
get { return this.sectionId; }
set { this.sectionId = value; }
}
public string Description
{
get { return this.description; }
set { this.description = value; }
}
public Section Parent
{
get { return this.parent; }
set { this.parent = value; }
}
public IList SubSections
{
get { return this.subSections; }
set { this.subSections = value; }
}
public string Title
{
get { return this.title; }
set { this.title = value; }
}
}
There is no point in documenting the class here separately as the inline documentation should be quite sufficient. As concerns the business logic layer, I’ll leave the solution open because otherwise we would wander very far from the original subject.
IHierarchyData
Let’s see the IHierarchyData interface first. It is a wrapper for hierachical data objects. This interface defines the following properties and methods.
- GetChildren() – returns a list of child objects of the current data object.
- GetParent() – returns the parent object of the current object.
- HasChildren – returns true if the current data object has child data objects.
- Item – returns the data object associated with the current object.
- Path – return the location path of the data object in the hierarchy.
- Type – the name of the object’s type.
To make, for example, a TreeView control to understand the sections class as a hierarchical class, we have to create a class that conforms to the IHierarchyData interface. Let’s name this class SectionData.
public class SectionData : IHierarchyData
{
private Section section;
public SectionData(Section section)
{
this.section = section;
}
public IHierarchicalEnumerable GetChildren()
{
return new SectionCollection(this.section.SubSections);
}
public IHierarchyData GetParent()
{
return new SectionData(this.section.Parent);
}
public bool HasChildren
{
get
{
if (this.section.SubSection == null)
return 0;
return (this.section.SubSections.Count > 0);
}
}
public object Item
{
get { return section; }
}
public string Path
{
get
{
Section section = this.section;
string s = this.section.Title;
while ((section = section.Parent) != null)
s = section.Title + ": " + s;
return s.Trim();
}
}
public string Type
{
get { return this.section.ToString(); }
}
}
Let’s provide the created SectionData object with a Section object the SectionData object uses to respond to the controller using it.
IHierchicalEnumerable
Data sources are collections of data objects. Therefore we also need one interface for hierarchical collections. For this the System.Web.UI namesapce offers the IHierarchicalEnumerable interface. This adds two methods to the collection.
- GetHierarchyData() – returns the wrapper matching the current data object, conforming to the IHierarchyData interface.
- GetEnumerator() – returns the enumerator to be used for cycling the elements of the collection.
Thus, now we have to create a collection conforming to the IHierarchicalEnumerable interface for our sections. To avoid copying the initial collection we should create a wrapper and provide the initial collection to it as an argument.
public class SectionCollection : IHierarchicalEnumerable
{
private IList sectionList = null;
public SectionCollection(IList sectionList)
{
this.sectionList = sectionList;
}
public IHierarchyData GetHierarchyData(object o)
{
return new SectionData((Section)o);
}
public IEnumerator GetEnumerator()
{
return sectionList.GetEnumerator();
}
}
Now we have everything needed to bind the sections to some hierarchical control automatically. This example deals with the TreeView control.
Binding Data to TreeView
Let’s try to bind the data to the TreeView control. For this we need one web form. The TreeView has to be dragged to this form. Let’s add the following block to the control’s definition.
<DataBindings>
<asp:TreeNodeBinding TextField="Title" ValueField="Id" />
</DataBindings>
In case of the GET call the data are bound to the control on loading the control and in case of the POST call when it is requested by the control that caused the posting.
protected void Page_Load(object sender, EventArgs e)
{
if (!this.IsPostBack)
this.BindTree();
}
private void BindTree()
{
SectionManager manager = new SectionManager();
SectionCollection sections;
sections = new SectionCollection(manager.ListFirstLevel());
this.trvSections.DataSource = sections;
this.trvSections.DataBind();
}
In this code SectionManager is the management class of the sections and the ListFirstLevel() returns a list of all the first level sections. When associated with data, the TreeView control named trvSections located on the form nicely displays the section hierarcy.
To comment this, I would like to add that we do not deal with creating the Section class and assigning values to its attributes here, as a whole separate article could be written on this subject.
Summary
To sum it up we can say it is not hard to build hierarchical data support for ASP.NET 2.0 controls. It is possible to create wrapper classes with the help of interfaces provided by the System.Web.UI namespace, that can be used for building a varied hierarchy support.
Do we need to have a wrapper class like SectionData, can you put it straight into the Section class?
Imho it is better idea to keep business classes and presentation layer wrappers separate. Wrapper classes in presentation layer are nothing unusual. For most of complex controls you have to use them and wrappers are good translators between business domain logic and presentation logic.
But how can I fill a hierarchical object from a database? So forget about the presentation layer, just to focus on Datalayer and Business Logic Layer.
It depents directly on your data structures. There are many strategies how to keep hierarchical data in database and how to retrieve it. I suggest you to check out books from Joe Celko (http://www.celko.com/books.htm) – it’s a high level reading.
The other factor is your objects runtime. Are you using your own DAL or do you use some O/R-mapper?
On the technical side, if you are using ADO.NET data objects you should have method in your DAL you can call to get children on specific row in specific table. Also you can load flat row set to DataTable and use DataViews to filter out rows you currently need.
If you are using O/R-mapper you should read its documentation and test how it can handle hierarchical data.
is there is any other way we have to represnting heirchical data to get a different look.it may be like binary tree.