【阿不】深入ASP.NET資料繫結(上)

iDotNetSpace發表於2008-07-16

在ASP.NET我們在使用Repeater,DetailsView,FormView,GridView等資料繫結模板時,都會使用或這樣的語法來單向或雙向繫結資料。但是我們卻很少去了解,在這些語法的背後,ASP.NET究竟都做了哪些事情來方便我們使用這樣的語法來繫結資料。究竟解析這樣的語法是在編譯時,還是執行時?如果沒有深入去了解,我們肯定不得而知。這個簡短的系列文章就是帶我們大家一起去深入探究一下ASP.NET繫結語法的內部機理,以讓我們更加全面的認識和運用它。

事件的起因是,我希望動態的為Repeater控制元件新增行項模板,我可以通過實現ITempate介面的方式來動態新增行模板。並希望它通過普通的頁面繫結語法來完成資料欄位的繫結功能,如下就是一個簡單的例子:

   1: /// 
   2: /// Summary description for DynamicTemplate
   3: /// 
   4: public class DynamicTemplate : ITemplate
   5: {
   6:     public DynamicTemplate()
   7:     {
   8:         //
   9:         // TODO: Add constructor logic here
  10:         //
  11:     }
  12:     #region ITemplate Members
  13:  
  14:     public void InstantiateIn(Control container)
  15:     {
  16:         TextBox textBox = new TextBox();
  17:         textBox.Text = @""ID"") %>";
  18:         container.Controls.Add(textBox);
  19:     }
  20:     #endregion
  21: }

在這個例子中,我在模板中新增了一個TextBox控制元件,並指定它的繫結欄位是“ID”。但是這做法,能否實現我們實現我們需要的功能呢?答案是否定,每一行的TextBox的值都是"",而不會像我們希望的那樣去繫結ID欄位。從結果來分析原因,我們可以非常容易得出,這段繫結語法並沒有得到ASP.NET執行時的承認,那麼頁面中使用相同的語法為什麼可以呢?故事就是從這裡開始的。

我們首先要去了解下,在頁面中使用這樣的語法ASP.NET都為我們做了哪些事情呢?要了解這個,我們要找到.aspx檔案在首次執行時動態編譯的程式集。

Thumbsup我們都知道,在ASP.NET執行時,也會把.aspx檔案編譯成一個動態類,這個類是繼承於.aspx的Page指令中Inherits屬性指定的類並且同時也直接實現了IHttpHandler介面。這個動態類會負責建立頁面中使用的各種伺服器端控制元件的例項,並且ASP.NET執行時會負責解析的編譯.aspx中存在的伺服器端程式碼(包括繫結語法)並將這些程式碼編譯到這個頁面類。WebSite工程和Web Application在頁面檔案上有些不同,WebSite工程的每個頁面最多可以有兩個檔案:.aspx和.aspx.cs檔案;而在Web Application還可以包括.aspx.designer.cs檔案,這個檔案所起的作用也非常有限,也就是為了能在頁面程式碼中使用伺服器端、控制元件例項而定義的一個例項變數,僅此而已。所以在設計時WebSite具備更多的動態行為,而在執行時WebSite工程和Web Application並沒有太大區別。

Thumbsup如何得到頁面的動態類呢?要首先得到這個頁所在的動態程式集,在Vista以前的作業系統上,一般是在:%SystemRoot%\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files 資料夾下,而在Vista中,而會在:%USERPROFILE%\AppData\Local\Temp\Temporary ASP.NET Files下。那麼如何快速得到程式集的路徑和名稱?你可以讓你的Web工程動態編譯出錯(比如重複的類名),就可以快速定位到當前動態程式集的目錄了。

動態類中會有很多的內容,我們不作更多的分析,我們把目光集中繫結程式碼上。假設現在頁面上有這麼一段Repeater繫結程式碼:

   1: "server" ID="repeater">
   2:     
   3:         
   4:             
   5:                 
   8:                 
  11:                  
  12:                 
  15:                 
  18:             
  19:     
  20:     
  21:         
  22:             
  25:             
  28:             
  29:             
  32:             
  35:         
  36:     
  37:     
  38:         
   6:                     ID
   7:                 
   9:                     電流{a}
  10:                 
電壓(V)
  13:                     備註'
  14:                 
  16:                     名稱]
  17:                 
  23:                 
  24:             
  26:                 
  27:             
  30:                 ")%>
  31:             
  33:                 名稱]")%>
  34:             
  39:     
  40: 

