下載實(shí)例 
簡介 
當(dāng)創(chuàng)建數(shù)據(jù)驅(qū)動(dòng)的 web 站點(diǎn)時(shí),web 開發(fā)人員遇到的最常見的任務(wù)之一就是創(chuàng)建數(shù)據(jù)輸入窗體。數(shù)據(jù)輸入窗體是為系統(tǒng)用戶提供數(shù)據(jù)輸入方法的 web 頁。創(chuàng)建特定數(shù)據(jù)輸入窗體的任務(wù)通常應(yīng)該先從需求分析入手,即,明確指出需要從用戶收集何種信息。需求確定之后,下一步是設(shè)計(jì)數(shù)據(jù)輸入 web 窗體,包括創(chuàng)建圖形用戶界面以及編寫根據(jù)用戶輸入更新數(shù)據(jù)庫的代碼。 
當(dāng)數(shù)據(jù)輸入窗體需求事先已知,并且此數(shù)據(jù)輸入窗體對系統(tǒng)中所有用戶都一樣時(shí),創(chuàng)建這樣的輸入窗體就毫無挑戰(zhàn)性。然而,如果需要?jiǎng)討B(tài)的數(shù)據(jù)輸入窗體,任務(wù)就會(huì)變得更艱巨。例如,考慮一個(gè)公司的 internet web 應(yīng)用程序,其目的是收集客戶購買的產(chǎn)品的信息;一種產(chǎn)品在線注冊系統(tǒng)。對于這樣的應(yīng)用程序,向用戶提出的問題可能會(huì)因購買不同產(chǎn)品而異,或者因從店鋪購買還是從公司 web 站點(diǎn)購買而異。 
如上面提到的例子,當(dāng)遇到需要提供動(dòng)態(tài)數(shù)據(jù)輸入用戶界面時(shí),一種選擇可能是“強(qiáng)加”一種解決方案。您可以為公司銷售的每種產(chǎn)品創(chuàng)建獨(dú)立的 web 頁,每張頁面包含需要的特定數(shù)據(jù)輸入元素。這種原始方法的問題是當(dāng)發(fā)布新產(chǎn)品時(shí),就需要添加新的頁面。雖然創(chuàng)建這些新頁面可能不會(huì)很困難,但是卻很耗時(shí),而且如果沒有充分的調(diào)試和測試時(shí)間就很容易出錯(cuò)。 
理想情況下,當(dāng)發(fā)布新產(chǎn)品時(shí),應(yīng)由某個(gè)非技術(shù)人員通過易用的、基于 web 的界面來指定需要提出什么問題。這樣的系統(tǒng)對 asp.net 來說是可以實(shí)現(xiàn)的,因?yàn)樗邆湓谶\(yùn)行時(shí)往 asp.net web 頁動(dòng)態(tài)加載控件的能力。只需要在開發(fā)和測試時(shí)投入少量的初期投資,您就可以創(chuàng)建一個(gè)可重復(fù)使用的、動(dòng)態(tài)的數(shù)據(jù)輸入用戶界面引擎。即使對計(jì)算機(jī)了解甚少的用戶,通過這種技術(shù)都可以輕松地創(chuàng)建自定義數(shù)據(jù)輸入窗體。在本文中,我們會(huì)介紹使用 asp.net 中的動(dòng)態(tài)控件的基礎(chǔ)知識(shí),然后介紹一個(gè)完整的、可運(yùn)轉(zhuǎn)的動(dòng)態(tài)數(shù)據(jù)輸入系統(tǒng),可以輕松地對它進(jìn)行自定義和擴(kuò)展。 
asp.net 中的動(dòng)態(tài)控件入門 
眾所周知,asp.net web 頁由兩部分組成:  
? html 部分,它包含靜態(tài)的 html 標(biāo)記和 web 控件,通過聲明性語法來添加。 
  
? 代碼部分,可以作為獨(dú)立的類文件實(shí)現(xiàn)(如采用 visual studio .net),或者包含在 html 文件的 <script runat="server"> 塊中。  
  
asp.net web 頁的 web 控件是在設(shè)計(jì)時(shí)通過聲明性語法來添加的,它明確指出了要添加的 web 控件及其初始屬性值,如: 
<asp:webcontrolname runat="server" prop1="value1" prop2="value2" ... propn="valuen"> </asp:webcontrolname>
要理解的一個(gè)重點(diǎn)是,當(dāng)?shù)谝淮卧L問 asp.net 頁面,或者當(dāng)其 html 部分修改后第一次訪問時(shí),asp.net 引擎會(huì)自動(dòng)將混合的靜態(tài) html 內(nèi)容和 web 控件語法轉(zhuǎn)換成一個(gè)類。這個(gè)自動(dòng)生成的類的作用是創(chuàng)建控件層次結(jié)構(gòu)。這個(gè)控件層次結(jié)構(gòu)是組成頁面的控件集 — 靜態(tài)的 html 標(biāo)記轉(zhuǎn)換成 literalcontrol 實(shí)例,而 web 控件轉(zhuǎn)換成相應(yīng)類類型的實(shí)例(例如, 轉(zhuǎn)換成 system.web.ui.webcontrols 命名空間中的 textbox 類的實(shí)例)。 
之所以稱為控件層次結(jié)構(gòu)是因?yàn)樗强丶恼嬲膶哟谓Y(jié)構(gòu)。每個(gè) asp.net 服務(wù)器控件可以有一組子控件和一個(gè)父控件。當(dāng)自動(dòng)生成的類構(gòu)造控件層次結(jié)構(gòu)時(shí),它會(huì)將代表 asp.net 頁面的 page 類實(shí)例放在層次結(jié)構(gòu)的頂層。page 類的子控件是那些在頁面的 html(通常是一些靜態(tài)的 html 標(biāo)記以及 web 窗體的服務(wù)器控件)中定義的頂級(jí)服務(wù)器控件。(asp.net 頁面的 web 窗體 — 也就是 <form runat="server">標(biāo)記 — 是作為 htmlform 類的實(shí)例實(shí)現(xiàn)的,可以在 system.web.ui.htmlcontrols 命名空間中找到這個(gè)類。) 
和任何其他服務(wù)器控件一樣,這個(gè) web 窗體可以包含子控件。web 窗體的子控件是那些在該 web 窗體本身中發(fā)現(xiàn)的控件。甚至 web 窗體中的控件本身還可能有子控件:panel 控件的內(nèi)容構(gòu)成了其子控件;當(dāng)將數(shù)據(jù)綁定到一個(gè) datagrid 時(shí),產(chǎn)生的內(nèi)容構(gòu)成了它的子控件集。因?yàn)轫敿?jí) page 類可能有子控件,子控件又有子控件,子控件又有子控件,等等,這組控件就構(gòu)成了控件層次結(jié)構(gòu)。 
為了幫助徹底理解這個(gè)概念(理解它對使用動(dòng)態(tài)控件是至關(guān)重要的),請想象您有一個(gè) asp.net 頁面,它在 html 部分有以下內(nèi)容: 
<html> <body> <h1>welcome to my homepage!</h1> <form runat="server"> what is your name? <asp:textbox runat="server" id="txtname"></asp:textbox> <br />what is your gender? <asp:dropdownlist runat="server" id="ddlgender"> <asp:listitem select="true" value="m">male</asp:listitem> <asp:listitem value="f">female</asp:listitem> <asp:listitem value="u">undecided</asp:listitem> </asp:dropdownlist> <br /> <asp:button runat="server" text="submit!"></asp:button> </form> </body> </html>
當(dāng)?shù)谝淮卧L問該頁面時(shí),會(huì)自動(dòng)生成一個(gè)類,這個(gè)類包含以編程方式構(gòu)建控件層次結(jié)構(gòu)的代碼。這個(gè)示例的控件層次結(jié)構(gòu)如圖 1 所示。 
圖 1. 控件層次結(jié)構(gòu)
以編程方式使用控件層次結(jié)構(gòu) 
正如前面提到的,每個(gè) asp.net 服務(wù)器控件可以包含一組子控件和一個(gè)父控件。子控件可通過類型為 controlcollection 的服務(wù)器控件的 controls 屬性訪問。controlcollection 類提供了以下功能:  
? 使用 count 只讀屬性來確定有多少子控件。  
  
