[ASP.NET] HierarchicalObjectDataSource

[ASP.NET] : HierarchicalObjectDataSource


前言 :

ASP.NET 3.5提供了,網頁上常使用到的樹狀選單TreeView。
可是TreeView的資料繫結,並沒有支援ObjectDataSource。
這篇文章分享如何實做ObjectDataSource,來支援TreeView的資料繫結功能。


分析設計 :

TreeView的DataSource只支援實做IHierarchicalDataSource介面的物件。
IHierarchicalDataSource這個看似簡單的介面,裡面卻包含了很多子界面。
將整個IHierarchicalDataSource拆解開來,並且經過分析設計之後的物件圖如下 :



每個物件的職責就不贅述了,大多為符合IHierarchicalDataSource所規範的功能而實做。
整個設計比較特別的只有IHierarchicalObjectDataFactory介面。
這個介面其實是多餘的,只是為了不讓HierarchicalObjectDataEnumerable及HierarchicalObjectData有循環相依。


執行結果 :


使用範例 :


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="CLK.Web.UI.WebApplication._Default" %> 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml" > 
<head id="Head1" runat="server"> 
<title></title> 
</head> 
<body> 
<form id="form1" runat="server"> 
<div> 
<asp:TreeView ID="TreeView1" runat="server"> 
<DataBindings> 
<asp:TreeNodeBinding TextField="Name" ValueField="Id" /> 
</DataBindings> 
</asp:TreeView> 
</div> 
</form> 
</body> 
</html>

namespace CLK.Web.UI.WebApplication
{
    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (this.IsPostBack == false)
            {
                List<IHierarchicalObject> demoDataList = new List<IHierarchicalObject>();
                demoDataList.Add(new DemoData(null, "AAA", "Id_AAA", "Name_AAA"));
                demoDataList.Add(new DemoData(null, "BBB", "Id_BBB", "Name_BBB"));
                demoDataList.Add(new DemoData(null, "CCC", "Id_CCC", "Name_CCC"));
                demoDataList.Add(new DemoData("BBB", "DDD", "Id_DDD", "Name_DDD"));
                demoDataList.Add(new DemoData("DDD", "EEE", "Id_EEE", "Name_EEE"));
                demoDataList.Add(new DemoData(null, "FFF", "Id_FFF", "Name_FFF"));

                HierarchicalObjectDataSource dataSource = new HierarchicalObjectDataSource(demoDataList);
                TreeView1.DataSource = dataSource;
                TreeView1.DataBind();
            }
        }
    }

    public class DemoData : IHierarchicalObject
    {
        // Properties 
        private readonly string _parentPath;

        private readonly string _path;

        private readonly string _id;

        private readonly string _name;


        // Construction 
        public DemoData(string parentPath, string path, string id, string name)
        {
            #region Require

            if (path == null) throw new ArgumentNullException();
            if (string.IsNullOrEmpty(id) == true) throw new ArgumentNullException();
            if (string.IsNullOrEmpty(name) == true) throw new ArgumentNullException();

            #endregion
            _parentPath = parentPath;
            _path = path;
            _id = id;
            _name = name;
        }


        // Member 
        public string ParentPath
        {
            get { return _parentPath; }
        }

        public string Path
        {
            get { return _path; }
        }

        public string Id
        {
            get { return _id; }
        }

        public string Name
        {
            get { return _name; }
        }
    }
}

實作 :


namespace CLK.Web.UI
{
    public class HierarchicalObjectDataSource : IHierarchicalDataSource
    {
        // Properties 
        private readonly IEnumerable<IHierarchicalObject> _objectEnumerable;


        // Construction 
        public HierarchicalObjectDataSource(IEnumerable<IHierarchicalObject> objectEnumerable)
        {
            #region Require

            if (objectEnumerable == null) throw new ArgumentNullException();

            #endregion
            _objectEnumerable = objectEnumerable;
        }


