asp.net控件開發基礎(18) --------讓DadaSource接受過多的數據源

本篇繼續上篇的討論,可能大家已經在使用asp.net2.0了,DataSource屬性不再使用,而是跟數據源控件搭配使用.現在討論的綁定技術都是基於1.1版本,先熟悉一下,本質上是一樣的,這樣一步步的學習.對以後絕對有幫助.因爲當你使用數據源控件,只需要設置一個DataSourceID,方便的同時你是否知道數據源控件幫你做了什麼事情,如果你想覺的夠用了,可以不用瞭解,但我相信你一定會有需求.

上篇最後說過了,討論還剛剛開始,我們大致把核心的方法都寫出來了.下面我們繼續.


一.控件對比

我們可以使用上篇製作的TemplatedList控件跟內置控件做一下對比異同

在2.0未到來的時候,我們只有Repeater,DataList,DataGrid,現在我們也根據這三個控件進行討論,下面把

TemplatedList與DataList進行對比

(1)佈局樣式沒DataList多...
(2)模板沒DataList多...
(3)TemplatedList沒ItemCollection
(4)TemplatedList沒有預定義Command事件(如EditCommand,UpdateCommand等)

或者還有更多的,上面的都是次要的,佈局上面我們可以改善,我們也可以添加ItemCollection,也可以預定義Command事件,但發現TemplatedList跟內置的綁定控件有幾個跟數據操作嚴重的不同點

(1)DataSource屬性類型不同  IEnumerable和Object

爲什麼要將其類型設置爲Object呢?
IEnumerable支持Array,ArrayList等返回類型,但卻不支持DataSet類型,這是一個很嚴重的問題,設置其類型爲Object,可以讓控件支持更廣泛的數據源(當然也要根據需求)這個是本次討論的重點

(2)DataMember 

其用於指定數據源的特定表,由於DataSet的介入,其可能含有多個表,所以也就有了這個屬性,否則的話就不需要他

(3)DataKeyField鍵字段

由於預定義Command事件的介入,實現對數據的操作,DataKeyField用於幫助數據特定記錄的操作

二.確定目標

根據上面的對比,我們已經知道接下來要做什麼了,要讓控件DataSouce屬性支持更多的數據源(只要還是DataSet)

本次的demo我們將要模仿Repeater來製作,爲什麼不用TemplatedList?因爲這樣我們可以對更多控件的實現更加的熟悉,這樣在使用內置控件的時候,你將明白的更透徹.此處的demo來自Building ASP.NET Server Controls書中的例子

Repeater與TemplatedList的異同

不同點

大家都知道Repeater可以靈活的進行佈局,所以去掉了模板樣式屬性,我們爲其添加了多個模板屬性,Repeater控件沒有預定義Command事件,所以不需要DataKeyField屬性.

還爲Repeater定義了TemplatedListmy沒有的ItemCollection集合,當然也可以爲TemplatedList添加這個集合

最大的不同

Repeater支持DataSet,TemplatedList不支持

相同點

都是數據綁定控件,所以裏面很多的實現方法幾乎相同,如果你看過TemplatedList的實現,再看Repeater的代碼,基本沒有難度,Repeater的實現比TemplatedList還要簡單.

好了,下面我們開始吧.

三.實現

1.爲數據控件做好準備

幾乎跟上篇一樣,所以不再介紹




2.編寫Repeater



(1)定義成員屬性和事件


        private object dataSource;
        
/// <summary>
        
/// 綁定的列表的數據源
        
/// </summary>

        [Category("Data"), Description("綁定的列表的數據源"),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
        DefaultValue(
null), Bindable(true)]
        
public object DataSource
        
{
            
get
            
{
                
return dataSource;
            }

            
set
            
{
                
if ((value is IEnumerable) || (value is IListSource) || (value == null))
                    dataSource 
= value;
                
else
                    
throw new Exception("錯誤的數據源類型");
            }

        }


        
/// <summary>
        
/// 當數據綁定到列表數據源時要提取的數據成員
        
/// </summary>

        [Category("Data"), Description("當數據綁定到列表數據源時要提取的數據成員")]
        
public virtual string DataMember
        
