Thursday, December 25, 2008

Custom Business Objects binding to Hierarchical datasource controls

I am great believer of Object Oriented Concepts and its implementation. Recently, I was looking for a class which will provide me a way to bind my business objects to a hierarchical datasource. I was unable to find it and so finally decided to develop my own hierarchical datasource.
Here is the class which will allow us to bind any of the custom business object collection to .NET Treeview control or Menu control.
The prerequisites are
1) The collection should implement an interface. I named it IHierarchySupport. The interface definition is
public interface IHierarchySupport<T>
{
List
<T> GetChildren(string parentId);
T GetParent (string childId);
bool HasChildren(string parentId);
}


2) Here T is a custom business object. The only consideration in developing the business object should be to override ToString() method and return the id of the object. In my case my object definition looks like

public class SecurityObjectInfo
{
#region Variables
SecurityObjectInfo _parentObject;
#endregion
public int ObjectId
{
get;
internal set;
}
public SecurityObjectInfo ParentObject
{
get
{
return _parentObject;
}
}
public string ObjectName
{
get;
internal set;
}

public string PageName
{
get;
internal set;
}

public SecurityObjectInfo()
{
}

public override string ToString() { return ObjectId.ToString(); }
}

3) Now I need a collection which will hold my business objects. As I am using CSLA framework to develop my business objects my collection is inherited from ReadOnlyListBase. I have removed data access code for having simplicity. This collection will implement the IHierarchySupport interface. It will look like


public class SecurityObjects:ReadOnlyListBase<SecurityObjects,SecurityObjectInfo>,IHierarchySupport<SecurityObjectInfo>
{
private SecurityObjects()
{
}
public static SecurityObjects GetSecurityObjects(string userName)
{
return DataPortal.Fetch (new SingleCriteria(userName));
}

#region IHierarchySupport Members
public List GetChildren(string parentId)
{
List
<T> children;
if (parentId.Equals(string.Empty))
children =this.Where(childObject => childObject.ParentObject == null).ToList();
else
children = this.Where(childObject => childObject.ParentObject != null && childObject.ParentObject.ObjectId.ToString().Equals(parentId)).ToList();
return children;
}

public SecurityObjectInfo GetParent(string childId)

{

SecurityObjectInfo parent = null;
if (!childId.Equals(string.Empty))
parent = this.Where(childObject => childObject.ObjectId.ToString().Equals(childId)).Single();
return parent;
}


public bool HasChildren(string parentId)
{
List
<T> children = this.Where(childObject => childObject.ParentObject != null && childObject.ParentObject.ObjectId.ToString().Equals(parentId)).ToList();
return children.Count > 0 ? true : false;
}

#endregion
}

4) Now we are done with Business Objects part. I need a class which implements IHierarchicalDataSource and should be generic enough to accomodate any business objects collection which implements IHierarchySupport interface. So, my HierarchicalDataSource class looks like

public class HierarchicalDataSource
<T>: IHierarchicalDataSource
{
IHierarchySupport
<T> _collection ;


public HierarchicalDataSource(IHierarchySupport
<T> collection)
{
this._collection = collection;
}

public HierarchicalDataSourceView GetHierarchicalView(string viewPath)
{
return new DataSourceView(this, viewPath);
}

string GetChildrenViewPath(string viewPath, T obj)
{
return viewPath + "\\" + obj.ToString();
}

bool HasChildren(string objectId )
{
return _collection.HasChildren(objectId);
}

string GetParentViewPath(string viewPath)
{
return viewPath.Substring(0, viewPath.LastIndexOf("\\"));
}

#region classes that implement required interfaces
class DataSourceView : HierarchicalDataSourceView
{
HierarchicalDataSource
<T> _collection;
string _viewPath;

public DataSourceView(HierarchicalDataSource
<T> collection, string viewPath)
{
this._collection = collection;
this._viewPath = viewPath;
}

public override IHierarchicalEnumerable Select()
{
return new HierarchicalEnumerable(_collection, _viewPath);
}
}

class HierarchicalEnumerable : IHierarchicalEnumerable
{
HierarchicalDataSource
<T> _collection;
string _viewPath;

public HierarchicalEnumerable(HierarchicalDataSource
<T> collection, string viewPath)
{
this._collection = collection;
this._viewPath = viewPath;
}

public IHierarchyData GetHierarchyData(object enumeratedItem)
{
return new HierarchyData(_collection, _viewPath,(T) enumeratedItem);
}

public IEnumerator GetEnumerator()
{
string parentId = string.Empty;
if (_viewPath == "")
parentId = string.Empty;
else
parentId = _viewPath.Substring(_viewPath.LastIndexOf("\\") + 1);
return _collection._collection.GetChildren(parentId).GetEnumerator();
}
}

class HierarchyData : IHierarchyData
{
HierarchicalDataSource
<T> _collection;
T _object;
string _viewPath;

public HierarchyData(HierarchicalDataSource
<T> collection, string viewPath, T obj)
{
this._collection = collection;
this._viewPath = viewPath;
this._object = obj;
}

public IHierarchicalEnumerable GetChildren()
{
return new HierarchicalEnumerable(_collection, _collection.GetChildrenViewPath(_viewPath, _object));
}

public IHierarchyData GetParent()
{
return new HierarchyData(_collection, _collection.GetParentViewPath(_viewPath), _collection._collection.GetParent(_object.ToString()));
}

public bool HasChildren
{
get
{
return _collection.HasChildren(_object.ToString());
}
}

public object Item
{
get
{
return _object;
}
}

public string Path
{
get
{
return _viewPath;
}
}

public string Type
{
get
{
return _object.ToString();
}
}
}

We are done with development part :).
5) Now we need to implement our development and let me show you how easily can we do this. My code will bind my collection of security objects to menu control.

menu1.DataSource = new HierarchicalDataSource
<SecurityObjectInfo>(User.SecurityObjects);

System.Web.UI.WebControls.MenuItemBinding binding = new System.Web.UI.WebControls.MenuItemBinding();

binding.TextField = "ObjectName";

binding.ValueField = "ObjectId";

binding.NavigateUrlField = "PageName";

menu1.DataBindings.Add(binding);

menu1.DataBind();


Done............ Isn't it cool?


3 comments:

  1. This is really helpful - however, your HierarchicalEnumerable.GetEnumerable() method has no return value, which is pretty critical. What do you return here?

    ReplyDelete
  2. Sorry, the line was missed out some how. It will be return _collection._collection.GetChildren(parentId).GetEnumerator();

    I have resolved it in the blog post as well. Thanks for pointing it out.

    ReplyDelete
  3. Thanks for useful coding and blog.I think it will us in binding business objects to hierarchical data source.

    ReplyDelete