摘要:了解 ASP.NET 2.0 中的用于生成自定義數據綁定控件的工具是如何演變的。
本頁內容
為什么需要新的數據源模型
數據綁定是開發人員在 ASP.NET 1.x 中發現的最令人愉快的意外功能之一。與 Active Server Pages 對數據訪問的支持相比,數據綁定是簡單性和有效性的非凡結合。然而,如果根據真正開發人員的需要進行衡量,則它還不夠完美。其局限不在于總體功能方面,而在于開發人員必須編寫大量代碼來處理甚至非常簡單和常見的操作(例如,分頁、排序或刪除)。為了彌補這一缺陷,ASP.NET 2.0 添加了一種新的數據源模型(請參閱我的文章:More Load, Less Code with the Data Enhancements of ASP.NET 2.0)。它包括很多不帶 UI 的新控件,這些控件將數據綁定控件的可視部分和數據容器聯系起來。開發人員需要在 ASP.NET 1.x 中編寫的絕大部分代碼經過適當的分解和創作,現在基本上都被嵌入到一系列新的控件中:數據源組件。
使用數據源組件有很多好處 — 首先,可以得到完全聲明性的數據綁定模型。新模型減少了以內聯方式插入到 ASPX 資源中或者分散在代碼隱藏類中的松散代碼。新的數據綁定體系結構強制開發人員遵守嚴格的規則。此外,它還從本質上改變了代碼的質量。附加到事件的較長代碼塊通常會消失,而被只是插入到現有框架中的組件所取代。這些數據源組件派生自抽象類,實現了已知的接口,并且總體而言意味著更高級別的可重用性。
Nikhil Kothari 的有關控件開發的優秀著作 — Developing Microsoft ASP.NET Server Controls and Components — 幫助成千上萬的開發人員生成自定義控件,并且說明了設計和實現的最佳做法。但是,一本書 — 無論它有多么偉大 — 都永遠無法取代一個更好的系統框架。借助于 ASP.NET 2.0,您還獲得了一個完全重新設計的類圖 — 當您沿著類樹從基礎類向葉子類滾動時,它能夠添加更具體的數據綁定功能。通過新的數據綁定控件層次結構,所有開發人員都可以更容易地選取正確的類來加以繼承,以便生成他們自己的自定義數據綁定控件。
在本文中,您將提前了解 ASP.NET 2.0 數據綁定模型中的能夠對自定義控件產生影響的更改。在此過程中,您將了解可用的新基類以及新的高質量自定義控件的新要求。
ASP.NET 2.0 中的數據綁定控件
ASP.NET 2.0 數據源模型并未要求必須使用新的控件(例如,GridView 和 FormView);它仍然能夠與舊樣式的控件(例如,DataGrid 和 CheckBoxList)協同工作。這對于控件開發人員而言意味著什么呢?有兩個截然不同類型的數據源需要處理 — 傳統的基于 IEnumerable 的數據容器(例如,DataView 和集合)以及數據源控件(例如,SqlDataSource 和 ObjectDataSource)。最后,無論數據源是 ADO.NET 對象、自定義集合還是數據源組件,ASP.NET 2.0 數據綁定控件都必須能夠將傳入的任何數據規格化為可枚舉的集合。
在 ASP.NET 1.x 中,文檔在某種程度上領先于框架。文檔正確地標識和討論了三個類型的數據綁定控件 — 標準控件、列表控件和復合控件。任何只是提供 DataBind 方法和 DataSource 屬性的非空實現的控件都屬于第一個類別。列表控件是下列兩者的有趣結合:高級布局屬性(例如,RepeatColumns 和 RepeatLayout),以及為綁定的每個數據元素重復的固定的嵌入式項模板。最后,復合控件負責通過組合一個或多個現有控件來設計最終的用戶界面。文檔準確地闡述了與創建上述類型的控件相關的任何問題;然而,ASP.NET 框架卻并未提供很多基類來簡化開發人員的任務。圖 1 顯示了 ASP.NET 2.0 中的新的數據綁定控件層次結構。請注意顯示為黃色的基類以及它們在整個類樹中的分布。
圖 1. ASP.NET 2.0 中的數據綁定控件的層次結構
對圖 1 中呈現的基類進行一番觀察是一件有趣的事情。它們在表 1 中列出并進行了詳細說明。
tdHeader vAlign=top>
類 |
說明 |
BaseDataBoundControl |
數據綁定控件的根類。執行數據綁定并驗證任何綁定數據。 |
DataBoundControl |
包含用于與數據源控件和數據容器進行通信的邏輯?梢詮脑擃惱^承以生成標準的數據綁定控件。 |
ListControl |
列表控件的基類,提供 Items 集合和高級布局呈現功能。 |
CompositeDataBoundControl |
實現復合控件所必需的典型代碼,包括在進行回發之后根據視圖狀態還原控件樹的代碼。 |
HierarchicalDataBoundControl |
基于樹的分層控件的根類。 |
表 1. ASP.NET 2.0 中的基本數據綁定類
對于任何曾經花費巨大精力來創建能夠管理自己的數據集合并且能夠正確地從視圖狀態還原的、具有豐富功能的數據綁定控件的人而言,這些類特別受歡迎。您需要能夠對此加以闡明的示例嗎?請繼續閱讀。
分析要點
ASP.NET 開發人員中心在過去幾個月中發布了幾篇有關下列 ASP.NET 1.1 數據綁定控件的文章:RssFeed 和 DetailsView 控件(這兩篇文章分別為 Building DataBound Templated Custom ASP.NET Server Controls 和 A DetailsView Control for ASP.NET 1.x)。如果您深入分析這兩個控件的代碼,則您會看到它們在源代碼中的各個位置利用了特殊的技術。例如,它們在向頁進行回發(即不在該控件管轄范圍內的回發)之后根據視圖狀態重新生成控件的樹;它們通過一個自定義集合類來公開項集合;它們允許賦予項樣式;它們支持很多個類型的輸入源。在 ASP.NET 1.1 中,對于上述每一種功能,您都必須編寫代碼,并且更重要的是,您必須按照特定的順序編寫代碼,重寫特定的基礎方法,并且認真遵守文檔和前面提到的優秀著作 Developing Microsoft ASP.NET Server Controls and Components 中的指導和建議。
在 ASP.NET 2.0 中,兩個示例控件中使用的大部分管線代碼都被硬編碼到表 1 中列出的基類中。為了對比 ASP.NET 1.1 和 2.0 中的數據綁定控件,我將重點討論下列要點:
• |
總體數據綁定機制和不同的數據源類型 |
• |
集合和視圖狀態管理 |
該列表很可能是不完備的,但是它肯定足以使您對控件開發有一個大致的了解。您將既愉快又吃驚地看到,開發功能豐富的自定義控件所需的代碼是如此之少。
數據綁定機制
要在 ASP.NET 2.0 中生成新的數據綁定控件,首先需要確定哪個類能夠更好地適合您的要求。然而,您的選擇并不局限于比較空的類,如 Control 和 WebControl 甚至 ListControl。讓我們探索一下那些深藏于幕后的類。BaseDataBoundControl 是所有數據綁定控件類的根。它定義了 DataSource 和 DataSourceID 屬性,并且驗證它們被分配的內容。DataSource 接受按照 ASP.NET 1.x 的方式獲得和分配的可枚舉對象。 Mycontrol1.DataSource = dataSet;
Mycontrol1.DataBind();
DataSourceID 是一個字符串,并且是指綁定數據源組件的 ID。一旦將控件綁定到數據源,則二者之間的任何進一步的交互(無論是讀還是寫)都將脫離您的控制范圍,并且不可見。這一點既有好的一面,也有壞的一面。好(更確切地說是偉大)的一面在于可以消除大量代碼。ASP.NET 框架能夠保證正確的代碼得以執行,并且按照公認的最佳做法編寫代碼。您的工作效率會更高,因為您可以完全確信在工作過程中不會出現令人難以捉摸的錯誤,從而可以更快地創作頁。如果您不喜歡這種情況(好像很多 ASP.NET 1.x 開發人員都抱怨這種情況),則您可以繼續使用通過 DataSource 屬性和 DataBind 方法完成的舊樣式的編程。而且,在這種情況下,基類使您不必完成一些常見的工作,即使這種效果在代碼中體現得不是那么明顯。
DataBoundControl 類用于與現有控件沒有多少共同點的標準的自定義數據綁定控件。如果您必須處理自己的數據項集合,管理視圖狀態和樣式,創建簡單但量身定制的用戶界面,則該類可以提供一個良好的起點。最為有趣的是,DataBoundControl 類將控件連接到數據源組件,并且在 API 級別隱藏了可枚舉數據源和特別組件之間的任何差異。簡而言之,當您從該類繼承時,您只需要重寫一個接收數據集合(無論數據源是 DataSet 對象還是較新的數據源組件)的方法。
讓我們詳細闡述這一點(它代表體系結構中的重大更改)。
BaseDataBoundControl 重寫了 DataBind 方法(原來在 Control 上定義),并且使它調用 PerformSelect 方法(該方法被標記為受保護的和抽象的)。正如其名稱所暗示的那樣,PerformSelect 能夠檢索有效的數據集合以使綁定發生。該方法是受保護的,因為它包含實現細節;它是抽象的(用 Visual Basic 行話說就是 MustInherit),因為它的行為只能由派生類(如 DataBoundControl)確定。
那么,DataBoundControl 完成哪些工作以重寫 PerformSelect 呢?
它連接到數據源對象并獲得默認視圖。數據源對象(例如,像 SqlDataSource 或 ObjectDataSource 之類的控件)執行它的選擇命令并返回得到的集合。操作數據檢索的受保護方法(名為 GetData)還足夠聰明,以便檢查 DataSource 屬性。如果 DataSource 非空,則將綁定對象包裝到一個動態創建的數據源視圖對象中,并且將其返回。
下一個步驟需要您以控件開發人員的身份參與。迄今為止,基類已經以一種完全自動的方式從 ADO.NET 對象或數據源組件中檢索數據。下一個步驟取決于您期望該控件完成哪些任務。這里正好用到可重寫的 PerformDataBinding 方法。以下代碼片段顯示了 DataBoundControl 中對該方法的實現。請注意,由框架傳遞給該方法的 IEnumerable 參數只包含要綁定的數據(不管它們的來源如何)。 protected virtual void PerformDataBinding(IEnumerable data)
{
}
在自定義數據綁定控件中,您只需要重寫該方法,并且填充任何特定于控件的集合,如包含很多個列表控件的 Items 集合(例如,CheckBoxList)?丶挠脩艚缑娴某尸F發生在 Render 方法或 CreateChildControls 中,具體取決于該控件的性質。Render 適用于列表控件;而 CreateChildControls 則非常適合于復合控件。
有一件事情尚未解釋:由誰啟動數據綁定過程?在 ASP.NET 1.x 中,數據綁定需要顯式調用 DataBind 方法才能開始工作。在 ASP.NET 2.0 中,如果您使用 DataSource 屬性將數據綁定到控件,則仍然需要這樣做。如果您改而通過 DataSourceID 屬性使用數據源組件,則應當避免這樣做。數據綁定過程由 DataBoundControl 中定義的內部 OnLoad 事件處理程序自動觸發,如下面的偽代碼所示。 protected override void OnLoad(EventArgs e)
{
this.ConnectToDataSourceView();
if (!Page.IsPostBack)
base.RequiresDataBinding = true;
base.OnLoad(e);
}
每當該控件被加載到頁中的時候(回發或首次加載),都會檢索和綁定數據。需要由數據源決定是再次運行查詢還是使用一些緩存數據。
如果該頁是首次顯示,則還會啟用 RequiresDataBinding 屬性以要求綁定數據。當分配的值為 true 時,該屬性的設置程序會在內部調用 DataBind。下面的偽代碼顯示了 RequiresDataBinding 設置程序的內部實現。 protected void set_RequiresDataBinding(bool value)
{
if (value && (DataSourceID.Length > 0))
DataBind();
else
_requiresDataBinding = value;
}
正如您可以看到的那樣,為了向后兼容,僅當 DataSourceID 不為空(即您綁定到 ASP.NET 2.0 數據源控件)時,才會發生對 DataBind 的自動調用。有鑒于此,如果您還顯式調用 DataBind,則會導致雙重數據綁定。
請注意,您無法同時設置 DataSource 和 DataSourceID。當發生這種情況時,將引發無效操作異常。
最后,稍微提一下 EnsureDataBound 這一受保護的方法。該方法是在 BaseDataBoundControl 類上定義的,它能夠確?丶呀洷徽_地綁定到必需的數據。如果 RequiresDataBinding 為 true,則該方法調用 DataBind,如下面的代碼片段所示。 protected void EnsureDataBound()
{
if (RequiresDataBinding && (DataSourceID.Length > 0))
DataBind();
}
如果您已經編寫了復雜且完善的數據綁定控件,則您很可能已經知道我的意思。在 ASP.NET 1.x 中,在下列兩種情況下,通常會將數據綁定控件設計為生成它自己的用戶界面:該控件具有對數據源的完全訪問權限,或者該控件基于視圖狀態。當該控件需要管理它自己的回發事件時(例如,假設該控件是支持分頁的 DataGrid),則前面提到的兩個選擇似乎是兩種極端的情況。在 ASP.NET 1.x 中,這些控件(同樣,請考慮 DataGrid)只有一種解決辦法:向要刷新的主頁引發事件。該方法導致 ASP.NET 1.x 頁中存在多余代碼這一眾所周知的問題 — 這也正是調用數據源組件來加以修復的問題。
在 ASP.NET 2.0 中,每當在控件的生存期中發生要求綁定數據的事情時,都需要將 RequiresDataBinding 設置為 true。設置該屬性會觸發相應的數據綁定機制,從而重新創建該控件的內部基礎結構的更新版本。內置的 OnLoad 事件處理程序還會將該控件連接到數據源。為了確實有效,該技術必須依賴于能夠將它們的數據緩存在某個位置的智能數據源控件。例如,SqlDataSource 控件支持很多屬性,以便在給定期限內將任何綁定結果集存儲到 ASP.NET 緩存中。
列表控件
數據綁定控件通常為列表控件。列表控件通過為它的主框架邊界內的每個綁定數據項重復固定的模板,生成它自己的用戶界面。例如,CheckBoxList 控件只是為每個綁定數據項重復 CheckBox 控件。同樣,DropDownList 控件遍歷它的數據源,并且在 <select> 父標記內創建新的 <option> 元素。除了列表控件以外,ASP.NET 還提供了迭代控件。它們有什么不同?
列表控件和迭代控件的不同之處在于被應用于每個數據項的可重復模板允許具有的自定義級別。像 CheckBoxList 控件一樣,Repeater 控件遍歷綁定數據項并應用用戶定義的模板。Repeater(以及更完善的 DataList 控件)極為靈活,但是在使代碼保持模塊化和分層化方面不能提供多少幫助。要使用 Repeater,您需要在該頁(或外部用戶控件)中定義模板,并使用 ASPX 源中的數據綁定屬性。它是快速、有效的,有時還是必要的,但肯定不是整潔和優雅的。
在 ASP.NET 1.x 中,所有列表控件都從 ListControl(它是表 1 中唯一一個已經在 1.x 中定義的類)繼承。讓我們進入編碼猴子模式,并且開始練習使用 ASP.NET 2.0 中的數據綁定控件。我將首先生成一個 HeadlineList 控件,以便為每個數據項呈現兩行數據綁定文本。此外,該控件還將具備一些布局功能,例如,垂直或水平呈現。
HeadlineList 示例控件
正如前面提到的那樣,ListControl 是 ASP.NET 1.x 和 2.0 中所有列表控件的基類。非常令人愉快的是,可以用一種非常平滑的方式將在此為 ASP.NET 2.0 編寫的 HeadlineList 控件向后移植到 ASP.NET 1.x。出于某種原因,當需要生成標題列表時,人們的大腦中涌現的第一個想法往往是使用 Repeater。的確,Repeater 會使這一工作變得非常簡單。 <asp:Repeater runat="server">
<HeaderTemplate>
<table>
</HeaderTemplate>
<ItemTemplate>
<tr><td>
<%# DataBinder.Eval(Container.DataItem, "Title") %>
<hr>
<%# DataBinder.Eval(Container.DataItem, "Abstract") %>
</td></tr>
</ItemTemplate>
<FooterTemplate>
</table>
</FooterTemplate>
</asp:Repeater>
這段代碼有什么問題?或者更準確地說,這段代碼中有哪些可以改進的地方?
注:在 ASP.NET 2.0 中,您可以將 DataBinder.Eval(Container.DataItem, field) 替換為一個較短的表達式,該表達式受益于 Page 類上的一個新的公共方法 — Eval。這一新的表達式類似于 Eval(field)。在內部,Eval 調用 DataBinder 類上的靜態 Eval 方法,并且確定要使用的正確綁定上下文。
字段的名稱在 ASPX 頁中硬編碼?梢詫崿F可重用性,但只能通過剪切和粘貼實現。您所添加的用于使 Repeater 的行為更加豐富多彩的代碼越多,對該解決方案及其跨越頁和項目的可重用性的危害就越大。如果標題列表控件恰恰是您需要的東西,則請改而嘗試以下方法。 public class HeadlineList : ListControl, IRepeatInfoUser
{
:
}
ListControl 是列表控件的基類(它位于與 CheckBoxList、DropDownList 和類似控件相同的系列中);IRepeatInfoUser 是上述大多數控件加以實現以便用水平或垂直方式在列和行中呈現的幾乎不為人所知的界面。請注意,ListControl 和 IRepeatInfoUser 還存在于 ASP.NET 1.x 中,并且以幾乎與 2.0 相同的方式工作。
列表控件是圍繞一個要重復的控件生成的;該控件(或控件圖)是一個類屬性,并且在加載時實例化以節省一些 CPU 時間。以下為私有 ControlToRepeat 屬性的實現。 private Label _controlToRepeat;
private Label ControlToRepeat
{
get
{
if (_controlToRepeat == null)
{
_controlToRepeat = new Label();
_controlToRepeat.EnableViewState = false;
Controls.Add(_controlToRepeat);
}
return _controlToRepeat;
}
}
在該示例中,要重復的控件(標題)是一個在首次讀取時實例化的 Label。HeadlineList 控件還應當向用戶提供通過多種屬性(如 RepeatLayout、RepeatColumns 和 RepeatDirection)影響外觀的方式。很多標準列表控件上都定義了這些屬性,因此開發人員不應該對它們感到陌生。它們的實現是類似的,并且看起來像下面的代碼。 public virtual RepeatDirection RepeatDirection
{
get
{
object o = ViewState["RepeatDirection"];
if (o != null)
return (RepeatDirection) o;
return RepeatDirection.Vertical;
}
set
{
ViewState["RepeatDirection"] = value;
}
}
為完成 HeadlineList 控件而需要編寫的另一段代碼以呈現為中心。IRepeatInfoUser 接口對您可以用來控制呈現過程的各種屬性進行計數。這方面的屬性示例有 HasHeader、HasFooter 和 HasSeparator 布爾型屬性。您可以像實現其他任何普通屬性一樣實現這些屬性,并且根據需要在 RenderItem 接口方法中使用它們。 public void RenderItem(ListItemType itemType, int repeatIndex,
RepeatInfo repeatInfo, HtmlTextWriter writer)
{
string format = "<b>{0}</b><hr style='solid 1px black'>{1}";
Label lbl = ControlToRepeat;
int i = repeatIndex;
lbl.ID = i.ToString();
string text = String.Format(format, Items[i].Text, Items[i].Value);
lbl.Text = text;
lbl.RenderControl(writer);
}
RenderItem 對向頁提供的輸出承擔最終的責任。它獲得要重復的控件,并且將其呈現到標記中。RenderItem 是從 Render 中調用的。 protected override void Render(HtmlTextWriter writer)
{
if (Items.Count >0)
{
RepeatInfo ri = new RepeatInfo();
Style controlStyle = (base.ControlStyleCreated
? base.ControlStyle : null);
ri.RepeatColumns = RepeatColumns;
ri.RepeatDirection = RepeatDirection;
ri.RepeatLayout = RepeatLayout;
ri.RenderRepeater(writer, this, controlStyle, this);
}
}
RepeatInfo 是一個 Helper 對象,它經過專門設計,以便通過重復現有的控件圖來生成新控件。以上就是所需的全部代碼。讓我們準備一個示例頁,并測試該控件。 <expo:headlinelist id="HeadlineList1" runat="server"
repeatlayout="Table" repeatdirection="Vertical" repeatcolumns="2"
datatextfield="LastName" datavaluefield="Notes" />
圖 2 顯示了該控件的工作方式。
圖 2. HeadlineList 數據綁定控件
該控件在設計時工作正常,并且不需要插入其他任何代碼。然而,這段代碼的最令人愉快的邊界效應并非免費的設計時支持。對我來說,它簡直太美妙了,因為它能夠使用 ADO.NET 數據源對象(例如,DataTable 或 DataSet)和數據源組件(如 SqlDataSource)。您可以取走這段代碼,將其編譯為 ASP.NET 1.x 項目,而它就可以使用基于 IEnumerable 的數據源。如果將這段代碼引入到 ASP.NET 2.0 項目中,則它無須更改就同樣可以使用數據源對象。
這一事實的意義是什么?
在 ASP.NET 1.x 中,ListControl 類是一個令人愉快的例外 — 但仍然是一個例外。在 ASP.NET 2.0 中,您可以使用類似的簡單但有效的方法來生成任何數據綁定控件。在這樣做的時候,您可以利用合并了大部分復雜性并且將大多數已知的最佳做法硬編碼的新基類。
管理自定義集合
ListControl 是一個過于專用的類,它以不受您控制的固定方式執行數據綁定 — 除非您重寫諸如 PerformSelect、OnDataBinding 和 PerformDataBinding 之類的方法。它還提供了預定義的 Items 集合屬性。讓我們在 ASP.NET 2.0 中的更低級別處理數據綁定,并且設計具有下列功能的 ButtonList 控件:
• |
使用自定義集合類來保留組成項 |
• |
用自定義方式管理視圖狀態 |
ButtonList 控件是另一個為每個綁定數據項輸出按鈕的列表控件。您可以讓它從 ListControl 繼承;而且,您可以獲得 HeadlineList 的源代碼,將 Label 替換為 Button,而它仍然應當正常工作。這一次,我將采用一種不同的方法來說明 DataBoundControl 的行為。為簡單起見,我仍將跳過 IRepeatInfoUser 接口。 public class ButtonList : System.Web.UI.WebControls.DataBoundControl
{
:
}
標題和命令名稱表現了每個按鈕的性質。該信息是通過幾個自定義屬性(如 DataTextField 和 DataCommandField)從綁定數據源中獲得的。您可以容易地添加類似的屬性,以提供數據綁定工具提示,甚至提供 URL。 public virtual string DataCommandField
{
get
{
object o = ViewState["DataCommandField"];
if (o == null)
return "";
return (string)o;
}
set { ViewState["DataCommandField"] = value; }
}
所發現的有關每個綁定按鈕的所有信息都被填充到一個通過 Items 屬性公開的自定義對象集合中。(請注意,Items 只是該屬性的標準、慣用而任意的名稱。) [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[PersistenceMode(PersistenceMode.InnerProperty)]
public virtual ButtonItemCollection Items
{
get
{
if (_items == null)
{
_items = new ButtonItemCollection();
if (base.IsTrackingViewState)
_items.TrackViewState();
}
return _items;
}
}
Items 集合是自定義 ButtonItemCollection 類的實例 — ButtonItem 對象的集合。ButtonItem 類只是存儲了有關綁定按鈕的關鍵信息 — Text 和 CommandName 屬性,外加幾個構造函數以及 ToString 方法。ButtonItem 類是作為普通列表控件的 ListItem 類的對等物。下面是一個示例。 public class ButtonItem
{
private string _text;
private string _command;
public ButtonItem(string text, string command) {
_text = text;
_command = command;
}
public string Text {
get {return _text;}
set {_text = value;}
}
public string CommandName {
get { return _command; }
set { _command = value; }
}
public override string ToString() {
return "Button [" + Text + "]";
}
}
現在,如何創建 ButtonItem 對象的集合呢?在 ASP.NET 1.x 中,您必須生成一個從 CollectionBase 繼承的自定義集合類,并且起碼重寫幾個方法。然而,自定義集合只是圍繞 ArrayList 對象的包裝而已,在訪問速度方面并沒有任何真正的優勢。實際上,仍然需要進行轉換。.NET 2.0 中的泛型提供了真正的轉折點。要生成 ButtonItem 對象集合,您需要以下代碼: public class ButtonItemCollection : Collection <ButtonItem>
{
}
并且,它的性能也會更好,因為編譯器在幕后完成了某些工作。ButtonList 控件只需要兩個被重寫的方法:Render 和 PerformDataBinding。Render 假定 Items 集合被填充;因此,它只是進行迭代并輸出標記代碼。 protected override void Render(HtmlTextWriter writer)
{
for(int i=0; i< } btn.RenderControl(writer); btn.CommandName="item.CommandName;" btn.Text="item.Text;" Button(); btn="new" Button item="Items[i];" ButtonItem { i++)>
Items 集合為什么如此重要?它可以幫助您獲得兩個結果。首先,您可以用手動添加的項填充該列表控件。其次,一旦在視圖狀態中持久保存該集合,您就可以在回發時重新生成該控件的用戶界面,而無須綁定到數據。在進行數據綁定時,Items 集合是在何處以及由誰填充的呢?這需要用到 PerformDataBinding。該方法獲得一個可枚舉的數據列表(無論原始數據源是什么)并使用它來填充 Items 集合。 protected override void PerformDataBinding(IEnumerable dataSource)
{
base.PerformDataBinding(dataSource);
string textField = DataTextField;
string commandField = DataCommandField;
if (dataSource != null) {
foreach (object o in dataSource)
{
ButtonItem item = new ButtonItem();
item.Text = DataBinder.GetPropertyValue(o, textField, null);
item.CommandName = DataBinder.GetPropertyValue(o,
DataCommandField, null);
Items.Add(item);
}
}
}
每當需要進行數據綁定時,該方法都能夠確保 Items 集合被填充。在回發時會發生什么?在這種情況下,必須根據視圖狀態重新構建 Items 集合。您可以通過 IStateManager 接口上的方法賦予自定義集合類這一能力。以下為該接口的關鍵方法: public void LoadViewState(object state)
{
if (state != null) {
Pair p = (Pair) state;
Clear();
string[] rgText = (string[])p.First;
string[] rgCommand = (string[])p.Second;
for (int i = 0; i < rgText.Length; i++)
Add(new ButtonItem(rgText[i], rgCommand[i]));
}
}
public object SaveViewState()
{
int numOfItems = Count;
object[] rgText = new string[numOfItems];
object[] rgCommand = new string[numOfItems];
for (int i = 0; i < numOfItems; i++) {
rgText[i] = this[i].Text;
rgCommand[i] = this[i].CommandName;
}
return new Pair(rgText, rgCommand);
}
該類使用一個 Pair 對象(一種經過優化的 2 位置數組)將自身序列化為視圖狀態。您需要創建兩個對象數組,以便保留每個按鈕的文本和命令名稱。這兩個數組隨后被成對打包并插入到該視圖狀態中。當還原該視圖狀態時,會將該數組對拆包,并且使用先前存儲的信息重新填充 Items 集合。使用該方法要比使 ButtonItem 類可序列化更可取,因為傳統的二進制格式化程序的性能(在空間和時間這兩個方面)更差。
然而,向集合中添加視圖狀態支持還不夠。還必須增強 ButtonList 控件以利用集合的序列化功能。您可以重寫控件類上的 LoadViewState 和 SaveViewState。 protected override void LoadViewState(object savedState)
{
if (savedState != null) {
Pair p = (Pair) savedState;
base.LoadViewState(p.First);
Items.LoadViewState(p.Second);
}
else
base.LoadViewState(null);
}
protected override object SaveViewState()
{
object baseState = base.SaveViewState();
object itemState = Items.SaveViewState();
if ((baseState == null) && (itemState == null))
return null;
return new Pair(baseState, itemState);
}
控件的視圖狀態由兩個元素組成:默認控件的視圖狀態以及 Items 集合。這兩個對象被打包到 Pair 對象中。除了 Pair 對象以外,您還可以使用 Triplet 對象(包含三個對象的數組),或者使用 Pair 或 Triplet 對組成任意數量的對象。
以這種方式設計的自定義集合還可以在設計時滿足需要。Visual Studio 2005 中嵌入的默認集合編輯器可以識別該集合并彈出如圖 3 所示的對話框。
圖 3. 設計時的 ButtonList Items 集合
值得說明的是,在 ASP.NET 2.0 中,某些數據綁定控件使您可以將數據綁定項與以編程方式通過 Items 集合添加的項分開。布爾型的 AppendDataBoundItems 屬性用于控制該控件的編程接口的這一方面。該屬性在 ListControl(而非 DataBoundControl)上定義,并且默認為 false。
關于復合控件的一點討論
CompositeDataBoundControl 類是生成復合控件(我相信當您想到數據綁定控件時,您所指的就是這些控件)的起點。復合控件必須能夠:
• |
充當命名容器。 |
• |
通過 CreateChildControls 方法創建它自己的用戶界面。 |
• |
實現特定的邏輯,以便在回發之后還原它的子元素層次結構。 |
最后一點在 Nikhil Kothari 的著作中進行了良好的闡述,并且在 ASP.NET 1.x 的所有內置控件中都得到了實現。如果您迄今為止尚未完全了解該概念,則好消息是您現在可以徹底忘掉有關它的所有內容。一切內容現在都在 CompositeDataBoundControl 類中硬編碼。您需要關心的主要方面是設計您的控件的子控件。您可以通過重寫按以下方式定義的新方法來完成該工作: protected abstract int CreateChildControls(
IEnumerable dataSource, bool dataBinding);
CompositeDataBoundControl 從 DataBoundControl 繼承,因此,本文中陳述的有關集合、綁定和視圖狀態的大部分內容也適用于復合控件。
小結
數據綁定和數據綁定控件代表著 ASP.NET 1.x 中的一項巨大突破,但是有一些要點沒有得到解釋,并且有幾個問題有待回答。Nikhil Kothari 的著作為所有開發人員提供了完美的權威的指南。ASP.NET 2.0 將該書的一些最佳做法(大部分已經在 ASP.NET 1.x 的幕后得以實現)轉換為可重用的類以及新的數據綁定控件對象模型。
本文重點討論了在從 ASP.NET 1.x 升級到 ASP.NET 2.0 時進行的主要更改,并且通過幾個實際示例概述了它們影響開發的方式。接下來,我們將需要關注 ASP.NET 2.0 控件開發中的樣式和主題。但是,那也許會成為在不久的將來問世的另一篇文章的中心議題。請繼續關注我們的工作。 |