在ASPNET我們在使用RepeaterDetailsViewFormViewGridView等數據綁定模板時都會使用<%# Eval(字段名) %>或<%# Bind(字段名) %>這樣的語法來單向或雙向綁定數據但是我們卻很少去了解在這些語法的背後ASPNET究竟都做了哪些事情來方便我們使用這樣的語法來綁定數據究竟解析這樣的語法是在編譯時還是運行時?如果沒有深入去了解我們肯定不得而知這個簡短的系列文章就是帶我們大家一起去深入探究一下ASPNET綁定語法的內部機理以讓我們更加全面的認識和運用它
事件的起因是我希望動態的為Repeater控件添加行項模板我可以通過實現ITempate接口的方式來動態添加行模板並希望它通過普通的頁面綁定語法來完成數據字段的綁定功能如下就是一個簡單的例子
: /// <summary>
: /// Summary description for DynamicTemplate
: /// </summary>
: public class DynamicTemplate : ITemplate
: {
: public DynamicTemplate()
: {
: //
: // TODO: Add constructor logic here
: //
: }
: #region ITemplate Members
:
: public void InstantiateIn(Control container)
: {
: TextBox textBox = new TextBox();
: textBoxText = @<%# Eval(ID) %>;
: containerControlsAdd(textBox);
: }
: #endregion
: }
在這個例子中我在模板中添加了一個TextBox控件並指定它的綁定字段是ID但是這做法能否實現我們實現我們需要的功能呢?答案是否定每一行的TextBox的值都是<%# Eval(ID) %>而不會像我們希望的那樣去綁定ID字段從結果來分析原因我們可以非常容易得出這段綁定語法並沒有得到ASPNET運行時的承認那麼頁面中使用相同的語法為什麼可以呢?故事就是從這裡開始的
我們首先要去了解下在頁面中使用這樣的語法ASPNET都為我們做了哪些事情呢?要了解這個我們要找到aspx文件在首次運行時動態編譯的程序集
我們都知道在ASPNET運行時也會把aspx文件編譯成一個動態類這個類是繼承於aspx的Page指令中Inherits屬性指定的類並且同時也直接實現了IHttpHandler接口這個動態類會負責創建頁面中使用的各種服務器端控件的實例並且ASPNET運行時會負責解析的編譯aspx中存在的服務器端代碼(包括綁定語法)並將這些代碼編譯到這個頁面類WebSite工程和Web Application在頁面文件上有些不同WebSite工程的每個頁面最多可以有兩個文件aspx和aspxcs文件而在Web Application還可以包括aspxdesignercs文件這個文件所起的作用也非常有限也就是為了能在頁面代碼中使用服務器端控件實例而定義的一個實例變量僅此而已所以在設計時WebSite具備更多的動態行為而在運行時WebSite工程和Web Application並沒有太大區別
如何得到頁面的動態類呢?要首先得到這個頁所在的動態程序集在Vista以前的操作系統上一般是在%SystemRoot%\MicrosoftNET\Framework\v\Temporary ASPNET Files 文件夾下而在Vista中而會在%USERPROFILE%\AppData\Local\Temp\Temporary ASPNET Files下那麼如何快速得到程序集的路徑和名稱?你可以讓你的Web工程動態編譯出錯(比如重復的類名)就可以快速定位到當前動態程序集的目錄了
動態類中會有很多的內容我們不作更多的分析我們把目光集中綁定代碼上假設現在頁面上有這麼一段Repeater綁定代碼
: <asp:Repeater runat=server ID=repeater>
: <HeaderTemplate>
: <table>
: <tr>
: <td>
: ID
: </td>
: <td>
: 電流{a}
: </td>
: <td>電壓(V)</td>
: <td>
: 備注
: </td>
: <td>
: 名稱]
: </td>
: </tr>
: </HeaderTemplate>
: <ItemTemplate>
: <tr>
: <td>
: <%# Eval(ID)%>
: </td>
: <td>
: <%# Eval(電流{a})%>
: </td>
: <td><%# Eval(電壓(V))%></td>
: <td>
: <%# Eval(備注)%>
: </td>
: <td>
: <%# Eval(名稱])%>
: </td>
: </tr>
: </ItemTemplate>
: <FooterTemplate>
: </table>
: </FooterTemplate>
: </asp:Repeater>
那麼在動態類中相應的會有這樣的一段函數是用來創建ID為repeater的控件實例
: [DebuggerNonUserCode]
: private Repeater __BuildControlrepeater()
: {
: Repeater repeater = new Repeater();
: baserepeater = repeater;
: repeaterHeaderTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this__BuildControl__control));
: repeaterItemTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this__BuildControl__control));
: repeaterFooterTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this__BuildControl__control));
: repeaterID = repeater;
: return repeater;
: }
:
:
CompiledTempateBuilder和BuildTemplateMethod只是模板實例化的一個中介真正用於添加模板內容的是後面的那些私有函數如ItemTempate的模板內容實例的創建就在__BuildControl__control函數中這個函數原型定義是
: [DebuggerNonUserCode]
: private void __BuildControl__control(Control __ctrl)
: {
: DataBoundLiteralControl control = this__BuildControl__control();
: IParserAccessor accessor = __ctrl;
: accessorAddParsedSubObject(control);
: }
:
在這個函數裡調用了另一個私有函數this__BuildControl__control這個函數返回的一個DataBoundLiteralControl對象並將對象輸出添加到__ctrl參數事實上只要我們去閱讀CompiledTempateBuilder就發現在這裡的__ctrol對象就是我們在實例化模板時傳入的對象也就是ITemplate中的InstantiateIn方法的那個container參數對象
為什麼使用的是AddParsedSubObject方法使用這個方法添加子控件相當於告訴父控件這是一個已經解析好的子控件對象不需再去將控件解析成HTML代碼而在輸出時直接輸出Text屬性的值即可從這裡我們還可以得知DataBoundLiteralControl的對象事實上就是承擔了字符串拼接的職責這一點我們可以在後面的分析中得以驗證
__BuildControl__control私有函數的定義如下
: [DebuggerNonUserCode]
: private DataBoundLiteralControl __BuildControl__control()
: {
: DataBoundLiteralControl control = new DataBoundLiteralControl( );
: controlTemplateControl = this;
: controlSetStaticString( \r\n <tr>\r\n <td>\r\n );
: controlSetStaticString( \r\n </td>\r\n <td>\r\n );
: controlSetStaticString( \r\n </td>\r\n \r\n <td>\r\n );
: controlSetStaticString( \r\n </td>\r\n <td>\r\n );
: controlSetStaticString( \r\n </td>\r\n </tr>\r\n );
: controlDataBinding += new EventHandler(this__DataBind__control);
: return control;
: }
在這個函數裡面創建了一個DataBoundLiteralControl對象並將頁面上定義的模板的靜態HTML代碼添加到該的靜態字符串數組裡並且設置了它的綁定事件代理函數__DataBind__control該函數的定義
: public void __DataBind__control(object sender EventArgs e)
: {
: DataBoundLiteralControl control = (DataBoundLiteralControl) sender;
: RepeaterItem bindingContainer = (RepeaterItem) controlBindingContainer;
: controlSetDataBoundString( ConvertToString(baseEval(ID) CultureInfoCurrentCulture));
: controlSetDataBoundString( ConvertToString(baseEval(電流{a}) CultureInfoCurrentCulture));
: controlSetDataBoundString( ConvertToString(baseEval(備注) CultureInfoCurrentCulture));
: controlSetDataBoundString( ConvertToString(baseEval(名稱]) CultureInfoCurrentCulture));
: }
在這個函數中我們看到了真正的數據綁定代碼了它調用了TemplateControl的Eval方法來將當前數據項的相應字段的值取出並按一定的格式轉化後添加到DataBoundLitreralControl對象中並在DataBoundLiteralControl將StaticString和DataBoundString字符串數組按一定的順序拼接起來作為Text屬性的輸出值而容器控件則直接向客戶端輸這段HTML
下面我們還有必要來分析下TemplateControl中的Eval方法這個方法有兩種重載簡單起見我們來分析較為簡單的重載
: protected internal object Eval(string expression)
: {
: thisCheckPageExists();
: return DataBinderEval(thisPageGetDataItem() expression);
: }
這個方法使用了DataBinderEval靜態方法來得到綁定表達式(字段名)的值它的數據是通過thisPageGetDataItem()這樣的一個方法得到的那麼為什麼thisPageGetDataItem()就可以得到當前正在被綁定的數據項呢?原來在頁面綁定數據時它會有一個堆棧來保存它所有的綁定控件綁定時用到的數據項我們只需要取得堆棧頂部的那個元素就可以在頁面的作用域內的任何一個位置得到當前正在被綁定的數據項如上的例子我們就可以取得當前綁定的RepeaterItem的DataItem的數據項因此我們不需要與RepeaterItem有任何的聯系
如果硬要用上面的代碼來描述數據綁定的全過程跨度過大但是有了以上的分析我們再用文字的形式再來總結下應該就會一個比較完整的印象了在ASPNET的數據模板控件中可以使用<%# %>這樣的語法來將字段值作為一個占位符用在HTML代碼中可以方便我們設計和生成最終的HTML代碼不需要很多的字符拼接工作而ASPNET運行時在首次執行頁面時會為頁面編譯一個動態類在這個動態類中會實例化所有的服務器端控件編譯和解析綁據模板控件的綁定語法並用一些對象和操作來完成數據綁定的字符串接拼接行為因此綁定語法的解析事實上是編譯時的行為只不過這個編譯時是延遲到頁面的首次執行時這就可以解釋為什麼在我們想在動態添加模板中使用<%# %>這樣的綁定語法時無法解析的原因
而對於DataBinderEval方法這是ASPNET提供的一個數據綁定輔助方法通過這個方法我們可以方便的從種不同的數據項如自定義對象或DataRow取出對象的字段(屬性值)從而為我們屏蔽很多不必要的數據來源類型的判斷同時DataBinder這個類還提供了其它的綁定輔助方法大家可以從MSDN查看更多有用的幫助
對數據綁定語法的分析就先到此為一個段落在上面我們主要討論了Eval的單向數據綁定在接下來的一篇文章中我們會來探討ASPNET通過Bind函數(關鍵字)
來實現數據雙向綁定的機理
From:http://tw.wingwit.com/Article/program/ASP/201311/21681.html