{
            
get
            
{
                
object member = ViewState["DataMember"];
                
if (member == null)
                    
return string.Empty;
                
else
                    
return (string)member;
            }

            
set
            
{
                ViewState[
"DataMember"= value;
            }

        }


主要的變化在於DataSource,類型更改爲object其對傳入的數據源進行判斷,另外還加入了DataMember屬性

(2)關鍵實現

1.因爲Repeater模板不具有樣式屬性,所以去掉了PrepareControlHierarchy方法,
2.由於不涉及到複雜的樣式屬性,所以不必重載視圖狀態管理的三個方法

這兩點就可以讓控件減少很多代碼的編寫

3.CreateControlHierarchy方法和CreateItem方法
Repeater模板的實現方法和TemplatedList稍有不同,但變化不大,應該容易理解.看下面代碼


        /// <summary>
        
/// 創建控件各種項
        
/// </summary>
        
/// <param name="itemIndex"></param>
        
/// <param name="itemType"></param>
        
/// <param name="dataBind"></param>
        
/// <param name="dataItem"></param>
        
/// <returns></returns>

        private RepeaterItem CreateItem(int itemIndex, ListItemType itemType, bool dataBind, object dataItem)
        
{
            ITemplate selectedTemplate;
            
//根據不同類型創建不同項
            switch (itemType)
            
{
                
case ListItemType.Header:
                    selectedTemplate 
= headerTemplate;
                    
break;
                
case ListItemType.Item:
                    selectedTemplate 
= itemTemplate;
                    
break;
                
case ListItemType.AlternatingItem:
                    selectedTemplate 
= alternatingItemTemplate;
                    
break;
                
case ListItemType.Separator:
                    selectedTemplate 
= separatorTemplate;
                    
break;
                
case ListItemType.Footer:
                    selectedTemplate 
= footerTemplate;
                    
break;
                
default:
                    selectedTemplate 
= null;
                    
break;
            }


            
if ((itemType == ListItemType.AlternatingItem) &&
               (alternatingItemTemplate 
== null))
            
{
                selectedTemplate 
= itemTemplate;
                itemType 
= ListItemType.Item;
            }


            RepeaterItem item 
= new RepeaterItem(itemIndex, itemType, dataItem);

            
if (selectedTemplate != null)
            
{
                selectedTemplate.InstantiateIn(item);
            }


            OnItemCreated(
new RepeaterItemEventArgs(item));

            Controls.Add(item);

            
if (dataBind)
            
{
                item.DataBind();
                OnItemDataBound(
new RepeaterItemEventArgs(item));
            }

            
return item;
        }


        
private ArrayList items = null;
        
private void CreateControlHierarchy(bool useDataSource)
        
{
            items 
= new ArrayList();
            IEnumerable ds 
= null;

            
if (HeaderTemplate != null)
            
{
                RepeaterItem header 
= CreateItem(-1, ListItemType.Header, falsenull);
            }


            
int count = -1;
            
if (useDataSource)
            
{
                
//解析DataSource
                ds = (IEnumerable)DataSourceHelper.ResolveDataSource(DataSource,
                   DataMember);
            }

            
else
            
{
                count 
= (int)ViewState["ItemCount"];
                
if (count != -1)
                
{
                    ds 
= new DummyDataSource(count);
                }

            }


            
if (ds != null)
            
{
                
int index = 0;
                count 
= 0;
                RepeaterItem item;
                ListItemType itemType 
= ListItemType.Item;

                
foreach (object dataItem in (IEnumerable)ds)
                
{
                    
if (index != 0)
                    
{
                        RepeaterItem separator 
= CreateItem(-1, ListItemType.Separator, falsenull);
                    }


                    item 
= CreateItem(index, itemType, useDataSource, dataItem);
                    items.Add(item);
                    index
++;
                    count
++;

                    
if (itemType == ListItemType.Item)
                        itemType 
= ListItemType.AlternatingItem;
                    
else
                        itemType 
= ListItemType.Item;
                }

            }


            
if (FooterTemplate != null)
            
{
                RepeaterItem footer 
= CreateItem(-1, ListItemType.Footer, falsenull);
            }


            
if (useDataSource)
            
{
                ViewState[
"ItemCount"= ((ds != null? count : -1);
            }

        }

其中最大的變化在於這裏,因爲還需要支持DataSet,DataSourceHelper類負責解析傳入的數據源DataSouce進行解析

            if (useDataSource)
            
{
                
//解析DataSource
                ds = (IEnumerable)DataSourceHelper.ResolveDataSource(DataSource,
                   DataMember);
            }

下面我們來重點看DataSourceHelper類

DataSourceHelper類可謂是這一篇的重頭戲,關鍵就在於這裏的理解.這裏搞明白了,纔算是明白.一起來看吧


 1    /// <summary>
 2    /// 一個解析DataSource的輔助類
 3    /// </summary>

 4    public class DataSourceHelper
 5    {
 6        public static object ResolveDataSource(object dataSource, string dataMember)
 7        {
 8            #region 如果數據源爲空,則返回空值
 9
10            if (dataSource == null)
11                return null;
12
13            #endregion

14
15            #region 如果數據源不爲空,且爲IEnumerable類型,則返回IEnumerable
16
17            if (dataSource is IEnumerable)
18            {
19                return (IEnumerable)dataSource;
20            }

21
22            #endregion

23
24            #region 如果數據源不爲空,且爲IListSource類型,則返回IListSource
25
26            else if (dataSource is IListSource)
27            {
28                IList list = null;
29                IListSource listSource = (IListSource)dataSource;
30                list = listSource.GetList();
31                #region 判斷是否爲IList對象集合的值
32                if (listSource.ContainsListCollection)
33                {
34                    //提供發現可綁定列表架構的功能,其中可用於綁定的屬性不同於要綁定到的對象的公共屬性
35                    ITypedList typedList = (ITypedList)list;
36                    //返回表示用於綁定數據的每項上屬性集合
37                    PropertyDescriptorCollection propDescCol =
38                       typedList.GetItemProperties(new PropertyDescriptor[0]);  //was (null)
39
40                    //如果屬性說明符數目爲0
41                    if (propDescCol.Count == 0)
42                        throw new Exception("ListSource without DataMembers");
43
44                    PropertyDescriptor propDesc = null;
45
46                    #region 判斷dataMember字符數給propDesc賦值
47                    //獲取屬性描述符
48                    //若不指定dataMember屬性則獲取默認數據成員
49                    if ((dataMember == null|| (dataMember.Length < 1))
50                    {
51                        propDesc = propDescCol[0];
52                    }

53                    else  
54                        //嘗試在屬性集合衆尋找數據成員
55                        propDesc = propDescCol.Find(dataMember, true);
56
57                    #endregion

58
59                    if (propDesc == null)
60                        throw new Exception("ListSource missing DataMember");
61                    
62                    object listitem = list[0];
63
64                    //獲取DataTable
65                    object member = propDesc.GetValue(listitem);
66
67                    if ((member == null|| !(member is IEnumerable))
68                        throw new Exception("ListSource missing DataMember");
69
70                    return (IEnumerable)member;
71                }

72                else
73                    //若不包含Ilist集合,則直接返回
74                    return (IEnumerable)list;  //robcamer added (IEnumerable)
75
76                #endregion

77            }

78
79            #endregion

80            return null;
81
82        }

83    }


這個輔助類判斷太多,剛看會看暈掉的,所以在if判斷這裏把代碼摺疊起來,有助於理解

這裏有幾個類可能沒見過,我們把關鍵用到的類一一列出來,希望大家查查MSDN

1.IListSource  向對象提供返回可以綁定到數據源列表的功能

2.ITypedList   提供發現可綁定列表架構的功能,其中可用於綁定的屬性不同於要綁定到的對象的公共屬性

3.PropertyDescriptor  提供類上的屬性的抽象化

4.PropertyDescriptorCollection 表示 PropertyDescriptor 對象的集合


下面開始

(1).首先如果傳入的數據源類型是IEnumerable的話,很好,可以直接返回

            if (dataSource is IEnumerable)
            
{
                
return (IEnumerable)dataSource;
            }

(2).轉化實現IListSource接口的類

雖然傳入的類型非
IEnumerable,如DataSet類實現了IListSource接口,其目的就是使用此接口的GetList方法返回一個IList(IList繼承IEnumerable,可以進行數據綁定),大家可以參考MSDN的原話

                IList list = null;
                IListSource listSource 
= (IListSource)dataSource;
                list 
= listSource.GetList();

假設傳入的是DataSet,list將會得到System.Data.DataViewManager集合

DataViewManager是什麼呢?爲默認DataTable默認的DataViewSettingCollection
DataViewSettingCollection是什麼呢?表示DataTable的DataViewSetting的集合
DataViewSetting是什麼呢?表示從 DataViewManager 創建的 DataView 的 的默認設置
上面的我們不熟,DataView大家應該熟悉,其可以對數據進行排序,過濾等

DataViewManager爲一個默認的DataView設置集合,不知這樣是否可以理解的好些.

我們的目的則是將其轉化到IEnumerable類型,繼續

DataViewManager實現了ITypedList接口

我們需要將DataViewManager(即list)轉化到ITypedList ,爲什麼?ITypedList的GetItemProperties方法將幫助你獲取DataView數據綁定的數據對象,而非DataView本身屬性

ITypedList的GetItemProperties方法綁定數據的每項屬性的PropertyDescriptorCollection集合,
PropertyDescriptorCollection表示PropertyDescriptor集合,PropertyDescriptor這個類很好玩,等同於屬性的說明書,即用了.net的反射技術,大家可以嘗試一下,其實以前也用過這個類.下面來看代碼片段

                    //提供發現可綁定列表架構的功能,其中可用於綁定的屬性不同於要綁定到的對象的公共屬性
                    ITypedList typedList = (ITypedList)list;
                    
//返回表示用於綁定數據的每項上屬性集合
                    PropertyDescriptor[] pd = new PropertyDescriptor[0];
                    PropertyDescriptorCollection propDescCol 
=
                       typedList.GetItemProperties(pd);  
//was (null)

                    
//如果屬性說明符數目爲0
                    if (propDescCol.Count == 0)
                        
throw new Exception("ListSource without DataMembers");

GetItemProperties方法傳入了一個PropertyDescriptor的數組,大家可能注意到了傳入的數組爲一個空數組,你還可以傳入一個空引用

PropertyDescriptorCollection propDescCol =
                       typedList.GetItemProperties(
null);  //was (null)

如果你爲DataTable創建了DataView,將調用空引用返回DataSet中的一個DataTable,其將返回一個表集合列的屬性描述符,繼續看下去,該到DataMember出場的時候了,DataMember可以選擇數據集中的特定表,
如何不設置DataMember,將獲取默認表,看下面代碼片段

     #region 判斷dataMember字符數給propDesc賦值
                    
//獲取屬性描述符
                    
//若不指定dataMember屬性則獲取默認數據成員
                    if ((dataMember == null|| (dataMember.Length < 1))
                    
{
                        propDesc 
= propDescCol[0];
                    }

                    
else  
                        
//嘗試在屬性集合中尋找數據成員
                        propDesc = propDescCol.Find(dataMember, true);

                    
#endregion


                    
if (propDesc == null)
                        
throw new Exception("ListSource missing DataMember");
                    

這樣我們就得到了一個DataTablePropertyDescriptor屬性描述符,繼續



                    object listitem = list[0];

                    
//獲取組件屬性當前值
                    object member = propDesc.GetValue(listitem);

                    
if ((member == null|| !(member is IEnumerable))
                        
throw new Exception("ListSource missing DataMember");

                    
return (IEnumerable)member;

此處實現原理:
DataViewManager會在其DataSet中的DataTableCollection中搜索datamember的值進行匹配,看下圖,做這麼多事情,我們一直在轉換



注GetValue用法

        PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(Button1);
        PropertyDescriptor pd 
= properties.Find("Text"false);
        Button b
=new Button();
        b.Text 
= "cc";
        
object c=pd.GetValue(b);
        Response.Write(c);
        
//return cc


用GetValue方法獲取listitem屬性值,此屬性跟datamember匹配,最後member得到的是一個DataView,

DataView實現了IEnumerable,現在終於可以轉換了

到此爲止就結束了,現在你可以成功的傳入DataSet了.
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章