ASP.NET進階(7):認清控制項之DataBind

來源:互聯網
上載者:User

    資料繫結,databind()是多麼親切的方法,我們每天很不能敲上幾百遍。但是你有木有想過他是如何?的?有木有!!!(咆哮體:)今天,我們拿Repeater來說說DataBind那些事兒。如果朋友你有看過我寫的模版引擎裡Label的初級應用的話,我在最後貼了一個List的Label的實現,其中有點意思的就是模仿Repeater。不過看沒看過無所謂的。今天我們看看微軟人家正兒八經的Repeater。

    一般控制項的綁定我們都是在Page_Load事件方法裡這麼寫

if(!IsPostBack) {     BindList(); } 

而BindList方法就是我們繫結控制項用的方法,獨立出來的目的是可能多處使用這個方法。很明顯,我們一般都是這麼綁定的。

public void BindList() {     lst.DataSource = DataHelper.GetList();     lst.DataBind(); }

那麼,我們就根據咱們寫的代碼一看綁定的究竟。

第一步就是我們給DataSource成員賦值,看看賦值的時候發生了什嗎?

public virtual object DataSource {     [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]     get     {         return this.dataSource;     }     set     {         if (((value != null) && !(value is IListSource)) && !(value is IEnumerable))         {             throw new ArgumentException(SR.GetString("Invalid_DataSource_Type", new object[] { this.ID }));         }         this.dataSource = value;         this.OnDataPropertyChanged();     } }

 
從上面的方法中我們可以看出DataSource還真不簡單,一個賦值就幹了很多事,其實get和set是兩個方法(get_dataSource、set_dataSource),只是C#裡這麼寫比較好看些。

如果我們指定的datasource不是IEnumerable(可迭代),不是IListSource類型,則拋出異常(參數錯誤:無效的資料來源),說明肯定要用到IListSource的方法,而且要迴圈使用(Repeater肯定的迴圈啊)。

然後執行了OnDataPropertyChanged()的方法。看名字就能猜到該方法就是一個地下黨,他會告訴類裡面的某些成員說:“嘿,夥計,資料來源變啦,各單位注意!”,這時候Repeater去擷取資料的時候會問這個地下黨:”哥們,現在風頭緊不緊。。。“

protected virtual void OnDataPropertyChanged() {     if (this._throwOnDataPropertyChange)     {         throw new HttpException(SR.GetString("DataBoundControl_InvalidDataPropertyChange", new object[] { this.ID }));     }     if (this._inited)     {         this.RequiresDataBinding = true;     }     this._currentViewValid = false; } 

第一句拋出異常就是”內部組織“決定殺人滅口,根本沒機會了。第二句,_inited,是否初始化過,_currentViewValid,意思是當前的DataView是否驗證過?(意思是這資料來源來源不明啊)

第二句話就是執行DataBind()了,DataBind就會用到上面的”地下組織“。

public override void DataBind() {     if ((!this.IsBoundUsingDataSourceID || !base.DesignMode) || (base.Site != null))     {         this.RequiresDataBinding = false;         this.OnDataBinding(EventArgs.Empty);     } } 

就是說是否使用DataSourceID來綁定? 顯然我們不是,我們直接給定的資料來源。 如果指定了DataSourceID,在Render Repeater時會搜尋名字為DataSourceID的DataSource控制項。這是一種前台指定資料來源的方式。不過最終都是要執行DataBind。我們來看Repeater的OnDataBinding:

