ASP.NET復合控件簡介及要點一覽
ASP.NET復合控件簡介
復合控件只不過是普通的 ASP.NET 控件,還不屬于要論及的另一種類型的 ASP.NET 服務器控件。既然這樣,為什么在各書籍和文檔中總要留出專門的章節(jié)來論述復合控件呢?ASP.NET復合控件有什么特別之處呢?
顧名思義,復合控件是將多個其他控件聚集在某單一頂部和單一 API 下的控件。如果某個自定義控件由一個標簽和一個文本框組成,就可以說該控件是一個復合控件。“復合”一詞表明該控件本質上是由其他構成組件在運行時組合而成。復合控件所暴露的方法集和屬性集通常(但不是必須)由構成組件的方法和屬性提供,并加入一些新成員。復合控件也可以引發(fā)自定義事件,還可以處理并激起子控件所引起的事件。
復合控件在 ASP.NET 中如此特別并不是因為其有可能成為服務器控件新類型的代表。更確切的說是因為它在呈現(xiàn)時獲得了 ASP.NET 運行時的支持。
復合控件是一個功能強大的工具,可以生成豐富復雜的組件,這些組件產(chǎn)生自活動對象的相互作用而不是某些字符串生成器對象的標記輸出。復合控件以構成控件樹的形式呈現(xiàn),每個構成控件都有其自己的生命周期和事件,并且所有構成控件都聯(lián)合構成一個全新的 API,并按需要盡可能地抽象化。
在本文中,我將論述復合控件的內(nèi)部體系結構,以闡明它在多種情況下為您帶來的好處。接下來,我將生成一個復合列表控件,與我在以前文章中所述控件的功能集相比,此控件的功能集更為豐富。
ASP.NET復合控件的要點是什么?
前一段時間,我曾經(jīng)自己嘗試在 ASP.NET. 中研究復合控件。我從 MSDN 文檔學習理論和實踐知識,并也設計出一些不錯的控件。但是,只有當我有一次在純屬偶然的情況下看到以下示例時,我才真正領悟到復合控件的要點(和優(yōu)點)。設想一下由兩個其他控件(Label 和 TextBox)的組合生成的迄今為止最簡單(也是最常見)的控件。以下介紹了一種編寫這種控件的可行方法。我們將其命名為 LabelTextBox。
- public class LabelTextBox :WebControl, INamingContainer
- {
- public string Text {
- get {
- object o = ViewState["Text"];
- if (o == null)
- return String.Empty;
- return (string) o;
- }
- set { ViewState["Text"] = value; }
- }
- public string Title {
- get {
- object o = ViewState["Title"];
- if (o == null)
- return String.Empty;
- return (string) o;
- }
- set { ViewState["Title"] = value; }
- }
- protected override void CreateChildControls()
- {
- Controls.Clear();
- CreateControlHierarchy();
- ClearChildViewState();
- }
- protected virtual void CreateControlHierarchy()
- {
- TextBox t = new TextBox();
- Label l = new Label();
- t.Text = Text;
- l.Text = Title;
- Controls.Add(l);
- Controls.Add(t);
- }
- }
該控件具備兩個公共屬性(Text 和 Title)以及一個呈現(xiàn)引擎。這兩個屬性保存在視圖狀態(tài)中,并分別表示 TextBox 和 Label 的內(nèi)容。該控件對于 Render 方法沒有替換方法,并通過 CreateChildControls 替換方法來生成其自己的標記。我馬上就會詳述呈現(xiàn)階段的例行過程。CreateChildControls 的代碼首先清除子控件的集合,然后為當前控件輸出的構成控件生成控件樹。CreateControlHierarchy 是一種特定于控件的方法,不要求必須標記為受保護和虛擬。但請注意,大多數(shù)自帶復合控件(例如 DataGrid)只是通過一個類似的虛擬方法來暴露用于生成控件樹的邏輯。
CreateControlHierarchy 方法會根據(jù)需要實例化多個構成組件,然后合成最終輸出。完成之后,各控件將被添加到當前控件的 Controls 集合。如果希望控件的輸出結果是一個 HTML 表,則可以創(chuàng)建一個 Table 控件,并相應添加含有各自內(nèi)容的行和單元格。所有行、單元格和所含控件都是最外部表的子項。這時,您只需將 Table 控件添加到 Controls 集合中即可。在上述代碼中,Label 和 TextBox 是 LabelTextBox 控件的直接子項并直接添加到集合中??丶某尸F(xiàn)狀態(tài)和運行狀態(tài)都很正常。
單純從性能上看,創(chuàng)建控件的暫態(tài)實例不如呈現(xiàn)一些純文本的效率高。讓我們考慮一種無需子控件就能編寫上述控件的替代方法。這次讓我們將其命名為 TextBoxLabel。
- public class LabelTextBox :WebControl, INamingContainer
- {
- :
- protected override void Render(HtmlTextWriter writer)
- {
- string markup = String.Format(
- "< span>{0}< /span>< input type=text value='{1}'>",
- Title, Text);
- writer.Write(markup);
- }
- }
該控件具備同樣的兩個屬性(Text 和 Title)并替換了 Render 方法。正如您所看到的那樣,其實現(xiàn)過程相當簡單并且代碼運行速度也略勝一籌。您可以通過在字符串生成器中合成文本并為瀏覽器輸出最終標記來取代合成子控件的這種方法。同樣,此時控件的呈現(xiàn)狀態(tài)良好。但我們真的可以說它的運行狀態(tài)也同樣良好嗎?圖 1 顯示了在示例頁中運行的兩個控件。
圖 1:使用不同呈現(xiàn)引擎的相似控件
在頁面中啟用跟蹤功能并重新運行。當頁面顯示在瀏覽器中時,將其向下滾動并查看控件樹。它將如下所示:
圖 2:由兩個控件生成的控件樹
ASP.NET復合控件由構成組件的活動實例組成。ASP.NET 運行時會發(fā)現(xiàn)這些子控件,并可以在處理已發(fā)布數(shù)據(jù)時同它們進行直接通信。其結果是,子控件可以自己處理視圖狀態(tài)并自動激起事件。
對于基于標記合成的控件,情況則不同。如圖中所示,該控件是一個帶有空 Controls 集合的代碼基本單位。如果標記在頁面中注入交互元素(文本框、按鈕、下拉式菜單),則 ASP.NET 在不涉及控件本身的情況下無法處理回發(fā)數(shù)據(jù)及事件。
嘗試在兩個文本框中輸入一些文本并單擊圖 1 中的“刷新”按鈕,這樣就可以發(fā)生一個回發(fā)。***個控件(即復合控件)在經(jīng)過回發(fā)后會正確保留所分配的文本。使用 Render 方法的第二個控件在經(jīng)過回發(fā)后會丟失新文本。為什么會這樣呢?其中兼有兩個原因。
***個原因是,在上述標記中我沒有為 < input> 標記命名。這樣,它的內(nèi)容就不會回發(fā)。請注意,必須使用 name 屬性來為元素命名。讓我們對 Render 方法做如下修改。
- protected override void Render(HtmlTextWriter writer)
- {
- string markup = String.Format(
- "< span>{0}< /span>< input type=text value='{1}' name='{2}'>",
- Title, Text, ClientID);
- writer.Write(markup);
- }
注入客戶端頁面的 < input> 元素現(xiàn)在與服務器控件使用相同的 ID。頁面回發(fā)時,ASP.NET 運行時可發(fā)現(xiàn)一個與已發(fā)布字段的 ID 相匹配的服務器控件。但它并不知道如何處理該控件。要使 ASP.NET 將所有的客戶端更改都應用于服務器控件,該控件必須實現(xiàn) IPostBackDataHandler 接口。
包含 TextBox 的復合控件無需擔心回發(fā)問題,因為所嵌入的控件會使用 ASP.NET 自動解決該問題。呈現(xiàn) TextBox 的控件需要與 ASP.NET 進行交互,以確??梢哉_處理回發(fā)值并正常引發(fā)事件。以下代碼表明了如何擴展 TextBoxLabel 控件以使其完全支持回發(fā)。
- bool LoadPostData(string postDataKey, NameValueCollection postCollection)
- {
- string currentText = Text;
- string postedText = postCollection[postDataKey];
- if (!currentText.Equals(postedText, StringComparison.Ordinal))
- {
- Text = postedText;
- return true;
- }
- return false;
- }
- void IPostBackDataHandler.RaisePostDataChangedEvent()
- {
- return;
- }
【編輯推薦】