【阿不】深入ASP.NET資料繫結(中)—資料雙向繫結機理

iDotNetSpace發表於2008-07-16

在上一篇《深入ASP.NET資料繫結(上)》中,我們分析了在.NET中的資料繫結語法的一些內部機理。簡單說來就是ASP.NET在執行時為我們完成了頁面的動態編譯,並解析頁面的各種伺服器端程式碼,包括資料繫結語法。而資料繫結的語法雖是一些程式碼塊,在生成的程式碼中,仍然使用了伺服器端控制元件以及在DataBinding事件呼叫DataBinder.Eval方法來完成資料的繫結工作。所有的資料繫結模板控制元件都使用了這樣的機制來進行資料的單向繫結,在.NET 2.0中新增了雙向的資料繫結方式,主要用在GridView,DetailsView,FormView等資料容器控制元件中,結合DataSourceControl就可以非常輕鬆的完成資料的更新和提交工作,而不需要我們手工去遍歷輸入控制元件的值。那在這樣的雙向資料繫結中,ASP.NET又是做了哪些工作,來為我們透明輸入控制元件與欄位的取值與對應關係,讓我們可以在DataSouceControl中方便得到資料項修改前的值和修改後的值?下面就讓我們一起來從一段頁面程式碼開始吧:

   1: <asp:DetailsDataSouce ID="DetailsDataSouce1" runat="server">
   2: asp:DetailsDataSouce>
   3: <asp:DetailsView ID="detailsView" runat="server" DefaultMode="Edit" DataSourceID="DetailsDataSouce1">
   4:     <Fields>
   5:         <asp:TemplateField>
   6:             <HeaderTemplate>
   7:                 電流:HeaderTemplate>
   8:             <EditItemTemplate>
   9:                 <asp:TextBox ID="textBox1" runat="server" Text=''>asp:TextBox>
  10:             EditItemTemplate>
  11:         asp:TemplateField>
  12:     Fields>
  13: asp:DetailsView>

在一個頁面中,定義瞭如上的一個DetailsView控制元件,為這個控制元件指定了ID為DetailsDataSource1的DataSouceControl控制元件,這個控制元件是我們自己定義的一個DataSourceControl,它返回的資料欄位包括:"ID","電流{a}","電壓(v)","備註'","名稱]"。我並沒有設定DetailsView的AutoGenerateRows屬性的值,預設情況下,它是為我們自動的生成這些欄位的對應的資料顯示和輸入控制元件。除此之外,我們還另外新增了一個資料模板欄位,在這個模板中指定了編輯模板。在編輯模板中我使用了這樣的語法,將textBox1與"[電流{a}]"欄位雙向繫結起來。

Thumbsup為什麼這裡的欄位都有一些特殊呢?因為我原先的意圖是除了分析繫結語法以外,還要測試哪些特殊字元無法使用資料繫結語法來繫結資料的。這個在下篇文章中會具體介紹。

ThumbsupBind與Eval不一樣,這樣的Bind並不Page或TemplateControl的一個方法,事實上我們應該把它當成一個關鍵字來看待,因為在ASP.NET的雙向資料繫結當中,並沒有這樣的一個函式存在,它的存在是隻是告訴ASP.NET動態編譯頁面類時,將這個語法編譯成一定的程式碼格式,並生成一些函式代理來達到雙向資料交流的目的。

那麼這一段程式碼,動態編譯生成的伺服器程式碼又是如何的呢?讓我們反編譯動態程式集,裡面會找到用於建立DetailsView的__BuildControldetailsView的私有方法,在這裡會呼叫到一些其它內部方法,我們不要讓這些方法來干擾我們的視線,直接找到建立如上模板欄位的方法:

   1: [DebuggerNonUserCode]
   2: private TemplateField __BuildControl__control5()
   3: {
   4:     TemplateField field = new TemplateField();
   5:     field.HeaderTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control6));
   6:     field.EditItemTemplate = new CompiledBindableTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control7), new ExtractTemplateValuesMethod(this.__ExtractValues__control7));
   7:     return field;
   8: }