? 使用 add() 或 addat() 方法向控件集合添加新項(xiàng)。  
  
? 通過 clear() 方法刪除所有子控件,或者通過 remove() 或 removeat() 方法刪除特定控件。  
  
要將一個(gè)控件作為 x 控件的子控件添加到控件層次結(jié)構(gòu)中,只需創(chuàng)建該控件的相應(yīng)類實(shí)例并添加到 x 控件的 controls 集合中。例如,要向 page 類的 controls 集合添加一個(gè) label 控件,可以使用下列代碼: 
’create a new label instance dim lbl as new label ’add the control to the page’s controls collection page.controls.add(lbl) ’set the label’s text property to the current date/time lbl.text = datetime.now
在 page 的 controls 集合尾部添加控件會(huì)使該控件出現(xiàn)在 web 頁的底部。如果您需要的控件比動(dòng)態(tài)添加的控件的位置多,您可以在頁面中添加一個(gè) placeholder web 控件,在層次結(jié)構(gòu)中指定要添加一個(gè)或多個(gè)動(dòng)態(tài)控件的位置。要在該位置中添加動(dòng)態(tài)控件,只需將它們添加到 placeholder 的 controls 集合中。例如,如果您想將 label 放在 web 窗體中的某個(gè)點(diǎn),您可以按如下方式添加一個(gè) placeholder 控件: 
<html> <body> ... <form runat="server"> ... <asp:placeholder runat="server" id="datetimelabel"></asp:placeholder> ... </form> </body> </html> 
要在上一個(gè)示例中添加動(dòng)態(tài)的 label,不應(yīng)該使用 page.controls.add(lbl),而應(yīng)該使用 datetimelabel.controls.add(lbl),從而將該 label 添加到 placeholder 的 controls 集合中,而不是添加到 page 的 controls 集合中。圖 2 圖示了將動(dòng)態(tài) label 添加到 placeholder 的 controls 集合前后的控件層次結(jié)構(gòu)。 
圖 2. 圖示了添加動(dòng)態(tài) label 前后的控件層次結(jié)構(gòu)
通常,最好的方式是使用 add() 方法將動(dòng)態(tài)控件添加到 controls 集合的尾部,而不是使用 addat() 將其添加到集合中的特定位置。其原因在于,視圖狀態(tài)的保存方式是每個(gè)控件記錄自己的視圖狀態(tài)及其子控件的視圖狀態(tài)。當(dāng)保存其子控件的視圖狀態(tài)時(shí),每個(gè)控件記錄子控件的視圖狀態(tài)及該控件在 controls 集合中的序號(hào)索引。  
在回發(fā)過程中,當(dāng)重新加載視圖狀態(tài)時(shí),將反向執(zhí)行這一過程,同時(shí)每個(gè)控件加載其子控件的視圖狀態(tài)。重新加載視圖狀態(tài)的控件通過視圖狀態(tài)信息枚舉,在 controls 集合的指定位置應(yīng)用該控件的視圖狀態(tài)。如果您在視圖狀態(tài)加載之前在 controls 集合的非尾部位置插入一個(gè)控件,則會(huì)出現(xiàn)問題,因?yàn)槊總€(gè)子控件的視圖狀態(tài)信息是與 controls 集合中的特定索引相連的。 
要查看在非尾部位置添加動(dòng)態(tài)控件為何會(huì)導(dǎo)致重新加載視圖狀態(tài)的問題,請參考圖 3。圖 3 顯示了一個(gè)服務(wù)器控件 p,它具有三個(gè)子控件:c0、c1 和 c2,其中控件 c1 有一些視圖狀態(tài)在回發(fā)過程中保持不變。如果在回發(fā)過程中向 p 的 controls 集合前端添加一個(gè)動(dòng)態(tài)控件 c,則當(dāng)重新加載視圖狀態(tài)時(shí),p 會(huì)試圖重新加載索引 1 中的 c1 的視圖狀態(tài),而它現(xiàn)在已被 c0 所占用。 
圖 3. 具有三個(gè)子控件的服務(wù)器控件 p
當(dāng)刪除控件時(shí),也同樣會(huì)出現(xiàn)與視圖狀態(tài)相關(guān)的問題。當(dāng)然,這一切都取決于在頁面生命周期的什么時(shí)候添加或刪除控件。有關(guān)視圖狀態(tài)、頁面生命周期,以及添加和刪除動(dòng)態(tài)控件與視圖狀態(tài)的相關(guān)問題等的更詳細(xì)討論,請務(wù)必閱讀我以前的文章 understanding asp.net view state。
訪問動(dòng)態(tài)添加的控件 
當(dāng)向 asp.net 頁面添加靜態(tài) web 控件時(shí),visual studio .net 會(huì)自動(dòng)在代碼隱藏類中添加對 web 控件的引用。這些對 web 控件的引用允許對控件、其屬性及方法進(jìn)行強(qiáng)類型訪問。當(dāng)處理動(dòng)態(tài)添加的控件時(shí),可以使用兩種技術(shù)來訪問控件的屬性、方法和事件。 
一種方法是通過對控件層次結(jié)構(gòu)進(jìn)行徹底的檢查,從而發(fā)現(xiàn)動(dòng)態(tài)控件。例如,以下代碼演示了如何遞歸循環(huán)訪問以指定控件為根的控件層次結(jié)構(gòu)。例如,如果已將大量 dropdownlist 控件動(dòng)態(tài)添加到指定的 placeholder 中,則這樣的代碼就十分有用。在這種情況下,您可以通過調(diào)用 recursethroughcontrolhierarchy(placeholdercontrol) 來枚舉 placeholder 的控件子代,在“do whatever it is you need to do with the current control, c 的類型是否是 dropdownlist,如果是,就采取某種操作。 
private sub recursethroughcontrolhierarchy()sub recursethroughcontrolhierarchy(byval c as control) ’do whatever it is you need to do with the current control, c ’recurse through c’s children controls for each child as control in c.controls recursethroughcontrolhierarchy(child) next end sub
如果您有大量相似的服務(wù)器控件需要共同處理,則上述方法行得通。但在很多情況下,您可能有大量不同的控件,需要在不同時(shí)間分別訪問并對每個(gè)控件執(zhí)行不同的操作。要以編程方式處理特定的動(dòng)態(tài)添加的控件,您可以使用 findcontrol(id) 方法,根據(jù)控件的 id 搜索控件。findcontrol() 方法是在 system.web.ui.control 類中定義的,所以所有的 服務(wù)器控件,從 textbox 到 placeholder,再到 web 窗體,都有這個(gè)方法。 
調(diào)用一個(gè)控件的 findcontrol() 方法并不需要搜索該控件的所有子代控件。findcontrol() 只搜索當(dāng)前的命名容器 (naming container)。實(shí)現(xiàn) inamingcontainer 的控件行為上就像一個(gè)命名容器,意味著它們在控件層次結(jié)構(gòu)中創(chuàng)建自己的 id 命名空間。例如,datagrid 控件是一個(gè)命名容器。給定一個(gè) id 為 mydatagrid 的 datagrid,其子控件的 id 以父控件的 id 為前綴,如 mydatagrid:childid。重要的是認(rèn)識(shí)到 findcontrol() 只枚舉子控件集或命名容器中的控件,而非控件層次結(jié)構(gòu)中父控件的所有子代。(另外,要使搜索范圍超越命名容器中的第一級(jí)控件,您需要使用作用域恰當(dāng)?shù)?nbsp;id。)其要點(diǎn)是,當(dāng)使用 findcontrol() 來查尋動(dòng)態(tài)添加的控件時(shí),要從該動(dòng)態(tài)控件的父控件(通常是 placeholder 控件)調(diào)用 findcontrol()。 
當(dāng)使用 findcontrol() 方法時(shí),可以使用如下代碼來分配一個(gè)唯一的 id 給動(dòng)態(tài)添加的控件,然后引用上述控件。 
’when adding the control, set the id property dim tb as new textbox placeholderid.controls.add(tb) tb.id = "dyntextbox" ’at some later point in the page lifecycle, ’reference the dynamic textbox dim dtb as textbox dtb = ctype(placeholderid.findcontrol("dyntextbox"), textbox)
由于 findcontrol() 方法使用控件的 id 來定位控件,所以當(dāng)使用這種技術(shù)來訪問動(dòng)態(tài)添加的控件時(shí),為每個(gè)動(dòng)態(tài)添加的控件的 id 屬性分配一個(gè)唯一可識(shí)別的值是很重要的。根據(jù)情況的不同,可以使用不同的方法。我們在本文后面也將看到,當(dāng)檢查動(dòng)態(tài)數(shù)據(jù)輸入用戶界面引擎時(shí),每個(gè)動(dòng)態(tài)問題都由數(shù)據(jù)庫中的一行表示,它包含一個(gè)唯一的主鍵字段。這個(gè)主鍵字段值即在 asp.net 頁面中作為每個(gè)動(dòng)態(tài)添加的控件的 id 使用。如果您不需要區(qū)分動(dòng)態(tài)添加的控件,則可以使用另一種技術(shù),該技術(shù)向這些控件提供遞增的編號(hào)作為 id,如 mydynctrl1 用于第一個(gè)動(dòng)態(tài)添加的控件,mydynctrl2 用于第二個(gè),等等。 
頁面生命周期和動(dòng)態(tài)控件 
任何時(shí)候訪問一個(gè) asp.net web 頁(不管是初始頁面訪問還是回發(fā)),每次 asp.net 引擎自動(dòng)生成的類都會(huì)從頭開始重新構(gòu)建控件層次結(jié)構(gòu)。不僅重新構(gòu)造控件層次結(jié)構(gòu),而且將控件的事件重新連接到其指定事件處理程序。因此,當(dāng)向 asp.net 頁面添加動(dòng)態(tài)控件時(shí),確保在每次 頁面訪問添加這些控件是很重要的。許多開發(fā)人員在開始添加動(dòng)態(tài)控件時(shí)都使用以下模式來實(shí)現(xiàn): 
’in the page_load event handler... if not page.ispostback then ’add dynamic controls... end if
這段代碼的問題是它只在第一次頁面訪問時(shí)添加動(dòng)態(tài)控件,而在后續(xù)回發(fā)時(shí)則沒有添加。如果您嘗試使用這段代碼,您會(huì)發(fā)現(xiàn),只要發(fā)生回發(fā),您的動(dòng)態(tài)控件就會(huì)從頁面中消失。因此,您必須確保在所有頁面訪問中添加所有動(dòng)態(tài)控件,方法是將這段代碼移到 if not page.ispostback 條件語句外面。 
添加動(dòng)態(tài)控件引出的一個(gè)重要問題是此類控件應(yīng)在頁面生命周期的什么時(shí)候添加。正如我在 understanding asp.net view state 中討論的,只要一個(gè)請求到達(dá),asp.net 頁面就要經(jīng)歷許多步驟。讓我們花點(diǎn)時(shí)間概述一下頁面生命周期內(nèi)幾個(gè)緊密相連的階段。為了能夠更深入理解,要確保先閱讀一下關(guān)于視圖狀態(tài)的文章,重點(diǎn)關(guān)注那篇文章中的 the asp.net page lifecycle 部分。 
asp.net 頁面生命周期回顧 
頁面生命周期中的第一個(gè)階段是實(shí)例化,在這個(gè)階段中,自動(dòng)生成的類會(huì)根據(jù)頁面的 html 部分中定義的靜態(tài)控件構(gòu)建控件層次結(jié)構(gòu)。構(gòu)造控件層次結(jié)構(gòu)時(shí),聲明性語法中指定的值會(huì)賦給添加的每個(gè)控件的屬性。實(shí)例化之后是初始化階段,在這個(gè)階段,靜態(tài)控件層次結(jié)構(gòu)已經(jīng)構(gòu)造,但還沒重新加載視圖狀態(tài)(假定頁面請求是回發(fā))。如果頁面請求是回發(fā),則在初始化之后是加載視圖狀態(tài)階段。在這個(gè)階段中,頁面會(huì)過濾出在隱藏的 viewstate 窗體字段中發(fā)現(xiàn)的視圖狀態(tài)數(shù)據(jù),如果需要,控件層次結(jié)構(gòu)中的每個(gè)控件會(huì)更新自己的狀態(tài)。 
如果頁面請求是回發(fā),則在加載視圖狀態(tài)階段之后是加載回發(fā)數(shù)據(jù)階段。這個(gè)階段會(huì)檢查發(fā)送的窗體字段值,并據(jù)此更新相應(yīng)控件的屬性。例如,通過 post 機(jī)制(發(fā)出信號(hào)表示 textbox 控件的名稱和用戶輸入的值),來回送用戶在 textbox web 控件中輸入的文本。頁面獲得這些值,在控件層次結(jié)構(gòu)中定位恰當(dāng)?shù)?nbsp;textbox,并將接收的值賦給它的 text 屬性。 
下一個(gè)階段是加載階段,發(fā)生在 page_load 事件處理程序激發(fā)時(shí)。加載階段之后還有更多階段,如引發(fā)回發(fā)事件、保存視圖狀態(tài)和呈現(xiàn) web 頁,但這些與動(dòng)態(tài)控件的主題無關(guān),因此不加以討論。圖 4 圖示了頁面在生命周期內(nèi)所經(jīng)歷的事件。 
圖 4. 頁面生命周期
確定在頁面生命周期的什么時(shí)候添加動(dòng)態(tài)控件 
關(guān)于在頁面生命周期的什么時(shí)候添加動(dòng)態(tài)控件的問題可以歸納如下:動(dòng)態(tài)控件需要在加載視圖狀態(tài)和重新加載回發(fā)數(shù)據(jù)之前添加,因?yàn)槲覀兿胍_添加特定于動(dòng)態(tài)控件的任何視圖狀態(tài)或回發(fā)值。考慮到這些限制,添加動(dòng)態(tài)控件的正常時(shí)間是在初始化階段,因?yàn)樗l(fā)生在加載視圖狀態(tài)階段和加載回發(fā)數(shù)據(jù)階段之前。 
然而,在初始化階段,視圖狀態(tài)和回發(fā)數(shù)據(jù)都還沒還原,因此不建議訪問或設(shè)置可能存儲(chǔ)在視圖狀態(tài)或被回發(fā)值修改的控件屬性(不管是動(dòng)態(tài)還是靜態(tài)控件),因?yàn)檫@些值將被生命周期后續(xù)階段的視圖狀態(tài)和回發(fā)值所覆蓋。當(dāng)處理動(dòng)態(tài)控件時(shí)我使用了以下模式:  
? 在初始化階段,我向控件層次結(jié)構(gòu)添加動(dòng)態(tài)控件并設(shè)置 id 屬性  
  
