C#+ASP.NET2.0定制復合組件之基礎篇
下載本文源代碼
摘要:在本系列文章中,我們首先討論使用ASP.NET 2.0構建一個標準ListBox控件的增強版本(EnhancedListBox)。這個控件能夠對它的項進行重排序,并且能夠實現客戶端與服務器端的同步功能。然后,我們把這樣的兩個控件組合起來創建一個復合控件(ListMover)。
一、 引言
構建提供豐富的客戶端接口的復雜Web控件經常需要把一些客戶端JavaScript代碼與控件的服務器端代碼集成到一起。然而,在一些情況下,為了達到某種巧妙的效果而把問題搞得過于復雜經常會破壞控件的內部服務器代碼與生成的客戶端HTML代碼之間的數據同步,而當進行頁面回寄時這將成為一個問題。在本文中,我將首先構建兩個“很酷”的Web控件(都極容易導致這一問題),然后向你展示如何來修改這一“脆弱性”。
本文中,我們將使用C#+ASP.NET 2.0來定制這些控件,并在后面向你簡短介紹如何使之工作在ASP.NET 1.1(或1.0)環境中。
當前,HTML仍然保持為Web應用程序生成階段的主要語言。遺憾的是,它所使用的協議是無狀態的,所以必須由Web開發者自己來處理這種無狀態特點。通過使用一些架構特征,例如回寄機制和ViewState變量,ASP.NET有助于處理這個問題。然而,為了實現某些功能,還需要再作努力,從而借助于回寄事件把Web頁面不斷向服務器發出請求的各種技術結合起來。
具體地說,我將分析如何使用JavaScript和DHTML存取在客戶端生成的元素。其實,把客戶端和服務器功能融合到一起要求使用大量的技巧才能達到最佳用戶體驗效果,而微軟在其ASP.NET校驗控件中就實現了這一點。為了提供一種豐富的客戶端校驗效果,該控件中使用了大量的JavaScript。
作者注:本文假定你對定制Web控件開發有一個基本了解。因此,我將不再重復Web控件開發的基礎內容,例如屬性工作原理與風格的添加方式。
二、 一種常規實現方法
下面,我想向你展示如何構建一組很酷的控件,它們具有你在商業控件中才能看到的優秀功能。稍后,我將繼續展示定制Web控件帶給Web編程的完全封裝優點。既然你已經了解如何開發定制Web控件,那么你應該知道的一個概念是封裝一個控件所有的功能和行為(就象你在一個標準業務對象中所實現的那樣)。在學習構建具有復雜行為的控件時,這種封裝將極有用處。
在第一個控件中,我將向你展示如何構建一個稱為EnhancedListBox的控件。這個控件將擴展ASP.NET的ListBox控件—添加一個頭部和一些重排序按鈕。注意,這是一個直接繼承自常規ListBox的控件。
之后,我還將向你展示如何構建一個復合控件—ListMover,它將包含兩個上面提到的EnhancedListBox控件。這個ListMover控件還包含一些允許你從一個列表到另一個列表中移動項的按鈕。
其實,用常規方法(非面向Web控件的)來實現這種ASP.NET功能也并不困難。首先,你要把一個常規ListBox控件拖動到你的Web表單上并且使用一些數據填充它。
然后,再添加一個標簽用作標題,還有一組按鈕用作重排序按鈕。捕獲這些按鈕的服務器端事件是ASP.NET中的標準操作;因此,你只需要使用一種方法來取得當前選定的項并且根據用戶點擊的按鈕從而把它放到該列表中的更高或更低的位置即可。例如,你可能編寫如下的代碼實現移動列表中的一項。
下面,讓我進行簡單的分析。首先,我保存了當前的列表中選定項的索引值與當前項。然后,我在當前位置刪除該項;之后,在一個較低位置(上一個索引值減1)重新插入該項。這里的邏輯非常簡單,那么為什么我還要說明這個問題呢?
借助于這種常規的ASP.NET編程方法,Web表單上面的重排序按鈕將會引發一個實現ListBox中重排序的服務器端事件。這是由一個到服務器的回寄觸發的;因此,這個回寄可能是一次“繁重的”往返,具體要信賴于表單上的具體內容及因特網速度。
然而,因為這一代碼實現的是一個標準ASP.NET回寄過程,所以由ASP.NET使用它的ViewState機制來負責狀態處理。當再次生成頁面時,列表框內容按要求的順序正確生成。
當然,你也可以使用與此相同的常規方式在ListMover控件中重新創建這個功能。篇幅所限,我在此省略,只好留待讀者您來實現。這個Web表單上包含一對ListBox,還有一些指示從左向右或從右向左移動的按鈕。這些按鈕的服務器端事件將從一個ListBox中提取選擇的項,然后把它添加到另一個列表中;反之亦然。如在剛才的例子中所展示的,ViewState在此能夠完好工作以保持這兩個ListBox中的項。
三、 目的
下面,我想向你展示如何把剛才描述的這些例子中所用的單個控件放到一個Web表單上。你可能猜出,我將向你展示如何把這兩個例子中的功能封裝到它們自己的一個Web控件中。借助于與在常規方法示例中描述的相同的服務器端事件模型,我們可以把所有的行為封裝到每一個控件中來實現必要的功能。既然每一個控件都能夠控制它自己的狀態,那么包含它們的Web表單不必要做任何額外的工作。
到目前為止,一切順利。你可能問:“問題在哪里?”很好,假定頁面開發者在含有大量內容的頁面上使用這兩個控件,而且每當發生一次重排序或移動,都需要到服務器端的重回寄時,這顯然不是一個高效的Web站點要實現的。這正是使用一些JavaScript的原因。
在本例中,你要使用JavaScript代碼來存取EnhancedListBox控件中ListBox的內容以便在客戶端進行重排序。
在ListMover控件中,JavaScript代碼將把項從一個列表移動到另一個列表。其最終結果是一樣的,但是不需要進行服務器來回傳送,因為不需要觸發任何回寄。這樣以來,你就可以解決即時響應和不需要回饋的問題。
四、 問題
ASP.NET在服務器端生成內容與在客戶端生成內容之間有明顯的界定。事實上,大部分情況下,這兩部分沒有關系;因此,問題出現了。其實,一個Web控件只是一個服務器端組件,它負責把HTML生成到瀏覽器端。的確,標準ASP.NET ListBox控件正是以HTML形式生成一個ListBox(作為一個<select>標簽)。
在<select>標簽中的<option>子標簽可以使用ListBox控件中的Item屬性的內容來創建。Item屬性在服務器端被填充,而其內容有助于在生成期間構建適當的HTML。這非常類似于生成一個<input>標簽的文本框Web控件,而它的Text屬性映射到<input>標簽的Value屬性。每當觸發一個到服務器的頁面回寄時,ListBox控件的Item屬性都被保存到ViewState中,并且在重新生成頁面前從ViewState中進行重建。
在EnhancedListBox中進行重排序或在服務器端的ListMover中移動項都非常直接,并且允許支持正常的內置的ViewState機制而不需要我們作任何干擾。但是,當你使用客戶端JavaScript添加這一能力來實現它們的功能時,它將破壞ViewState。這些控件并不再轉回到服務器端,所以Item集合屬性永遠不會被保存以便在重新生成時被重載。代之的是,直接在HTML級別上存取生成的<select>標簽中的<option>項。你可以借助JavaScript代碼移動或重排序控件項;但是,當在頁面上再次發生回寄時,你猜發生了什么?在移動(或重排序)開始前,控件的列表項就恢復它們的狀態。
我說過,如果功能發生在回寄期間的服務器端,那么,ViewState被保存并且被良好重載,從而使Item集合正確填充。但是,既然你的最終目標是在客戶端實現這個功能,那么你就不再需要重新調整Item屬性的內容,而是由你依賴的這個屬性負責狀態存儲。現在,你可能會為難了。但是別擔心—我有一個解決方案。現在,讓我們開始使用必要的客戶端腳本代碼來開發該控件來實現每一個子控件所需要的功能。然后,我將向你展示如何使它與服務器代碼保持重新同步。
五、 EnhancedListBox控件
在這個控件中,你要把兩部分內容添加到現有ASP.NET ListBox控件。首先,添加一個頭部—把一個標簽放到一個ListBox的上方。然后,把兩個按鈕添加到ListBox—分別用于向下和向上重排序。
注意 為了簡單起見,我在后面所有的代碼描述中省略所有的屬性部分。
現在,創建一個繼承自ListBox控件的新類,如下所示:
如果你編譯這部分代碼并且把該控件添加到你的工具箱中,那么你將有一個完整功能的ASP.NET ListBox控件副本。我把這個控件作為一個繼承控件開發,是因為我想使它具有一個ASP.NET ListBox控件的“占位符”的作用。以后,我再添加其它的屬性以實現頭部的可見性并支持重排序按鈕的打開或關閉。當這些屬性全部關閉時,這些控件將在外觀與行為上與一個常規ListBox控件一樣。然而,你不能使用一個重載的CreateChildControls把控件添加到其上,因為這個函數是用來構建一個控件層次樹的。這個ASP.NET ListBox控件被編寫為一個生成控件而且直接把它的所有HTML內容繪制到生成引擎;這樣以來,你需要在此處“注入”你的內容。你將使用生成控件方法來構建一個標簽和兩個按鈕,并且通過重載Render方法來生成它們。然而,一旦你重載這個方法,你就完全取消了所有的在原始ListBox中的生成內容,而這是不可取的。因此,我想借助于一些小技巧來實現。
六、 代碼注入
我的方案是,以一個標準生成控件方式來繪制這個控件,其中包括table標簽以及該標簽與我添加的按鈕的生成方式等。當我編寫生成顯示部分時,也就是在我想注入繼承的原始的ListBox的地方,我調用了base.Render方法。這將把微軟為ListBox控件編寫的所有代碼注入到我試圖繪制的HTML部分(見源碼中的列表1)。下列表格1中列出的屬性決定了這部分代碼的外觀與行為。在本文中,我沒有列出相應的屬性代碼,但是你可以在下載源代碼中找到。注意,在代碼中,你要生成的按鈕將導致一個回寄(基于屬性ReorderButtonPostback的值);而對接口IPostBackEventHandler的實現將捕獲這個回寄。
表格1:EnhancedListBox屬性
現在,既然該控件已經看上去如你希望的樣式(見圖1),那么你可以讓該按鈕多負責一些工作而不是僅引發一個回寄。最終的產品中包含事件處理代碼;這部分代碼位于接口IPostBackEventHandler的實現中,這樣以來事件能夠被有選擇地向服務器激發,而另一方面開發者也可以在此處加入更多的代碼。但是記住,你要使用這些按鈕來重排序ListBox中的項,并且希望在不執行回寄的情況下實現這一功能。現在,我們開始分析最有趣的部分。
摘要:在本系列文章中,我們首先討論使用ASP.NET 2.0構建一個標準ListBox控件的增強版本(EnhancedListBox)。這個控件能夠對它的項進行重排序,并且能夠實現客戶端與服務器端的同步功能。然后,我們把這樣的兩個控件組合起來創建一個復合控件(ListMover)。
一、 引言
構建提供豐富的客戶端接口的復雜Web控件經常需要把一些客戶端JavaScript代碼與控件的服務器端代碼集成到一起。然而,在一些情況下,為了達到某種巧妙的效果而把問題搞得過于復雜經常會破壞控件的內部服務器代碼與生成的客戶端HTML代碼之間的數據同步,而當進行頁面回寄時這將成為一個問題。在本文中,我將首先構建兩個“很酷”的Web控件(都極容易導致這一問題),然后向你展示如何來修改這一“脆弱性”。
本文中,我們將使用C#+ASP.NET 2.0來定制這些控件,并在后面向你簡短介紹如何使之工作在ASP.NET 1.1(或1.0)環境中。
當前,HTML仍然保持為Web應用程序生成階段的主要語言。遺憾的是,它所使用的協議是無狀態的,所以必須由Web開發者自己來處理這種無狀態特點。通過使用一些架構特征,例如回寄機制和ViewState變量,ASP.NET有助于處理這個問題。然而,為了實現某些功能,還需要再作努力,從而借助于回寄事件把Web頁面不斷向服務器發出請求的各種技術結合起來。
具體地說,我將分析如何使用JavaScript和DHTML存取在客戶端生成的元素。其實,把客戶端和服務器功能融合到一起要求使用大量的技巧才能達到最佳用戶體驗效果,而微軟在其ASP.NET校驗控件中就實現了這一點。為了提供一種豐富的客戶端校驗效果,該控件中使用了大量的JavaScript。
作者注:本文假定你對定制Web控件開發有一個基本了解。因此,我將不再重復Web控件開發的基礎內容,例如屬性工作原理與風格的添加方式。
二、 一種常規實現方法
下面,我想向你展示如何構建一組很酷的控件,它們具有你在商業控件中才能看到的優秀功能。稍后,我將繼續展示定制Web控件帶給Web編程的完全封裝優點。既然你已經了解如何開發定制Web控件,那么你應該知道的一個概念是封裝一個控件所有的功能和行為(就象你在一個標準業務對象中所實現的那樣)。在學習構建具有復雜行為的控件時,這種封裝將極有用處。
在第一個控件中,我將向你展示如何構建一個稱為EnhancedListBox的控件。這個控件將擴展ASP.NET的ListBox控件—添加一個頭部和一些重排序按鈕。注意,這是一個直接繼承自常規ListBox的控件。
之后,我還將向你展示如何構建一個復合控件—ListMover,它將包含兩個上面提到的EnhancedListBox控件。這個ListMover控件還包含一些允許你從一個列表到另一個列表中移動項的按鈕。
其實,用常規方法(非面向Web控件的)來實現這種ASP.NET功能也并不困難。首先,你要把一個常規ListBox控件拖動到你的Web表單上并且使用一些數據填充它。
然后,再添加一個標簽用作標題,還有一組按鈕用作重排序按鈕。捕獲這些按鈕的服務器端事件是ASP.NET中的標準操作;因此,你只需要使用一種方法來取得當前選定的項并且根據用戶點擊的按鈕從而把它放到該列表中的更高或更低的位置即可。例如,你可能編寫如下的代碼實現移動列表中的一項。
| i_Index = ListBox1.SelectedIndex; o_Item = ListBox1.SelectedItem; ListBox1.Items.RemoveAt(this.SelectedIndex); i_Index--; if(i_Index < 0) i_Index = 0; ListBox1.Items.Insert(i_Index, o_Item); |
下面,讓我進行簡單的分析。首先,我保存了當前的列表中選定項的索引值與當前項。然后,我在當前位置刪除該項;之后,在一個較低位置(上一個索引值減1)重新插入該項。這里的邏輯非常簡單,那么為什么我還要說明這個問題呢?
借助于這種常規的ASP.NET編程方法,Web表單上面的重排序按鈕將會引發一個實現ListBox中重排序的服務器端事件。這是由一個到服務器的回寄觸發的;因此,這個回寄可能是一次“繁重的”往返,具體要信賴于表單上的具體內容及因特網速度。
然而,因為這一代碼實現的是一個標準ASP.NET回寄過程,所以由ASP.NET使用它的ViewState機制來負責狀態處理。當再次生成頁面時,列表框內容按要求的順序正確生成。
當然,你也可以使用與此相同的常規方式在ListMover控件中重新創建這個功能。篇幅所限,我在此省略,只好留待讀者您來實現。這個Web表單上包含一對ListBox,還有一些指示從左向右或從右向左移動的按鈕。這些按鈕的服務器端事件將從一個ListBox中提取選擇的項,然后把它添加到另一個列表中;反之亦然。如在剛才的例子中所展示的,ViewState在此能夠完好工作以保持這兩個ListBox中的項。
三、 目的
下面,我想向你展示如何把剛才描述的這些例子中所用的單個控件放到一個Web表單上。你可能猜出,我將向你展示如何把這兩個例子中的功能封裝到它們自己的一個Web控件中。借助于與在常規方法示例中描述的相同的服務器端事件模型,我們可以把所有的行為封裝到每一個控件中來實現必要的功能。既然每一個控件都能夠控制它自己的狀態,那么包含它們的Web表單不必要做任何額外的工作。
到目前為止,一切順利。你可能問:“問題在哪里?”很好,假定頁面開發者在含有大量內容的頁面上使用這兩個控件,而且每當發生一次重排序或移動,都需要到服務器端的重回寄時,這顯然不是一個高效的Web站點要實現的。這正是使用一些JavaScript的原因。
在本例中,你要使用JavaScript代碼來存取EnhancedListBox控件中ListBox的內容以便在客戶端進行重排序。
在ListMover控件中,JavaScript代碼將把項從一個列表移動到另一個列表。其最終結果是一樣的,但是不需要進行服務器來回傳送,因為不需要觸發任何回寄。這樣以來,你就可以解決即時響應和不需要回饋的問題。
四、 問題
ASP.NET在服務器端生成內容與在客戶端生成內容之間有明顯的界定。事實上,大部分情況下,這兩部分沒有關系;因此,問題出現了。其實,一個Web控件只是一個服務器端組件,它負責把HTML生成到瀏覽器端。的確,標準ASP.NET ListBox控件正是以HTML形式生成一個ListBox(作為一個<select>標簽)。
在<select>標簽中的<option>子標簽可以使用ListBox控件中的Item屬性的內容來創建。Item屬性在服務器端被填充,而其內容有助于在生成期間構建適當的HTML。這非常類似于生成一個<input>標簽的文本框Web控件,而它的Text屬性映射到<input>標簽的Value屬性。每當觸發一個到服務器的頁面回寄時,ListBox控件的Item屬性都被保存到ViewState中,并且在重新生成頁面前從ViewState中進行重建。
在EnhancedListBox中進行重排序或在服務器端的ListMover中移動項都非常直接,并且允許支持正常的內置的ViewState機制而不需要我們作任何干擾。但是,當你使用客戶端JavaScript添加這一能力來實現它們的功能時,它將破壞ViewState。這些控件并不再轉回到服務器端,所以Item集合屬性永遠不會被保存以便在重新生成時被重載。代之的是,直接在HTML級別上存取生成的<select>標簽中的<option>項。你可以借助JavaScript代碼移動或重排序控件項;但是,當在頁面上再次發生回寄時,你猜發生了什么?在移動(或重排序)開始前,控件的列表項就恢復它們的狀態。
我說過,如果功能發生在回寄期間的服務器端,那么,ViewState被保存并且被良好重載,從而使Item集合正確填充。但是,既然你的最終目標是在客戶端實現這個功能,那么你就不再需要重新調整Item屬性的內容,而是由你依賴的這個屬性負責狀態存儲。現在,你可能會為難了。但是別擔心—我有一個解決方案。現在,讓我們開始使用必要的客戶端腳本代碼來開發該控件來實現每一個子控件所需要的功能。然后,我將向你展示如何使它與服務器代碼保持重新同步。
五、 EnhancedListBox控件
在這個控件中,你要把兩部分內容添加到現有ASP.NET ListBox控件。首先,添加一個頭部—把一個標簽放到一個ListBox的上方。然后,把兩個按鈕添加到ListBox—分別用于向下和向上重排序。
注意 為了簡單起見,我在后面所有的代碼描述中省略所有的屬性部分。
現在,創建一個繼承自ListBox控件的新類,如下所示:
| using System.Web.UI; using System.Web.UI.WebControls; public class EnhancedListBox : ListBox {} |
如果你編譯這部分代碼并且把該控件添加到你的工具箱中,那么你將有一個完整功能的ASP.NET ListBox控件副本。我把這個控件作為一個繼承控件開發,是因為我想使它具有一個ASP.NET ListBox控件的“占位符”的作用。以后,我再添加其它的屬性以實現頭部的可見性并支持重排序按鈕的打開或關閉。當這些屬性全部關閉時,這些控件將在外觀與行為上與一個常規ListBox控件一樣。然而,你不能使用一個重載的CreateChildControls把控件添加到其上,因為這個函數是用來構建一個控件層次樹的。這個ASP.NET ListBox控件被編寫為一個生成控件而且直接把它的所有HTML內容繪制到生成引擎;這樣以來,你需要在此處“注入”你的內容。你將使用生成控件方法來構建一個標簽和兩個按鈕,并且通過重載Render方法來生成它們。然而,一旦你重載這個方法,你就完全取消了所有的在原始ListBox中的生成內容,而這是不可取的。因此,我想借助于一些小技巧來實現。
六、 代碼注入
我的方案是,以一個標準生成控件方式來繪制這個控件,其中包括table標簽以及該標簽與我添加的按鈕的生成方式等。當我編寫生成顯示部分時,也就是在我想注入繼承的原始的ListBox的地方,我調用了base.Render方法。這將把微軟為ListBox控件編寫的所有代碼注入到我試圖繪制的HTML部分(見源碼中的列表1)。下列表格1中列出的屬性決定了這部分代碼的外觀與行為。在本文中,我沒有列出相應的屬性代碼,但是你可以在下載源代碼中找到。注意,在代碼中,你要生成的按鈕將導致一個回寄(基于屬性ReorderButtonPostback的值);而對接口IPostBackEventHandler的實現將捕獲這個回寄。
表格1:EnhancedListBox屬性
| 屬性名 | 類別 |
| UpButtonCaption | Appearance |
| DownButtonCaption | Appearance |
| ShowReorderButtons | Appearance |
| ShowHeading | Appearance |
| HeadingCaption | Appearance |
| ReorderButtonPostBack | Behavior |
| SideButtonWidth | Layout |
| SideButtonHeight | Layout |
| SideButtonVerticalAlign | Layout |
| ReorderButtonSide | Layout |
| UpButtonStyle | Styles |
| DownButtonStyle | Styles |
| ListBoxStyle | Styles |
| HeadingStyles | Styles |
現在,既然該控件已經看上去如你希望的樣式(見圖1),那么你可以讓該按鈕多負責一些工作而不是僅引發一個回寄。最終的產品中包含事件處理代碼;這部分代碼位于接口IPostBackEventHandler的實現中,這樣以來事件能夠被有選擇地向服務器激發,而另一方面開發者也可以在此處加入更多的代碼。但是記住,你要使用這些按鈕來重排序ListBox中的項,并且希望在不執行回寄的情況下實現這一功能。現在,我們開始分析最有趣的部分。
![]() 圖1.EnhancedListBox控件讓用戶重排序一個列表中的項。 |