        // Member 
        public HierarchicalDataSourceView GetHierarchicalView(string viewPath)
        {
            return new HierarchicalObjectDataSourceView(_objectEnumerable, viewPath);
        }


        // Event 
        public event EventHandler DataSourceChanged;
        protected void OnDataSourceChanged(object sender, EventArgs e)
        {
            if (this.DataSourceChanged != null)
            {
                this.DataSourceChanged(sender, e);
            }
        }
    }

    public class HierarchicalObjectDataSourceView : HierarchicalDataSourceView
    {
        // Properties 
        private readonly IEnumerable<IHierarchicalObject> _objectEnumerable;

        private readonly IHierarchicalObject _currentObject;

        private readonly IHierarchicalObjectDataFactory _objectFactory;


        // Construction 
        public HierarchicalObjectDataSourceView(IEnumerable<IHierarchicalObject> objectEnumerable, string path)
        {
            #region Require

            if (objectEnumerable == null) throw new ArgumentNullException();
            if (path == null) throw new ArgumentNullException();

            #endregion
            _objectFactory = new HierarchicalObjectDataFactory();

            _objectEnumerable = objectEnumerable;

            _currentObject = null;
            foreach (IHierarchicalObject currentObject in _objectEnumerable)
            {
                if (currentObject.Path == path)
                {
                    _currentObject = currentObject;
                    break;
                }
            }
        }


        // Member 
        public override IHierarchicalEnumerable Select()
        {
            return _objectFactory.CreateHierarchicalEnumerable(_objectEnumerable, _currentObject);
        }
    }

    public class HierarchicalObjectDataFactory : IHierarchicalObjectDataFactory
    {
        // Member 
        public IHierarchicalEnumerable CreateHierarchicalEnumerable(IEnumerable<IHierarchicalObject> objectEnumerable, IHierarchicalObject currentObject)
        {
            return new HierarchicalObjectDataEnumerable(objectEnumerable, currentObject, this);
        }

        public IHierarchyData CreateHierarchyData(IEnumerable<IHierarchicalObject> objectEnumerable, IHierarchicalObject currentObject)
        {
            return new HierarchicalObjectData(objectEnumerable, currentObject, this);
        }
    }

    public interface IHierarchicalObjectDataFactory
    {
        IHierarchicalEnumerable CreateHierarchicalEnumerable(IEnumerable<IHierarchicalObject> objectEnumerable, IHierarchicalObject currentObject);

        IHierarchyData CreateHierarchyData(IEnumerable<IHierarchicalObject> objectEnumerable, IHierarchicalObject currentObject);
    }

    public class HierarchicalObjectDataEnumerable : IHierarchicalEnumerable
    {
        // Properties 
        private readonly IEnumerable<IHierarchicalObject> _objectEnumerable;

        private readonly IHierarchicalObject _currentObject;

        private readonly IHierarchicalObjectDataFactory _objectFactory;


        // Construction 
        public HierarchicalObjectDataEnumerable(IEnumerable<IHierarchicalObject> objectEnumerable, IHierarchicalObject currentObject, IHierarchicalObjectDataFactory objectFactory)
        {
            #region Require

            if (objectEnumerable == null) throw new ArgumentNullException();
            //if (currentObject == null) throw new ArgumentNullException(); 
            if (objectFactory == null) throw new ArgumentNullException();

            #endregion
            _objectEnumerable = objectEnumerable;
            _currentObject = currentObject;
            _objectFactory = objectFactory;
        }


        // Member 
        public IHierarchyData GetHierarchyData(object enumeratedItem)
        {
            #region Require

            if (enumeratedItem == null) throw new ArgumentNullException();

            #endregion
            if (enumeratedItem is IHierarchicalObject)
            {
                return _objectFactory.CreateHierarchyData(_objectEnumerable, enumeratedItem as IHierarchicalObject);
            }
            return null;
        }