? 在加載階段,我在 if not page.ispostback 條件語句中為動(dòng)態(tài)控件賦予任何需要的初始值。  
  
我需要在每次回發(fā)時(shí)添加動(dòng)態(tài)控件,但只在第一次頁面加載時(shí)設(shè)置屬性值,因?yàn)檫@些值會(huì)保留在視圖狀態(tài)中。以下代碼片段說明了這種模式: 
’in the init event of the page, add a dynamic textbox dim tb as new textbox placeholderid.controls.add(tb) tb.id = "dyntextbox" ’in the page_load event handler, set the properties ’of the textbox if not page.ispostback then dim dtb as textbox dtb = ctype(placeholderid.findcontrol("dyntextbox"), textbox) dtb.text = "some initial value" dtb.backcolor = color.red ’initial backcolor end if
除了在初始化階段加載動(dòng)態(tài)控件外,您還可以在加載階段添加,這樣不會(huì)有什么負(fù)面影響。當(dāng)將控件添加到另一個(gè)控件的 controls 集合時(shí),所添加的控件會(huì)立即在其新父控件的生命周期內(nèi)被確立。例如,如果父控件處于初始化階段,則會(huì)引發(fā)所添加控件的 init 事件,使該控件與其父控件保持同步。如果父控件處于加載階段或以后的階段,則所添加的子控件會(huì)立即經(jīng)歷初始化階段、加載視圖狀態(tài)階段、加載回發(fā)數(shù)據(jù)階段和加載階段。 
當(dāng)在加載階段添加控件時(shí),有一個(gè)警告需要注意。當(dāng)一個(gè)控件完成其加載視圖狀態(tài)階段后,它就開始跟蹤對其視圖狀態(tài)的更改。這意味著加載視圖狀態(tài)階段之后 的任何屬性更改都會(huì)自動(dòng)保留在控件的視圖狀態(tài)中。在一個(gè)控件開始跟蹤其視圖狀態(tài)的更改之前,屬性值更改不會(huì)保留在視圖狀態(tài)中。如果您在初始化階段添加控件然后在加載階段設(shè)置其屬性,則不會(huì)有問題,因?yàn)樵诔跏蓟A段和加載階段之間已經(jīng)發(fā)生了加載視圖狀態(tài)階段,控件的跟蹤視圖狀態(tài)更改標(biāo)志已設(shè)置。也就是說,如果在初始化階段添加動(dòng)態(tài)控件,則從運(yùn)行加載階段起,動(dòng)態(tài)控件的屬性賦值會(huì)保留在視圖狀態(tài)中。 
注 頁面開發(fā)人員無法修改“跟蹤視圖狀態(tài)更改標(biāo)志”。system.web.ui.control(所有 asp.net 服務(wù)器控件都由此派生)只提供對該標(biāo)志的受保護(hù)訪問。具體來說,有一個(gè)名為 istrackingviewstate 的受保護(hù)只讀屬性來指示是否在跟蹤視圖狀態(tài),還有一個(gè)受保護(hù)的 trackviewstate() 方法來指示應(yīng)該開始跟蹤視圖狀態(tài)。所有控件在初始化階段結(jié)束時(shí)都會(huì)自動(dòng)調(diào)用這個(gè)方法。 
然而,如果您直到加載階段才添加動(dòng)態(tài)控件,則該動(dòng)態(tài)控件的任何屬性只有在將該控件添加到控件層次結(jié)構(gòu)之后 才能設(shè)置,這一點(diǎn)很重要。為了幫助理解其中原因,請考慮如果在加載階段執(zhí)行以下代碼會(huì)發(fā)生什么: 
dim tb as new textbox if not page.ispostback then tb.backcolor = color.red ’initial backcolor end if placeholderid.controls.add(tb)
正如您所看到的,在每次頁面加載時(shí)都會(huì)創(chuàng)建一個(gè) textbox。只有在第一次頁面加載時(shí)才會(huì)將 textbox 的 backcolor 屬性設(shè)置為 red,而在以后的每次頁面加載都會(huì)將該控件添加到控件層次結(jié)構(gòu)中。雖然在第一次頁面加載時(shí) textbox 的背景顏色確實(shí)為 red,但問題是在回發(fā)時(shí) textbox 的背景顏色會(huì)還原為默認(rèn)值(沒有背景顏色)。這是因?yàn)?nbsp;textbox 的 backcolor 屬性賦值沒有保留到視圖狀態(tài)中,所以在回發(fā)時(shí)丟失。丟失的原因是 textbox 與其他任何服務(wù)器控件一樣,只有在加載視圖狀態(tài)階段之后才開始跟蹤視圖狀態(tài)。但是 textbox 只有在被添加到控件層次結(jié)構(gòu)之后才會(huì)經(jīng)歷這個(gè)階段,所以 backcolor 賦值沒有保留到視圖狀態(tài)中。若要更正這個(gè)問題,請確保將控件添加到控件層次結(jié)構(gòu)中,使其提前經(jīng)歷加載視圖狀態(tài)階段,然后再對其屬性賦值,如下所示:
dim tb as textbox placeholderid.controls.add(tb) if not page.ispostback then tb.backcolor = color.red ’initial backcolor end if
如果您在初始化階段添加動(dòng)態(tài)控件,則與上述細(xì)節(jié)無關(guān)。有關(guān)這個(gè)問題的更深入討論,請參考 my blog 條目 control building and view state lesson for the day。 
事件和動(dòng)態(tài)控件 
與靜態(tài)服務(wù)器控件一樣,動(dòng)態(tài)添加的控件也可以將事件與事件處理程序相關(guān)聯(lián)。正如每次頁面訪問都必須將控件添加到控件層次結(jié)構(gòu)中,每次頁面訪問也都需要將動(dòng)態(tài)控件的事件與指定事件處理程序連接起來。這樣做的部分挑戰(zhàn)是您需要在類中定義適當(dāng)?shù)氖录幚沓绦颉H绻目丶_實(shí)是動(dòng)態(tài)的,則如何知道代碼隱藏類需要什么樣的事件處理程序呢?依我的經(jīng)驗(yàn),我發(fā)現(xiàn)處理事件和動(dòng)態(tài)控件的最好辦法是使用用戶控件,而不要使用單獨(dú)的 web 控件。對于用戶控件,我可以在用戶控件的代碼部分嵌入特定事件處理程序和程序設(shè)計(jì)邏輯。我們將在下一節(jié)介紹如何動(dòng)態(tài)添加用戶控件。 
如果您必須將動(dòng)態(tài)添加的 web 控件的事件與事件處理程序相關(guān)聯(lián),請確保在每次頁面訪問時(shí)都進(jìn)行關(guān)聯(lián)。以下代碼(包含在本文下載中)演示了如何將一個(gè)動(dòng)態(tài)添加的 button web 控件的 click 事件與一個(gè)現(xiàn)有的事件處理程序相關(guān)聯(lián)。(ph 是頁面中 placeholder 控件的名稱。有關(guān)用 c# 將事件與事件處理程序連接起來的示例,以及在 .net framework 中進(jìn)行事件處理的更詳細(xì)信息,請參閱 peter bromberg 的文章 delegates to the event。) 
private sub page_load()sub page_load(byval sender as system.object, byval e as system.eventargs) handles mybase.load dim b as new button ph.controls.add(b) if not page.ispostback then b.text = "click me" end if addhandler b.click, new eventhandler(addressof me.buttonclickeventhandler) end sub private sub buttonclickeventhandler()sub buttonclickeventhandler(byval sender as object, byval e as eventargs) response.write("the button has been clicked!") end sub
構(gòu)建動(dòng)態(tài)數(shù)據(jù)輸入用戶界面引擎 
在過去幾年里,我參與過的大量項(xiàng)目都需要?jiǎng)討B(tài)數(shù)據(jù)輸入用戶界面,即依賴于一個(gè)或多個(gè)受用戶影響因素的用戶界面。所有這些項(xiàng)目的一個(gè)基本要求是需要這些動(dòng)態(tài)界面能夠由非電腦通的用戶輕松地創(chuàng)建、更新和刪除。經(jīng)過這些項(xiàng)目的鍛煉,我開發(fā)了一個(gè)動(dòng)態(tài)數(shù)據(jù)輸入用戶界面引擎,它允許開發(fā)人員創(chuàng)建用戶界面生成塊,然后由非開發(fā)人員將它們拼湊在一起以形成特定于特殊用戶的用戶界面。  
在本文的剩下部分,我將循序漸進(jìn)地介紹這個(gè)引擎的簡化版本。特別是,本文中的演示說明了如何根據(jù)客戶的類型產(chǎn)生針對客戶的獨(dú)特?cái)?shù)據(jù)輸入用戶界面。例如,顯示給普通客戶的用戶界面與在線客戶 (online-only) 或批量購買的客戶不同。 
動(dòng)態(tài)數(shù)據(jù)輸入用戶界面引擎的基本組成是:  
? 用戶界面生成塊:用戶界面生成塊是用戶控件,由小組中的開發(fā)人員負(fù)責(zé)創(chuàng)建。這些生成塊的設(shè)計(jì)只特定于它們收集的信息的類型,而不特定于所請求的數(shù)據(jù)。例如,本演示中包含的一個(gè) ui 生成塊是一個(gè)提示用戶輸入整數(shù)值的生成塊。該用戶控件包含一個(gè) textbox 和一個(gè) comparevalidator,用于確保用戶輸入的是有效的整數(shù)值。只要將生成塊與一個(gè)問題(如“您多大歲數(shù)了?”或“您家到辦公室有多少公里?”)相關(guān)聯(lián),該生成塊就可以組合進(jìn)一個(gè)動(dòng)態(tài)數(shù)據(jù)輸入用戶界面。  
  