這裡首先把this.__BuildControl__control6作為一個代理函式,用於建立頭部模板的內容,也就是如上的“電流:”欄位標題。然後才是建立EditItemTemplate,這個模板又被一些的中介模板所代替,我們只需要來關心this.__BuildControl__control7和__ExtractValues__control7即可。__BuildControl__control7是為了編輯資料欄位時,將資料欄位的值顯示在輸入控制元件中(輸入控制元件的初始化,即欄位值繫結到輸入控制元件中);而__ExtractValues__control7則是在提交資料時,要找出這個模板內所有的雙向繫結欄位,將這些欄位的值以繫結欄位名為Key,以輸入控制元件的值為Value新增了IOrderedDictionary字典中。DetailsView等資料繫結控制元件呼叫這些委託代理來收集所有的被雙向繫結的欄位的最新的值。下面分別是兩段函式的程式碼片段:

   1: [DebuggerNonUserCode]
   2: private TextBox __BuildControl__control8()
   3: {
   4:     TextBox box = new TextBox();
   5:     box.TemplateControl = this;
   6:     box.ApplyStyleSheetSkin(this);
   7:     box.ID = "textBox1";
   8:     box.DataBinding += new EventHandler(this.__DataBinding__control8);
   9:     return box;
  10: }
  11: public void __DataBinding__control8(object sender, EventArgs e)
  12: {
  13:     TextBox box = (TextBox) sender;
  14:     IDataItemContainer bindingContainer = (IDataItemContainer) box.BindingContainer;
  15:     if (this.Page.GetDataItem() != null)
  16:     {
  17:         box.Text = Convert.ToString(base.Eval("[電流{a}]"), CultureInfo.CurrentCulture);
  18:     }
  19: }
   1: [DebuggerNonUserCode]
   2: public IOrderedDictionary __ExtractValues__control7(Control __container)
   3: {
   4:     TextBox box = (TextBox) __container.FindControl("textBox1");
   5:     OrderedDictionary dictionary = new OrderedDictionary();
   6:     if (box != null)
   7:     {
   8:         dictionary["[電流{a}]"] = box.Text;
   9:     }
  10:     return dictionary;
  11: }

由上面的程式碼片段可以瞭解到,ASP.NET動態編譯器是將Bind語法拆分為兩部分:繫結輸出和讀取輸入控制元件值。繫結輸出部分與前篇介紹的機制是完全一樣的,並且也是呼叫DataBinder.Eval方法來繫結資料;而讀取輸入控制元件值則是會根據頁面上控制元件的型別,以及繫結的控制元件屬性名稱,生成一段強型別的控制元件屬性讀取程式碼,並將控制元件的值儲存到dictionay中返回出去。而它全然不知,容器控制元件是如何將這些值合併起來傳給對應的DataSouceControl控制元件的。

Thumbsup關於資料容器控制元件而何與DataSouceControl協同工作,並不是我們這裡要分析的重點。但是我們可以簡單的描述一下工作流程,以DetailsView的資料更新為例:大家通過反編譯DetailsView的原始碼,會找到名稱為HandleUpdate的私有方法,在這個方法裡面會去處理資料項更新前的值(至於在Web環境中如何儲存更新前的值,就需要靠ViewState的強大功能了),和更新後的值(通過ExtractRowValues函式呼叫類似上面生成的__ExtractValues__control7代理函式來收集所有雙向繫結欄位的值存到NewValues裡面),並將他們分別儲存在兩個不同的IOrderedDictionary物件(OldValues,NewValues)中。然後將呼叫對應的DataSouceView的Update方法,傳入原欄位值和新欄位值和一些必須的引數,即可由我們通過重寫DataSourceView的方法來得到所有需要更新欄位的原始值和新值,並可以對比比較哪些欄位值是否發生了變化。NBearDataSource控制元件就是利用了這樣的機制來直接重DataSourceControl和DataSourceView來達到資料的全自動修改和新增方案的。

這裡還有一點不得不說,在GridView,DetailsView,並不一定需要使用語法來實現資料的雙向繫結,他們的欄位雙向繫結可以通過BoundField及它的子控制元件代替模板控制元件的繫結語法,一樣可以達到雙向繫結的目的,簡單但沒有模板來得靈活。而在存取不同版本的欄位值也是類似的機制。

由於這部分涉及到的都是動態和內部程式碼,如果沒有親自去閱讀這些程式碼,估計還是很難理解。最後我們再來簡單總結一下:ASP.NET在模板中雙向繫結欄位,是通過這樣的語法,但是Bind我們更應該把它理解為是一個關鍵字,而不是一個函式。因為在ASP.NET的控制元件中,並沒有存在這個函式。ASP.NET執行時在編譯頁面程式碼時,會把Bind關鍵字的程式碼當成兩部分來編譯:一部分是單向繫結程式碼;另一部分而是讀取對應輸入控制元件的繫結屬性,以繫結欄位名為Key,新增到IOrderedDictionary中收集返回給資料容器控制元件(GridView,DetailsView,FormView)等,讓它們處理。

總體來說,ASP.NET 2.0的雙向繫結機制給我們在提交資料時帶來了極大的方便,儘管有些人很排斥DataSourceControl的模式,但是我們不可否認合理應用會大大提高我們的開發效率。希望通過這兩篇的介紹,我們能對ASP.NET資料繫結機制有更多的認識。在下一篇的文章中,我們將會介紹一些關於資料繫結方式,效能,以及對欄位名的侷限性等相關主題

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-402161/,如需轉載,請註明出處,否則將追究法律責任。

相關文章