        public System.Collections.IEnumerator GetEnumerator()
        {
            return new HierarchicalObjectDataEnumerator(_objectEnumerable.GetEnumerator(), _currentObject);
        }
    }

    public class HierarchicalObjectDataEnumerator : IEnumerator<IHierarchicalObject>
    {
        // Properties 
        private readonly IEnumerator<IHierarchicalObject> _objectEnumerator;

        private readonly IHierarchicalObject _currentObject;

        private IHierarchicalObject _currentChildrenObject = null;


        // Construction 
        public HierarchicalObjectDataEnumerator(IEnumerator<IHierarchicalObject> objectEnumerator, IHierarchicalObject currentObject)
        {
            #region Require

            if (objectEnumerator == null) throw new ArgumentNullException();
            //if (currentObject == null) throw new ArgumentNullException(); 

            #endregion
            _objectEnumerator = objectEnumerator;
            _currentObject = currentObject;
        }

        public void Dispose()
        {
            _objectEnumerator.Dispose();
        }


        // Member 
        public IHierarchicalObject Current
        {
            get
            {
                return _currentChildrenObject;
            }
        }

        object System.Collections.IEnumerator.Current
        {
            get
            {
                return this.Current;
            }
        }

        public bool MoveNext()
        {
            while (_objectEnumerator.MoveNext() == true)
            {
                if (_currentObject != null)
                {
                    if (_objectEnumerator.Current.ParentPath == _currentObject.Path)
                    {
                        _currentChildrenObject = _objectEnumerator.Current;
                        return true;
                    }
                }
                else
                {
                    if (_objectEnumerator.Current.ParentPath == null)
                    {
                        _currentChildrenObject = _objectEnumerator.Current;
                        return true;
                    }
                }
            }

            _currentChildrenObject = null;
            return false;
        }

        public void Reset()
        {
            _objectEnumerator.Reset();
            _currentChildrenObject = null;
        }
    }

    public class HierarchicalObjectData : IHierarchyData
    {
        // Properties 
        private readonly IEnumerable<IHierarchicalObject> _objectEnumerable;

        private readonly IHierarchicalObject _currentObject;

        private readonly IHierarchicalObjectDataFactory _objectFactory;


        // Construction 
        public HierarchicalObjectData(IEnumerable<IHierarchicalObject> objectEnumerable, IHierarchicalObject currentObject, IHierarchicalObjectDataFactory objectFactory)
        {
            #region Require

            if (objectEnumerable == null) throw new ArgumentNullException();
            if (currentObject == null) throw new ArgumentNullException();
            if (objectFactory == null) throw new ArgumentNullException();

            #endregion
            _objectEnumerable = objectEnumerable;
            _currentObject = currentObject;
            _objectFactory = objectFactory;
        }


        // Member 
        public IHierarchicalEnumerable GetChildren()
        {
            return _objectFactory.CreateHierarchicalEnumerable(_objectEnumerable, _currentObject);
        }

        public IHierarchyData GetParent()
        {
            if (_currentObject.ParentPath != null)
            {
                foreach (IHierarchicalObject parentObject in _objectEnumerable)
                {
                    if (parentObject.Path == _currentObject.ParentPath)
                    {
                        return _objectFactory.CreateHierarchyData(_objectEnumerable, parentObject);
                    }
                }
            }
            return null;
        }

        public bool HasChildren
        {
            get
            {
                foreach (object childrenObject in this.GetChildren())
                {
                    return true;
                }
                return false;
            }
        }

        public object Item
        {
            get { return _objectFactory.CreateHierarchyData(_objectEnumerable, _currentObject); }
        }

        public string Path
        {
            get { return _currentObject.Path; }
        }

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

    public interface IHierarchicalObject
    {
        string Path { get; }

        string ParentPath { get; }
    }
}
期許自己
能以更簡潔的文字與程式碼,傳達出程式設計背後的精神。
真正做到「以形寫神」的境界。