protected override void OnDataBinding(EventArgs e) {     base.OnDataBinding(e);//執行了父類Control的OnDataBinding事件     this.Controls.Clear();//清空子控制項     base.ClearChildViewState();//清空子控制項的ViewState資訊     this.CreateControlHierarchy(true);//建立子控制項     base.ChildControlsCreated = true;//標記已建立 } 

父類的OnDataBingding事件很簡單:

protected virtual void OnDataBinding(EventArgs e) {     if (this.HasEvents())     {         EventHandler handler = this._events[EventDataBinding] as EventHandler;         if (handler != null)         {             handler(this, e);         }     } }

 
就是查看是否訂閱了DataBingding事件方法,訂閱即執行,我們一般都是給Repeater的ItemTemplate子控制項裡綁定資料的。

清空的我們就不看了,都是一些remove。我們看看CreateControlHierarchy:

/// <summary> /// 建立子控制項 /// </summary> /// <param name="useDataSource">是否使用資料來源</param> protected virtual void CreateControlHierarchy(bool useDataSource) {     IEnumerable data = null;//聲明資料     int dataItemCount = -1;//資料的個數     if (this.itemsArray != null)     {         this.itemsArray.Clear();//看看還有模版沒,有就清掉     }     else     {         this.itemsArray = new ArrayList();//沒有就初始化一個     }     if (!useDataSource)//如果不使用的資料來源,就直接從ViewState裡初始化容量,並弄了偽資料     {         dataItemCount = (int) this.ViewState["_!ItemCount"];         if (dataItemCount != -1)         {             data = new DummyDataSource(dataItemCount);             this.itemsArray.Capacity = dataItemCount;         }     }     else     {         data = this.GetData();//如果指定了資料來源,則擷取資料         ICollection is2 = data as ICollection;         if (is2 != null)         {             this.itemsArray.Capacity = is2.Count;         }     }     if (data != null)//如果資料正常     {         int itemIndex = 0;         bool flag = this.separatorTemplate != null;//是否有分隔字元模版         dataItemCount = 0;         if (this.headerTemplate != null)//如果有頭部,則建立頭部         {             this.CreateItem(-1, ListItemType.Header, useDataSource, null);//顯然 -1 不算行數,類型是頭部,使用資料來源,且頭部行使用的資料居然是null...         }         foreach (object obj2 in data)//遍曆資料         {             if (flag && (dataItemCount > 0))             {                 this.CreateItem(itemIndex - 1, ListItemType.Separator, useDataSource, null);//分隔字元             }             ListItemType itemType = ((itemIndex % 2) == 0) ? ListItemType.Item : ListItemType.AlternatingItem;             RepeaterItem item = this.CreateItem(itemIndex, itemType, useDataSource, obj2);//Item和Alter交替建立             this.itemsArray.Add(item);             dataItemCount++;             itemIndex++;         }         if (this.footerTemplate != null)         {             this.CreateItem(-1, ListItemType.Footer, useDataSource, null);//建立底部         }     }     if (useDataSource)     {         this.ViewState["_!ItemCount"] = (data != null) ? dataItemCount : -1;//給ViewState的ItemCount賦值     } }

從上面的注釋中我們能看出就是迴圈建立Item。這些Item就是我們常用的HeadItemTemplate  ItemTemplate等子控制項啦。把資料傳給他們,讓他們自己實現資料繫結。話說我的模版引擎也這麼做的哦:),但是我真的沒抄襲他,哈哈。

其中的GetData方法就是擷取資料的:

protected virtual IEnumerable GetData(){    DataSourceView view = this.ConnectToDataSourceView();    if (view != null)    {        return view.ExecuteSelect(this.SelectArguments);    }    return null;}

ConnectToDataSourceView()方法比較長其主要工作就是拿資料來源:

private DataSourceView ConnectToDataSourceView(){    if (!this._currentViewValid || base.DesignMode)//資料來源沒有驗證過,指定DataSource的值就會_currentViewValid  = false    {        if ((this._currentView != null) && this._currentViewIsFromDataSourceID)        {            this._currentView.DataSourceViewChanged -= new EventHandler(this.OnDataSourceViewChanged);        }        IDataSource source = null;        string dataSourceID = this.DataSourceID;        if (dataSourceID.Length != 0)//如果是指定了DataSourceID的資料來源,就FindControl 找到對應的DataSource控制項        {            Control control = DataBoundControlHelper.FindControl(this, dataSourceID);            if (control == null)            {                throw new HttpException(SR.GetString("DataControl_DataSourceDoesntExist", new object[] { this.ID, dataSourceID }));            }            source = control as IDataSource;            if (source == null)            {                throw new HttpException(SR.GetString("DataControl_DataSourceIDMustBeDataControl", new object[] { this.ID, dataSourceID }));            }        }        if (source == null)//如果沒有從控制項找到資料,就用我們指定的資料來源        {            source = new ReadOnlyDataSource(this.DataSource, this.DataMember);        }        else if (this.DataSource != null)//如果兩個資料來源都存在就拋出異常        {            throw new InvalidOperationException(SR.GetString("DataControl_MultipleDataSources", new object[] { this.ID }));        }        DataSourceView view = source.GetView(this.DataMember);        if (view == null)        {            throw new InvalidOperationException(SR.GetString("DataControl_ViewNotFound", new object[] { this.ID }));        }        this._currentViewIsFromDataSourceID = this.IsBoundUsingDataSourceID;        this._currentView = view;        if ((this._currentView != null) && this._currentViewIsFromDataSourceID)        {            this._currentView.DataSourceViewChanged += new EventHandler(this.OnDataSourceViewChanged);        }        this._currentViewValid = true;    }    return this._currentView;}

我們關注下CreateItem方法,因為實際的資料顯示地方在哪裡,這裡只是把大體架構做好。

/// <summary> /// 建立Repeater的Item /// </summary> /// <param name="itemIndex">第幾行</param> /// <param name="itemType">Item的類型</param> /// <param name="dataBind">是否綁定資料</param> /// <param name="dataItem">本行資料</param> private RepeaterItem CreateItem(int itemIndex, ListItemType itemType, bool dataBind, object dataItem) {     RepeaterItem item = this.CreateItem(itemIndex, itemType);//先聲明個Item     RepeaterItemEventArgs e = new RepeaterItemEventArgs(item);//聲明個事件參數     this.InitializeItem(item);//給repeater的各種template賦值     if (dataBind)     {         item.DataItem = dataItem;//如果要綁定資料則把資料指定給DataItem屬性     }     this.OnItemCreated(e);//執行建立Item的事件(其實我們貌似都沒用過吧)     this.Controls.Add(item);//添加Item     if (dataBind)     {         item.DataBind();//正點,開始綁定資料啦~         this.OnItemDataBound(e);//執行綁定後的事件方法         item.DataItem = null;//卸掉資料,等他記憶體回收     }     return item; } 

Item的dateItem資料就是DataBinder.Eval("xxx")時需要被反射的對象。也就是每個控制項當前的資料,是的,每個控制項,剛才上面我們看到repeater的整了一大堆的item。。。

DataBinder的Eval是個靜態方法,代碼非常簡單,就是靠反射擷取DataItem的值。我們可以看看他的實現:

    public static object Eval(object container, string expression)     {         if (expression == null)         {             throw new ArgumentNullException("expression");         }         expression = expression.Trim();         if (expression.Length == 0)         {             throw new ArgumentNullException("expression");         }         if (container == null)         {             return null;         }         string[] expressionParts = expression.Split(expressionPartSeparator);         return Eval(container, expressionParts);     }    private static object Eval(object container, string[] expressionParts)     {         object propertyValue = container;         for (int i = 0; (i < expressionParts.Length) && (propertyValue != null); i++)         {             string propName = expressionParts[i];             if (propName.IndexOfAny(indexExprStartChars) < 0)             {                 propertyValue = GetPropertyValue(propertyValue, propName);             }             else             {                 propertyValue = GetIndexedPropertyValue(propertyValue, propName);             }         }         return propertyValue;     }    public static string Eval(object container, string expression, string format)     {         object obj2 = Eval(container, expression);         if ((obj2 == null) || (obj2 == DBNull.Value))         {             return string.Empty;         }         if (string.IsNullOrEmpty(format))         {             return obj2.ToString();         }         return string.Format(format, obj2);     }    public static object GetDataItem(object container)     {         bool flag;         return GetDataItem(container, out flag);     }

看看RepeaterItem的DataBind,其實就是Control類的DataBind

public virtual void DataBind() {     this.DataBind(true); } protected virtual void DataBind(bool raiseOnDataBinding) {     bool flag = false;     if (this.IsBindingContainer)//是綁定的容器嗎     {         bool flag2;         object dataItem = DataBinder.GetDataItem(this, out flag2);//擷取該容器的DataItem,在之前賦值過的,內部的執行也是先擷取成員,擷取不到再反射。         if (flag2 && (this.Page != null))//如果擷取到了資料,並且當前Page不為null         {             this.Page.PushDataBindingContext(dataItem);//這句我也沒弄明白用處,就是把當前繫結資料給當前Page綁定內容相關的一個棧裡,這個資料會被Page.GetDataItem()用到,所以也就是Page的Eval才能用到,可是控制項綁定完畢後就被卸載了啊?我沒弄清楚用處:)             flag = true;         }     }     try     {         if (raiseOnDataBinding)         {             this.OnDataBinding(EventArgs.Empty);         }         this.DataBindChildren();//綁定子控制項     }     finally     {         if (flag)         {             this.Page.PopDataBindingContext();//整個綁定完後,卸載這條資料         }     } } 

子控制項的綁定其實就是整個流程的遞迴了,不管你有啥子控制項,咱在讓子控制項來一次DataBind(),方法如下:

protected virtual void DataBindChildren() {     if (this.HasControls())     {         string errorMsg = this._controls.SetCollectionReadOnly("Parent_collections_readonly");         try         {             try             {                 int count = this._controls.Count;                 for (int i = 0; i < count; i++)                 {                     this._controls[i].DataBind();                 }             }             finally             {                 this._controls.SetCollectionReadOnly(errorMsg);             }         }         catch         {             throw;         }     } } 

這樣,整個繫結資料流程就是這樣,Repeater的展現其實就是他的每個Item的展現。

看了CMS模版引擎的同學,我這裡多介紹下關於嵌套的問題。我的CMS模版引擎沒有做嵌套標籤那一塊,如果做的話,也會類似這個方法,不過這個方式並沒有傳遞當前的DataItem給子控制項,因為我們會在ItemDataBounded事件方法裡給子控制項的dataSource賦值。但是在模版引擎了我沒法去自訂方法,所以得傳遞DataItem進去。

感覺編程很有意思是,有時候你思來想去的東西,發現別人早已實現,而且做的異常強大,但這也是自己成長的一個過程吧。之所以分析WebForm的一些源碼,也是在寫了CMS模版後,發現很像WebForm的一些東西,只是沒有他那麼龐大,但有些思路居然出奇的相似。 重複造輪子或許能讓我們更容易把一些東西理解透徹。僅僅做一個程式碼群組裝工沒意思,你說呢?

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.