? 問題:問題是自定義生成塊,是由非電腦通用戶通過基于 web 的界面創(chuàng)建的。一個(gè)問題將一些文本和一個(gè) ui 生成塊相關(guān)聯(lián)。  
  
? 區(qū)分變量:每個(gè)動(dòng)態(tài)數(shù)據(jù)輸入用戶界面以一個(gè)或多個(gè)變量為基礎(chǔ)。例如,對于在線產(chǎn)品注冊 web 站點(diǎn),用戶界面可能與購買何種產(chǎn)品有關(guān)。對于雇員信息的數(shù)據(jù)輸入,ui 可能因雇員部門而異。對于本文所提出的引擎,區(qū)分變量被硬編碼為客戶類型。  
  
? 動(dòng)態(tài)問題:對于給定的區(qū)分變量,指定了一組問題。問題和區(qū)分變量的組合映射形成了系統(tǒng)的動(dòng)態(tài)問題。  
  
? 動(dòng)態(tài)答案:當(dāng)給定客戶的動(dòng)態(tài)數(shù)據(jù)輸入窗體完成時(shí),該客戶的信息必須保存到數(shù)據(jù)庫中。給定客戶的答案集就是系統(tǒng)中的動(dòng)態(tài)答案。  
  
在 asp.net 應(yīng)用程序中,動(dòng)態(tài)數(shù)據(jù)輸入用戶界面引擎的用戶界面生成塊部分是作為用戶控件實(shí)現(xiàn)的。其余部分則是作為數(shù)據(jù)庫實(shí)體來實(shí)現(xiàn)的。圖 5 顯示了引擎的實(shí)體關(guān)系圖,描述了各個(gè)部分在數(shù)據(jù)庫中如何表示。 
圖 5. 實(shí)體關(guān)系圖
當(dāng)查看圖 5 時(shí),首先注意到 dq_questions 表。表中的記錄表示系統(tǒng)中的問題。每個(gè)問題都有一些與問題相關(guān)的文本 (questiontext) 和一個(gè)用戶控件 (controlsrc)。controlsrc 字段包含了用戶控件的文件名,如 dqintegerinput.ascx。其次,在左下角有一個(gè) dq_customers 表。每個(gè)客戶都有一個(gè)特定的客戶類型,dq_customertypes 表中明確指出了所有這些類型。 
動(dòng)態(tài)問題(問題和客戶類型之間的映射集)通過 dq_dynamicquestions 表來實(shí)現(xiàn)。其中,一個(gè)問題與一種客戶類型和一種排序順序相關(guān)聯(lián),后者指示對于特定客戶類型提出問題的順序。最后,動(dòng)態(tài)答案存儲(chǔ)在 dq_dynamicanswers 表中,它將每個(gè)動(dòng)態(tài)問題與一個(gè)特定客戶相關(guān)聯(lián)。因?yàn)槲覀儾荒艽_定給定問題的答案類型(它可能是字符串、布爾值、整數(shù)等),所以 dq_dynamicanswers 表有六列,每列對應(yīng)系統(tǒng)允許的一種數(shù)據(jù)類型。給定的問題可以只有一種類型,對于其答案,相應(yīng)的字段有答案的值,而其他列則為 null 值。 
注 數(shù)據(jù)模型方面有幾點(diǎn)亟需注意。我決定在 dq_dynamicquestions 表中使用一個(gè)綜合主鍵 (dynamicquestionid) 而不是將 customertypeid 和 questionid 作為組合主鍵使得特定的客戶類型允許有重復(fù)的問題。例如,一個(gè)問題可能是“其他意見”并使用包含多行 textbox 的用戶界面生成塊。因?yàn)槟赡苄枰谄渌S多問題之后有“其他意見”問題,所以我決定允許有重復(fù)的問題。dq_questions 表有著最簡單的格式。在過去的項(xiàng)目中我來回使用以下兩種做法:保留非常簡單的一個(gè)表并將詳細(xì)信息嵌入到用戶界面生成塊中,或者將附加字段添加到與 ui 交互的這個(gè)表中。例如,一個(gè)應(yīng)用程序可能需要能夠指示一些問題是必選的,而其他問題是可選的。在這樣的系統(tǒng)中有兩種方式可以解決這個(gè)問題。第一種是將責(zé)任交給 ui 生成塊。也就是說,不是創(chuàng)建單一的 ui 生成塊(比如整數(shù)輸入),而是創(chuàng)建兩個(gè) — 一個(gè)使用 requiredfieldvalidator 來確保輸入了一個(gè)值,另一個(gè)則不強(qiáng)加此類條件。當(dāng)組織這樣的問題時(shí),管理員可以根據(jù)問題是否為必選來選擇使用哪個(gè) ui 生成塊。一種替代辦法是將 required 字段添加到 dq_questions 表中,并只使用一個(gè) ui 生成塊。當(dāng)采用第二種方法時(shí),每個(gè) ui 生成塊都需要一個(gè) required 屬性,并負(fù)責(zé)根據(jù)這個(gè)屬性值啟用或禁用適當(dāng)?shù)尿?yàn)證控件。 
最后是 dq_dynamicanswers 表,它有六個(gè)與答案相關(guān)的字段,并僅允許來自一個(gè) ui 生成塊的標(biāo)量答案。也就是說,一個(gè) ui 生成塊的答案可以是字符串、整數(shù)、雙精度型、日期、貨幣或布爾值。但如果我們需要 ui 生成塊有更復(fù)雜的答案,如本身可能含有幾個(gè)字段的地址,則怎么辦呢?對于這樣復(fù)雜的答案,當(dāng)返回答案時(shí)需要由 ui 生成塊將其序列化為一個(gè)可接受的類型。當(dāng)顯示答案時(shí),需要對這類結(jié)果進(jìn)行相應(yīng)的反序列化。要實(shí)現(xiàn)這一點(diǎn)您可以依賴 .net 固有的二進(jìn)制序列化能力,但要這樣做您可能需要在這個(gè)表中添加一個(gè)類型為 binary 的 binaryanswer 字段。 
用戶界面生成塊的設(shè)計(jì)規(guī)則 
為了方便生成具有開發(fā)人員設(shè)計(jì)的用戶控件的真正動(dòng)態(tài)數(shù)據(jù)輸入用戶界面,作為 ui 生成塊使用的用戶控件能夠提供一個(gè)基本級(jí)別的功能是很重要的。iuibuildingblock 接口明確指出了這種基本級(jí)別的功能。這個(gè)接口定義了三個(gè)屬性,所有 ui 生成塊都必須實(shí)現(xiàn):  
? datatype:一個(gè)只讀屬性,返回 ui 生成塊所提供的答案的數(shù)據(jù)類型。必須是來自 dqdatatypes 枚舉的一個(gè)值。  
  