那麼在動態類中,相應的會有這樣的一段函式,是用來建立ID為repeater的控制元件例項:

   1: [DebuggerNonUserCode]
   2: private Repeater __BuildControlrepeater()
   3: {
   4:     Repeater repeater = new Repeater();
   5:     base.repeater = repeater;
   6:     repeater.HeaderTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control4));
   7:     repeater.ItemTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control5));
   8:     repeater.FooterTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control7));
   9:     repeater.ID = "repeater";
  10:     return repeater;
  11: }
  12:  
  13:  

CompiledTempateBuilder和BuildTemplateMethod只是模板例項化的一箇中介,真正用於新增模板內容的是後面的那些私有函式,如ItemTempate的模板內容例項的建立就在__BuildControl__control5函式中,這個函式原型定義是:

   1: [DebuggerNonUserCode]
   2: private void __BuildControl__control5(Control __ctrl)
   3: {
   4:     DataBoundLiteralControl control = this.__BuildControl__control6();
   5:     IParserAccessor accessor = __ctrl;
   6:     accessor.AddParsedSubObject(control);
   7: }
   8:  

在這個函式裡,呼叫了另一個私有函式this.__BuildControl__control6,這個函式返回的一個DataBoundLiteralControl物件,並將物件輸出新增到__ctrl引數。事實上,只要我們去閱讀CompiledTempateBuilder就發現在,這裡的__ctrol物件就是我們在例項化模板時傳入的物件,也就是ITemplate中的InstantiateIn方法的那個container引數物件。

Thumbsup為什麼使用的是AddParsedSubObject方法,使用這個方法新增子控制元件相當於告訴父控制元件,這是一個已經解析好的子控制元件物件,不需再去將控制元件解析成HTML程式碼,而在輸出時直接輸出Text屬性的值即可。從這裡我們還可以得知DataBoundLiteralControl的物件,事實上就是承擔了字串拼接的職責,這一點我們可以在後面的分析中得以驗證。

__BuildControl__control6私有函式的定義如下:

   1: [DebuggerNonUserCode]
   2: private DataBoundLiteralControl __BuildControl__control6()
   3: {
   4:     DataBoundLiteralControl control = new DataBoundLiteralControl(5, 4);
   5:     control.TemplateControl = this;
   6:     control.SetStaticString(0, "\r\n                \r\n                    \r\n                        ");
   7:     control.SetStaticString(1, "\r\n                    
\r\n \r\n ");
   8:     control.SetStaticString(2, "\r\n                    
\r\n \r\n \r\n ");
   9:     control.SetStaticString(3, "\r\n                    
\r\n \r\n ");
  10:     control.SetStaticString(4, "\r\n                    
\r\n \r\n "
);
  11:     control.DataBinding += new EventHandler(this.__DataBind__control6);
  12:     return control;
  13: }

在這個函式裡面,建立了一個DataBoundLiteralControl物件,並將頁面上定義的模板的靜態HTML程式碼新增到該的靜態字串陣列裡,並且設定了它的繫結事件代理函式__DataBind__control6,該函式的定義:

   1: public void __DataBind__control6(object sender, EventArgs e)
   2: {
   3:     DataBoundLiteralControl control = (DataBoundLiteralControl) sender;
   4:     RepeaterItem bindingContainer = (RepeaterItem) control.BindingContainer;
   5:     control.SetDataBoundString(0, Convert.ToString(base.Eval("ID"), CultureInfo.CurrentCulture));
   6:     control.SetDataBoundString(1, Convert.ToString(base.Eval("電流{a}"), CultureInfo.CurrentCulture));
   7:     control.SetDataBoundString(2, Convert.ToString(base.Eval("備註'"), CultureInfo.CurrentCulture));
   8:     control.SetDataBoundString(3, Convert.ToString(base.Eval("名稱]"), CultureInfo.CurrentCulture));
   9: }

在這個函式中,我們看到了真正的資料繫結程式碼了,它呼叫了TemplateControl的Eval方法來將當前資料項的相應欄位的值取出,並按一定的格式轉化後新增到DataBoundLitreralControl物件中,並在DataBoundLiteralControl將StaticString和DataBoundString字串陣列按一定的順序拼接起來,作為Text屬性的輸出值。而容器控制元件則直接向客戶端輸這段HTML。

下面,我們還有必要來分析下TemplateControl中的Eval方法,這個方法有兩種過載,簡單起見,我們來分析較為簡單的過載:

   1: protected internal object Eval(string expression)
   2: {
   3:     this.CheckPageExists();
   4:     return DataBinder.Eval(this.Page.GetDataItem(), expression);
   5: }

這個方法,使用了DataBinder.Eval靜態方

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

相關文章