Gunnar Peipman's ASP.NET blog

ASP.NET, C#, SharePoint, SQL Server and general software development topics.

Sponsors

News

 
 
 
 
 
Programming Blogs - Blog Catalog Blog Directory
 
 
 

Links

Social

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. 


/// <summary>
/// Tree for content sections.
/// </summary>
public class Section
{
    private Int32 sectionId = 0;
    private string title;
    private string description;

    private Section parent;
    private IList subSections;

    /// <summary>
    /// Gets or sets section's unique id.
    /// </summary>
    public Int32 Id
    {
        get { return this.sectionId; }
        set { this.sectionId = value; }
    }

    /// <summary>
    /// Gets or sets section's description.
    /// </summary>   
    public string Description
    {
        get { return this.description; }
        set { this.description = value; }
    }

    /// <summary>
    /// Gets or sets parent section. For first level sections the parent is null.
    /// </summary>   
    public Section Parent
    {
        get { return this.parent; }
        set { this.parent = value; }
    }

    /// <summary>
    /// Gets or sets list of subsections.
    /// </summary>   
    public IList SubSections
    {
        get { return this.subSections; }
        set { this.subSections = value; }
    }

    /// <summary>
    /// Gets or sets section's title.
    /// </summary>   
    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.


/// <summary>
/// Hierarchical wrapper for Section.
/// </summary>
public class SectionData : IHierarchyData
{
    private Section section;

    /// <summary>
    /// Constructor.
    /// </summary>
    /// <param name="section">Section for which the wrapper will be created.</param>
    public SectionData(Section section)
    {
        this.section = section;
    }

    /// <summary>
    /// Gets list of subsections.
    /// </summary>
    public IHierarchicalEnumerable GetChildren()
    {
        return new SectionCollection(this.section.SubSections);
    }

    /// <summary>
    /// Returns parent of wrapped section.
    /// </summary>
    public IHierarchyData GetParent()
    {
        return new SectionData(this.section.Parent);
    }

    /// <summary>
    /// Returns true if wrapped section has subsections.
    /// </summary>
    public bool HasChildren
    {
        get
        {
            if(this.section.SubSection == null)
                return 0;
            return (this.section.SubSections.Count> 0);
        }
    }

    /// <summary>
    /// Returns wrapped section.
    /// </summary>
    public object Item
    {
        get { return section; }
    }

    /// <summary>
    /// Returns path of section in object hierarchy.
    /// </summary>
    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();
        }
    }

    /// <summary>
    /// Returns wrapped section's type name for hierarchical control node.
    /// </summary>
    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.


/// <summary>
/// Hierarchical wrappers for sections collection.
/// </summary>
public class SectionCollection : IHierarchicalEnumerable
{
    private IList sectionList = null;

    /// <summary>
    /// Constructor of class.
    /// </summary>
    /// <param name="sectionList">List of sections.</param>
    public SectionCollection(IList sectionList)
    {
        this.sectionList = sectionList;
    }

    /// <summary>
    /// Returns wrapper for given data object.
    /// </summary>
    /// <param name="o">Data object for which the wrapper is needed.
    /// </param>   
    public IHierarchyData GetHierarchyData(object o)
    {
        return new SectionData((Section)o);
    }

    /// <summary>
    /// Returns collection's enumerator.
    /// </summary>
    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.


/// <summary>
/// Page load event.
/// </summary>
protected void Page_Load(object sender, EventArgs e)
{
    if(!this.IsPostBack)
        this.BindTree();
}
/// <summary>
/// Binds data to TreeView control.
/// </summary>
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.


kick it on DotNetKicks.com vote it on WebDevVote.com pimp it Progg it Shout it
Posted: Oct 01 2007, 11:00 PM by DigiMortal | with 11 comment(s)
Filed under:

Comments

JohanNL said:

Do we need to have a wrapper class like SectionData, can you put it straight into the Section class?

# April 8, 2008 8:03 AM

DigiMortal said:

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.

# April 8, 2008 8:24 AM

JohanNL said:

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.

# April 14, 2008 10:34 AM

DigiMortal said:

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 <a href="www.celko.com/books.htm">Joe Celko</a> - 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.

# April 14, 2008 12:47 PM

DotNetShoutout said:

Thank you for submitting this cool story - Trackback from DotNetShoutout

# July 4, 2009 3:40 AM

PimpThisBlog.com said:

Thank you for submitting this cool story - Trackback from PimpThisBlog.com

# July 4, 2009 3:43 AM

DotNetKicks.com said:

You've been kicked (a good thing) - Trackback from DotNetKicks.com

# July 4, 2009 3:44 AM

WebDevVote.com said:

You are voted (great) - Trackback from WebDevVote.com

# July 4, 2009 3:47 AM

progg.ru said:

Thank you for submitting this cool story - Trackback from progg.ru

# July 4, 2009 3:48 AM

Dieveatiotats said:

А как на вашу рсс-ленту подписаться? что то не пойму

# July 4, 2009 11:29 AM

Dieveatiotats said:

А как на вашу рсс-ленту подписаться? что то не пойму

# July 4, 2009 8:21 PM
Leave a Comment

(required) 

(required) 

(optional)

(required)