? questiontext:要在 ui 生成塊中顯示的問題文本。  
  
? 答案:該 ui 生成塊的答案。  
  
為了闡述如何使用這些屬性,我們來看一個(gè)簡單的 ui 生成塊。假設(shè)我們想要?jiǎng)?chuàng)建一個(gè) ui 生成塊,它提示用戶輸入一個(gè)整數(shù)。我們可以這樣實(shí)現(xiàn):創(chuàng)建一個(gè)新的用戶控件,它在 html 部分包含以下內(nèi)容: 
<asp:label id="dqquestion" runat="server" cssclass="dqquestiontext"></asp:label>: <asp:textbox id="dqanswer" runat="server" cssclass="dqanswer" columns="4"></asp:textbox> <asp:comparevalidator id="comparevalidator1" runat="server" cssclass="dqerrormessage" errormessage="you must enter a number here." controltovalidate="dqanswer" type="integer" operator="datatypecheck"></asp:comparevalidator>
這個(gè)標(biāo)記包括:  
? 一個(gè) label web 控件 (dqquestion),顯示 ui 生成塊的 questiontext 屬性;  
  
? textbox (dganswer),用戶在其中輸入整數(shù)值  
  
? comparevalidator,確保輸入的確實(shí)是一個(gè)整數(shù)。  
  
該用戶控件的源代碼部分相當(dāng)簡單。它讓用戶控件的類實(shí)現(xiàn) iuibuildingblock 接口,并為三個(gè)必需的屬性提供邏輯: 
public class dqintegerquestionclass dqintegerquestion inherits system.web.ui.usercontrol implements iuibuildingblock ... public readonly property datatype()property datatype() as dqdatatypes implements iuibuildingblock.datatype get return dqdatatypes.integer end get end property public property answer()property answer() as object implements iuibuildingblock.answer get if dqanswer.text.trim() = string.empty then return dbnull.value else return dqanswer.text end if end get set(byval value as object) dqanswer.text = value end set end property public property questiontext()property questiontext() as string implements iuibuildingblock.questiontext get return dqquestion.text end get set(byval value as string) dqquestion.text = value end set end property end class
datatype 只讀屬性返回用戶控件所返回的數(shù)據(jù)類型 — 整數(shù)。questiontext 屬性只從 dqquestion label 控件的 text 屬性讀取或向其寫入,而 answer 屬性則從 dganswer textbox 的 text 屬性讀取或向其寫入。所有這些都包含在 ui 生成塊中。對于簡單的 ui 生成塊(就像這一個(gè)),代碼和 html 標(biāo)記很少,但不要讓如此簡單的示例掩飾了 ui 生成塊的真正強(qiáng)大之處。因?yàn)橛脩艨丶梢杂卸鄠€(gè)包含事件處理程序等的 web 控件,所以您可以構(gòu)建豐富的 ui 生成塊。包含在本文代碼下載中的一個(gè) ui 生成塊闡述了如何在一個(gè) ui 生成塊中擁有兩個(gè)依賴 dropdownlist。 
注 當(dāng)創(chuàng)建 ui 生成塊時(shí),確保將它們都放在同一個(gè)目錄中。不過具體放在哪個(gè)目錄并無關(guān)系。在 web.config 文件中,您可以找到一個(gè)鍵名為 buildingblockpath 的 元素。這個(gè)設(shè)置需要提供對用戶控件目錄的引用。在代碼下載中,其默認(rèn)路徑是 ~/usercontrols/,但您可以隨意進(jìn)行更改(如果喜歡的話)。 
有關(guān)使用帶動(dòng)態(tài)加載用戶控件的界面的好處的詳細(xì)信息,請務(wù)必閱讀 tim stall 的文章 understanding interfaces and their usefulness。 
生成問題并將它們與客戶類型相關(guān)聯(lián) 
為了使創(chuàng)建動(dòng)態(tài)數(shù)據(jù)輸入用戶界面成為非開發(fā)人員也能輕松執(zhí)行的任務(wù),我創(chuàng)建了一個(gè)基于 web 的管理界面,可以用它來生成問題并將它們與客戶類型相關(guān)聯(lián)。該界面可以在本文的代碼下載中獲得。 
管理界面中有兩個(gè)緊密相關(guān)的頁面。第一個(gè)是 createquestion.aspx,它允許管理員生成新的問題。回顧一下,一個(gè)問題就是特定的問題文本加上 ui 生成塊。該 web 頁非常簡單,它提供了讓用戶輸入問題文本和從 ui 生成塊目錄(其路徑在 web.config 文件中指定)中選擇一個(gè)用戶控件的方法。圖 6 顯示了該頁面的一個(gè)快照。 
圖 6. 為非開發(fā)人員設(shè)計(jì)的基于 web 的界面
管理界面中的下一屏允許管理員指定將什么問題以及按照什么順序與每個(gè)客戶類型關(guān)聯(lián)起來。該界面(如圖 7 所示)一看就明白。管理員從最頂部的 dropdownlist 選擇一個(gè)客戶類型,然后就可以從第二個(gè) dropdownlistbox 添加問題。datagrid 列出了選定的客戶類型的當(dāng)前問題,它允許用戶從列表中刪除問題或者通過向上和向下箭頭對它們進(jìn)行重排序。 
圖 7. 用于選擇問題順序的 web ui
顯示動(dòng)態(tài)問題和保存結(jié)果 
一旦系統(tǒng)管理員已生成問題并將它們映射到特定客戶類型后,就可以輸入客戶的數(shù)據(jù)。enterdata.aspx 頁面通過查詢字符串獲取客戶的 id,并構(gòu)建對應(yīng)于該客戶的客戶類型的動(dòng)態(tài)數(shù)據(jù)輸入用戶界面。這個(gè)頁面有三個(gè)需要關(guān)注的方法:  
? builddynamicui():這個(gè)方法是在 page_init 事件處理程序(它在頁面生命周期中的初始化階段執(zhí)行)中調(diào)用的,它構(gòu)建對應(yīng)于適當(dāng)客戶類型的動(dòng)態(tài)控件。正如前面討論的,builddynamicui() 只是將必要的控件添加到控件層次結(jié)構(gòu)中。  
  
? page_load:該 page_load 事件處理程序?yàn)閯?dòng)態(tài)添加的 web 控件賦予初始的默認(rèn)值。例如,如果用戶已經(jīng)為特定客戶提供一些值,則當(dāng)訪問頁面時(shí),這些值就會(huì)填充到適當(dāng)?shù)膭?dòng)態(tài)控件中。這些屬性只在第一次頁面訪問時(shí)設(shè)置,以后的回發(fā)將不再設(shè)置。  
  
? btnsavevalues_click:該方法與 save 按鈕的 click 事件相連接。它枚舉動(dòng)態(tài)添加的控件并更新數(shù)據(jù)庫。  
  
讓我們簡要看一下這三個(gè)方法。builddynamicui() 方法是在 page_init 事件處理程序中調(diào)用的。(該事件處理程序由 visual studio .net 在“web form designer generated code”區(qū)域自動(dòng)添加。)該方法通過查詢字符串捕捉客戶 id,然后用對應(yīng)于指定客戶類型的動(dòng)態(tài)問題填充一個(gè) sqldatareader。然后會(huì)循環(huán)訪問此 sqldatareader。對于每條記錄,指定的用戶控件會(huì)加載并添加到 dynamiccontrols placeholder。為每個(gè)動(dòng)態(tài)控件提供一個(gè) dqdynamicquestionid 形式的 id。 
private sub builddynamicui()sub builddynamicui() ’called from page_init customerid = convert.toint32(request.querystring("id")) ... ’get the list of dynamic controls for the specified customer reader = sqlhelper.executereader(connectionstring, _ commandtype.storedprocedure, _ "dq_getdynamicquestionsforcustomertype", _ new sqlparameter("@customertypeid", customertypeid)) ’for each question, add the necessary user control while reader.read dim dq as usercontrol = _ loadcontrol(resolveurl(buildingblockpath & _ reader("controlsrc"))) ctype(dq, iuibuildingblock).questiontext = reader("questiontext") dq.id = string.concat("dq", reader("dynamicquestionid")) dynamiccontrols.controls.add(dq) dynamiccontrols.controls.add(new literalcontrol("")) end while reader.close() end sub
注 在本文所包括的示例代碼中,我使用 microsoft data access application block (daab) 2.0 版來訪問數(shù)據(jù)庫。該 daab 的 sqlhelper 類提供了一個(gè)包裝,可以用于通過一行代碼從 microsoft sql server 數(shù)據(jù)庫訪問數(shù)據(jù)。有關(guān) daab 的更多信息,請務(wù)必訪問 data access application block for .net 官方頁面,以及閱讀 john jakovich 的文章 examining the data access application block。 
另外,如代碼所示,要?jiǎng)討B(tài)加載一個(gè)用戶控件,您需要使用 loadcontrol(usercontrolpath) 方法而不是創(chuàng)建用戶控件類的新實(shí)例。有關(guān)其中原因的詳盡討論以及對用戶控件的深入介紹,請務(wù)必閱讀 an extensive examination of user controls。 
接下來,在 page_load 事件處理程序中,從數(shù)據(jù)庫檢索針對動(dòng)態(tài)控件的客戶當(dāng)前答案并進(jìn)行循環(huán)訪問。引用相應(yīng)的動(dòng)態(tài)控件,并將其 answer 屬性設(shè)置為從數(shù)據(jù)庫獲得的答案。這只在第一次頁面訪問時(shí)執(zhí)行,在以后回發(fā)時(shí)不執(zhí)行,因?yàn)槲覀儾幌敫采w用戶在其中一個(gè)窗體字段中輸入的值。 
’get the answers for this customer ’get the list of dynamic controls for the specified customer dim reader as sqldatareader = _ sqlhelper.executereader(connectionstring, _ commandtype.storedprocedure, _ "dq_getdynamicanswersforcustomer", _ new sqlparameter("@customerid", customerid)) while reader.read dim dq as iuibuildingblock = dynamiccontrols.findcontrol(string.concat("dq", reader("dynamicquestionid"))) if not dq is nothing then select case dq.datatype case dqdatatypes.string dq.answer = reader("stringanswer").tostring() case dqdatatypes.integer dq.answer = convert.toint32(reader("integeranswer")) case dqdatatypes.double dq.answer = convert.tosingle(reader("doubleanswer")) case dqdatatypes.date dq.answer = convert.todatetime(reader("dateanswer")) case dqdatatypes.currency dq.answer = convert.todecimal(reader("currencyanswer")) case dqdatatypes.boolean dq.answer = convert.toboolean(reader("booleananswer")) end select end if end while
最后,當(dāng)用戶單擊 save 按鈕時(shí),會(huì)枚舉 dynamiccontrols placeholder 的 controls 集合,對于已經(jīng)做出回答的每個(gè)動(dòng)態(tài)添加的控件,答案會(huì)寫回到數(shù)據(jù)庫中。 
’create the needed parameters dim stringparam as new sqlparameter("@stringanswer", sqldbtype.ntext) dim integerparam as new sqlparameter("@integeranswer", sqldbtype.int) dim doubleparam as new sqlparameter("@doubleanswer", sqldbtype.decimal) dim dateparam as new sqlparameter("@dateanswer", sqldbtype.datetime) dim currencyparam as new sqlparameter("@currencyanswer", sqldbtype.money) dim booleanparam as new sqlparameter("@booleananswer", sqldbtype.bit) ’enumerate each answer and save it back to the database for each c as control in dynamiccontrols.controls if typeof c is iuibuildingblock then ’mark all of the parameters as null stringparam.value = dbnull.value : integerparam.value = dbnull.value doubleparam.value = dbnull.value : dateparam.value = dbnull.value currencyparam.value = dbnull.value : booleanparam.value = dbnull.value ’determine which parameter needs to be set dim uib as iuibuildingblock = ctype(c, iuibuildingblock) select case uib.datatype case dqdatatypes.string stringparam.value = uib.answer case dqdatatypes.integer integerparam.value = uib.answer case dqdatatypes.double doubleparam.value = uib.answer case dqdatatypes.date dateparam.value = uib.answer case dqdatatypes.currency currencyparam.value = uib.answer case dqdatatypes.boolean booleanparam.value = uib.answer end select dim dynamicquestionid as integer = convert.toint32(c.id.substring(2)) sqlhelper.executereader(connectionstring, _ commandtype.storedprocedure, "dq_adddynamicanswer", _ new sqlparameter("@customerid", customerid), _ new sqlparameter("@dynamicquestionid", dynamicquestionid), _ stringparam, integerparam, doubleparam, _ dateparam, currencyparam, booleanparam) end if next
結(jié)束語 
該動(dòng)態(tài)數(shù)據(jù)輸入用戶界面引擎是為您的 web 應(yīng)用程序開發(fā)此類系統(tǒng)的一個(gè)好起點(diǎn),但它不是為現(xiàn)有系統(tǒng)提供無縫集成而設(shè)計(jì)的。它的設(shè)計(jì)只是作為演示系統(tǒng),而不是作為完整的工作系統(tǒng)。該系統(tǒng)的一個(gè)部分還不完善,那就是管理界面,雖然也實(shí)現(xiàn)了一定功能,但與完整的系統(tǒng)相比還差得很遠(yuǎn)。具體而言,在如何處理從特定客戶類型刪除動(dòng)態(tài)問題方面還存在問題。例如,假設(shè)管理員對系統(tǒng)進(jìn)行配置,使得可以對在線客戶提出一個(gè)是非問題,“這是您在本公司購買的第一件產(chǎn)品嗎?” 
現(xiàn)在,假設(shè)有大量客戶回答了這個(gè)問題。如果管理員決定從在線用戶的問題集中刪除這個(gè)問題,則將會(huì)發(fā)生什么呢?應(yīng)該將相應(yīng)的答案從 dq_dynamicanswers 表中刪除嗎?應(yīng)該保存它們以便提供對以前答案的歷史視圖嗎?對于您的應(yīng)用程序,您需要對這個(gè)問題做出回答。現(xiàn)在,當(dāng)您刪除一種客戶類型的一個(gè)動(dòng)態(tài)問題時(shí),管理界面并沒有做什么事情,這意味著如果有一個(gè)或多個(gè)客戶回答了這個(gè)問題,就會(huì)產(chǎn)生異常而且不會(huì)刪除問題,因?yàn)檫@樣做與在數(shù)據(jù)庫中建立的引用完整性相沖突。 
小結(jié) 
在本文中,我們介紹了如何利用 asp.net 中的動(dòng)態(tài)控件創(chuàng)建動(dòng)態(tài)數(shù)據(jù)輸入用戶界面。正如本文前半部分所討論的,asp.net 頁面由一個(gè)控件層次結(jié)構(gòu)組成,后者通常嚴(yán)格地由靜態(tài)定義的控件組成。然而,我們可以在運(yùn)行時(shí)操作該控件層次結(jié)構(gòu),方法是在該層次結(jié)構(gòu)中的現(xiàn)有控件的 controls 集合中添加動(dòng)態(tài)控件。我們還了解了訪問動(dòng)態(tài)添加的控件的技術(shù)以及添加這些控件并與之進(jìn)行交互的通用模式。 
本文的后半部分介紹創(chuàng)建和使用動(dòng)態(tài)數(shù)據(jù)輸入用戶界面的特定實(shí)現(xiàn)。所介紹的引擎允許非技術(shù)人員的用戶根據(jù)用戶界面生成塊輕松地生成問題,該用戶界面生成塊是由開發(fā)人員創(chuàng)建的 asp.net 用戶控件。針對這些問題,那些非技術(shù)人員的管理用戶可以將一組問題與特定的客戶類型相關(guān)聯(lián)。一個(gè)單一的 web 頁,enterdata.aspx 根據(jù)訪問頁面的客戶顯示和保存適當(dāng)?shù)臄?shù)據(jù)輸入窗體字段和值。 
該工具是一個(gè)強(qiáng)大實(shí)用的工具,它可以在運(yùn)行時(shí)操作 asp.net 頁面的控件層次結(jié)構(gòu),使得應(yīng)用程序可以適應(yīng)許多常見場景。通過閱讀本文,您應(yīng)該能夠自信地在您的 asp.net 頁面中使用動(dòng)態(tài)控件。 
盡情享受編程的樂趣吧! 
特別感謝…… 
在將本文提交給我的 msdn 編輯之前,有許多志愿者幫助我校對本文并為本文的內(nèi)容、語法和目的提供反饋。本文審閱過程中的主要貢獻(xiàn)者包括 milan negovan、marko rangel、hilton giesenow、carlos santos、dave donaldson 和 carl lambrecht。如果您有興趣加入到不斷壯大的審閱者隊(duì)伍中,請通過 [email protected] 給我發(fā)郵件。  
scott mitchell 著有六本書,是 4guysfromrolla.com 的創(chuàng)始人,也是一個(gè)各方面都很優(yōu)秀的人才。他自 1998 年起就開始從事 microsoft web 技術(shù)。scott 是一位獨(dú)立顧問、培訓(xùn)師和作家。您可以通過 mailto:[email protected]或他的網(wǎng)絡(luò)日記 http://scottonwriting.net/ 來與他取得聯(lián)系。