導言
在前面三節的示例中,GridView和DetailsView控件使用的是綁定列和CheckBoxField(綁定GridView和DetailsView時,通過智能標記可以令VS根據數據庫自動增加對應的類型)。當編輯GridView或者DetailsView中的一行時,非只讀屬性的綁定列將自動轉為textbox,以便用戶修改現有的數據。同樣地,當在DetailsView控件中新增記錄時,InsertVisible屬性為true(默認值)的綁定列會呈現出空的textbox,以接受用戶輸入。CheckBoxField列也是如此,通常作為只讀的checkbox顯示,新增/編輯記錄時則可以接受選擇。
盡管BoundField和CheckBoxField提供的編輯和添加界面相當有用,卻缺乏驗證功能。當用戶產生一些數據錄入錯誤――比如遺漏了 ProductName字段或者為UnitsInStock輸入一個無效值(如-50)――那麼應用程序將從底層拋出一個異常。盡管我們可以很好的處理這個異常像上節教程previous tutorial中討論的,但是,一個完美的‘新增/編輯'用戶界面應該包括驗證控件,在第一時間阻止用戶輸入這些無效數據。
為了提供一個自定義的新增/編輯界面,需要將BoundField和CheckBoxField換成模板列(ItemplateField)。關於模板列,已經在《Using TemplateFields in the GridView Control 和 Using TemplateFields in the DetailsView Control》教程裡討論過了,由幾個處理不同行狀態的模板組成。模板列的項模板(ItemTemplate),用來呈現DetailsView或GridView控件中的只讀字段或行,而EditItemplate和InsertItemTemplate則分別是編輯和新增模式的界面模板。
在本節教程中,你會發現為模板列的EditItemTemplate和InsertItemTemplate提供驗證控件來提供更健壯的用戶界面是多麼的簡單。明確一點,本節教程采用《Examining the Events Associated with Inserting, Updating, and Deleting 》中創建的示例代碼,來增加新增/編輯時的相關驗證。
一、復制《研究插入、更新和刪除的關聯事件》的示例代碼
在《研究插入、更新和刪除的關聯事件》教程中我們創建了一個頁面,並在一個可編輯的GridView中列表顯示產品的名字和價格。頁面還有一個DetailsView,DefaultMode 屬性設置成Insert,因此始終呈現為新增模式。通過DetailsView,用戶可以錄入名字和價格增加新的產品,點擊Insert後,新產品就被增加到系統裡(見圖1)。
圖1:以前的代碼允許用戶增加新的產品或修改已有的產品
本節教程的目標是為DetailsView和GridView提供驗證控件。更精確一些,此驗證邏輯將是:
· 新增/編輯產品時name為必填項
· 新增記錄時price為必填項;編輯時依然需要價格,並且在GridView的RowUpdating事件處理中應用上節教程previous tutorial中的程序邏輯
· 確保輸入的price是有效的貨幣格式
在考慮為前面代碼增加驗證之前,我們首先需要復制上節教程previous tutorial 示例DataModificationEvents.aspx中的代碼到本節教程的UIValidation.aspx頁面上。要完成此點需要復制DataModificationEvents.aspx頁面的元素標記和它的後台代碼。先按下面步驟拷貝元素標記:
1.在Visual Studio中打開DataModificationEvents.aspx
2.轉到頁面的源視圖(單擊頁面底部的源(Source)按鈕)
3.拷貝<asp:Content> 至 </asp:Content> 標記間的文本(3到44行),見圖2。
圖2:拷貝<asp:Content> 控件中的文本
4.打開UIValidation.aspx頁
5.轉到頁面的源視圖
6.粘貼文本到<asp:Content>控件
然後打開代碼文件DataModificationEvents.aspx.cs,拷貝EditInsertDelete_DataModificationEvents 類中的代碼,及3個事件處理(Page_Load, GridView1_RowUpdating, 和 ObjectDataSource1_Inserting),注意不要把類聲明和using語句也拷貝過來,然後將它們粘貼到UIValidation.aspx.cs中的 EditInsertDelete_UIValidation裡。
上面的工作完成後,不要急著動手,先砌杯茶在浏覽器裡查看一下是否有誤,這兩個頁面應該具有同樣的輸出和功能。(參照圖1 ,DataModificationEvents.aspx運行時的抓圖)。
二、將綁定列轉換為模板列
要增加驗證控件到新增/編輯界面,DetailsView 和 GridView必須將綁定列轉換為模板列。要實現此轉換,先點擊GridView的智能標記(譯者:GridView右上角的箭頭),再選擇‘編輯列 …'(Edit Columns),在左邊依次選擇綁定字段並點擊‘將此字段轉換為TemplateField'鏈接(英文版是Convert this field into a TemplateField,下同)。
圖3:將DetailsView和GridView的綁定列轉換為模板列
通過剛才操作的字段(英文版是Fields)對話框,綁定列可以轉換為模板列,同樣擁有了只讀,編輯,新增等原有功能。下面的代碼顯示了 DetailsView中轉換為模板列後的ProductName字段的元素標記:
<asp:TemplateField HeaderText="ProductName" SortExpression="ProductName"> <EditItemTemplate> <asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("ProductName") >'></asp:TextBox> </EditItemTemplate> <InsertItemTemplate> <asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("ProductName") %>'></asp:TextBox> </InsertItemTemplate> <ItemTemplate> <asp:Label ID="Label1" runat="server" Text='<%# Bind("ProductName") %>'></asp:Label> </ItemTemplate> </asp:TemplateField>
注意該模板列自動創建了三個模板列,ItemTemplate, EditItemTemplate以及InsertItemTemplate。項模板ItemTemplate使用Label Web控件簡單顯示字段值(ProductName),而EditItemTemplate和InsertItemTemplate則使用TextBox控件並利用其Text屬性來處理相關的數據。由於我們在頁面上只使用DetailsView實現新增,你可以刪除ItemTemplate和EditItemTemplate,當然留著也無關緊要。
由於GridView不支持DetailsView內建的新增功能,將GridView的ProductName字段轉換為模板列,並只保留ItemTemplate和 EditItemTemplate:
<asp:TemplateField HeaderText="ProductName" SortExpression="ProductName"> <EditItemTemplate> <asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("ProductName") %>'></asp:TextBox> </EditItemTemplate> <ItemTemplate> <asp:Label ID="Label1" runat="server" Text='<%# Bind("ProductName") %>'></asp:Label> </ItemTemplate> </asp:TemplateField>
通過點擊‘將此字段轉換為TemplateField'鏈接,Visual Studio創建了一個模板列模擬綁定列的界面,這一點可以通過在浏覽器裡查看頁面來證實,替換前後外觀和行為應該是完全一致的。
注意:可以根據需要在模板裡隨意定制編輯界面。例如,也許我們對UnitPrice字段使用一個小一點的TextBox。要實現這一點可以通過設置 TextBox的Columns屬性或者通過Width屬性指定一個固定寬度。下節教程會討論如何用其它的數據輸入Web控件替換TextBox來定制編輯界面。
三、為GridView的項編輯模板(EditItemTemplate)增加驗證控件
創建數據錄入表單時,限制用戶錄入必填的,合法的以及格式化的數據十分重要。為確保用戶錄入數據都是有效的,ASP.NET提供了5種內建的驗證控件來驗證單一控件的值:
· RequiredFieldValidator – 計算輸入控件的值以確保用戶輸入值
· CompareValidator – 將輸入控件的值同常數值或其他輸入控件的值相比較,以確定這兩個值是否與由比較運算符(小於、等於、大於、類型等等)指定的關系相匹配
· RangeValidator – 計算輸入控件的值,以確定該值是否在指定的上限與下限之間
· RegularExpressionValidator – 計算輸入控件的值,以確定該值是否與某個正則表達式 所定義的模式相匹配
· CustomValidator – 計算輸入控件的值以確定它是否通過自定義的驗證邏輯
關於這五種控件的更多信息,請參閱 《ASP.NET Quickstart Tutorials》中的Validation Controls section。
本節教程中,對於DetailsView和GridView中的ProductName模板列我們需要使用RequiredFieldValidator,而DetailsView的UnitPrice模板列也需要一個RequiredFieldValidator。此外,還需要給所有的UnitPrice模板列增加一個CompareValidator,以確保輸入的價格大於等於0並且是有效的貨幣格式。
注意:ASP.NET 1.x中已經包含了這幾個驗證控件,但是ASP.NET 2.0中增加了一些改進,主要的兩點是客戶端腳本對非IE浏覽器的支持和對頁面上的部分驗證控件進行分組實現某個按鈕的特定驗證控件組,參閱《Dissecting the Validation Controls in ASP.NET 2.0》(譯者:也可參閱MSDN http://msdn.microsoft.com/asp.net/default.aspx?pull=/library/en-us/dnvs05/html/ValGroups.asp)。
現在我們來給GridView模板列中的EditItemTemplate增加這些要用到的驗證控件。首先點擊GridView的智能標記選擇編輯模板打開模板編輯界面,然後從下拉列表中選擇你要編輯的模板。由於我們要處理的是編輯界面,這裡我們要給ProductName和UnitPrice的EditItemTemplate模板增加驗證控件。
圖4:展開ProductName和UnitPrice的 EditItemTemplate模板
在ProductName的EditItemTemplate中,通過拖拉方式從工具箱裡給編輯界面增加一個RequiredFieldValidator,放在TextBox後面。
圖5:為ProductName的EditItemTemplate增加一個RequiredFieldValidator
所有的驗證控件都只能為單個ASP.NET Web控件服務,因此,需要讓新增的這個驗證控件為EditItemTemplate的TextBox控件進行驗證;這需要將要驗證控件的ID設置給驗證控件的 ControlToValidate property屬性。TextBox當前的ID可能是一個莫明的TextBox1,我們最好還是賦予它一個更合適的ID,單擊模板中的TextBox,按F4查看屬性窗口,將ID由TextBox1改為EditProductName。
圖6:將TextBox的ID改名為 EditProductName
接下來,設置RequiredFieldValidator的ControlToValidate屬性為EditProductName。最後,設置ErrorMessage屬性為“You must provide the product's name” 並將Text屬性設置為“*”。如果設置了Text屬性,那麼當驗證失敗的時候文本值就會被顯示出來。ErrorMessage屬性也是必須的,它是為ValidationSummary准備的;當Text屬性值被省略時,ErrorMessage屬性也會在無效輸入時作為文本顯示出來。
設置完RequiredFieldValidator的這些屬性後,屏幕應該如圖7所示:
圖7:設置RequiredFieldValidator控件的 ControlToValidate, ErrorMessage和Text 屬性
為ProductName的EditItemTemplate增加完RequiredFieldValidator,余下的就是為UnitPrice的EditItemplate模板增加一些必要的驗證控件。由於我們決定UnitPrice編輯時作為選填,所以並不需要RequiredFieldValidator。不過需要增加一個CompareValidator來確保UnitPrice 有效,必須大於等於0並且時貨幣格式。
在為UnitPrice 的EditItemTemplate模板增加CompareValidator之前,先將TextBox的ID改為EditUnitPrice。然後添加CompareValidator控件並設置 ControlToValidate屬性為EditUnitPrice,ErrorMessage屬性為“The price must be greater than or equal to zero and cannot include the currency symbol”,Text 屬性為 “*”。
為了確保UnitPrice值必須大於等於0,設置CompareValidator的Operator屬性為GreaterThanEqual,ValueToCompare屬性為 “0”, 並且Type屬性為Currency。下面的代碼顯示了UnitPrice 模板列中的 EditItemTemplate調整後的樣子:
<EditItemTemplate> <asp:TextBox ID="EditUnitPrice" runat="server" Text='<%# Bind("UnitPrice", "{0:c}") %>' Columns="6"></asp:TextBox> <asp:CompareValidator ID="CompareValidator1" runat="server" ControlToValidate="EditUnitPrice"ErrorMessage="The price must be greater than or equal to zero and cannot include the currency symbol"Operator="GreaterThanEqual" Type="Currency" ValueToCompare="0">*</asp:CompareValidator> </EditItemTemplate>
這些調整之後,在浏覽器裡查看這個頁面。如果對product編輯時你嘗試省略name或者輸入一個無效的price,星號就會顯示在文本框後面。如圖8顯示,包含了貨幣符合的price,如$19.95,將被視作無效。CompareValidator控件的Currency類型允許數字分割符(像逗號,小數點,取決於culture設置),以正負號開頭,但是不允許貨幣符號。而編輯界面UnitPrice卻呈現為貨幣形式,這種行為可能令用戶很困惑。
注意:回想一下《研究插入、更新和刪除的關聯事件》教程,我們設置了綁定列的DataFormatString屬性為{0:c},使其格式化為貨幣。由於我們將ApplyFormatInEditMode屬性置為true,導致 GridView編輯界面將UnitPrice格式化為貨幣格式。當綁定列轉換為模板列會保留這些設置並且對TextBox的Text屬性使用綁定語法<%# Bind("UnitPrice", "{0:c}") %>進行格式化。
圖8:無效輸入時文本框後面顯示的星號
基於如此的驗證方式,在編輯記錄時用戶必須手動刪除貨幣符號,很難讓人接受。下面有三種選擇進行補救:
1. 配置EditItemTemplate使 UnitPrice不會被格式化為貨幣。
2. 移除CompareValidator並替換為RegularExpressionValidator,允許用戶輸入貨幣符號,但是要編寫代碼來適應不同的文化設置。
3. 移除驗證控件並在GridView的RowUpdating事件處理中進行服務器端驗證邏輯。
我們這裡采用第一種方式。UnitPrice通過EditItemTemplate中的綁定表達式<%# Bind("UnitPrice", "{0:c}") %>轉換為貨幣格式。將其改為Bind("UnitPrice", "{0:n2}")格式化為兩位小數的數字。這些操作可以直接在元素標記裡完成,也可以通過點擊EditUnitPrice文本框的‘編輯 DataBindings…'鏈接(見圖9、圖10)
圖9:點擊TextBox的‘編輯 DataBindings'鏈接
圖10:為綁定表達式指定特定格式
這些改變之後,編輯界面的price被格式化為含義逗號和小數點的格式,卻沒有了貨幣符號。
注意: UnitPrice的 EditItemTemplate 不包含 RequiredFieldValidator, 運行回傳並繼續更新邏輯。然而,《研究插入、更新和刪除的關聯事件》教程中拷過來的RowUpdating 事件處理包含了對提供的UnitPrice的檢查代碼。刪除邏輯,保持原樣,或者給UnitPrice的EditItemTemplate增加RequiredFieldValidator 悉隨尊便。
四:概述頁上的數據錄入問題
除了這5個驗證控件之外,ASP.NET包含了一個總結控件ValidationSummary control,可以顯示那些檢測到無效數據的驗證控件的ErrorMessage。以文本方式在頁上某個位置概述錯誤結果,或者通過一個客戶端消息框。下面我們為程序增加一個客戶端消息框概述頁上全部的驗證問題。
從工具箱拖一個ValidationSummary控件到設計窗口上,它的位置沒什麼要求,因為我們打算把它以消息框的形式顯示。在增加控件完之後,設置其ShowSummary屬性為false並設置ShowMessageBox屬性為true。這樣以來,所有的驗證錯誤都會顯示在一個客戶端消息框中。
圖11:客戶端消息框中的驗證錯誤總結 (點擊放大)
五、為DetailsView的InsertItemTemplate增加驗證控件
本教程中余下的部分就是為DetailsView的新增界面增加驗證控件。這一工作與第三小節一樣,這裡不再贅言。像GridView的EditItemTemplates 操作中提到的,推薦重命名TextBox的ID,這裡分別使用InsertProductName 和 InsertUnitPrice而不是TextBox1 和 TextBox2。
為ProductName的 InsertItemTemplate增加RequiredFieldValidator驗證控件,並設置其ControlToValidate 為模板中TextBox的ID, 設置Text 屬性為 “*” ,ErrorMessage 屬性為 “You must provide the product's name”。
由於頁面上的UnitPrice對於新增記錄是必填項,所以我們在UnitPrice的 InsertItemTemplate中為其增加RequiredFieldValidator,設置ControlToValidate, Text和ErrorMessage 等相關屬性。最後,在UnitPrice 的InsertItemTemplate增加適當的CompareValidator,參照前面UnitPrice增加 CompareValidator的情形配置其ControlToValidate, Text, ErrorMessage, Type, Operator和 ValueToCompare等相關屬性。
通過增加的這些驗證控件,新的product如果不提供name或者price為負數或者非法格式都會被系統拒絕添加。
圖12:DetailsView新增界面中添加的驗證邏輯 (點擊放大)
六、對驗證控件進行分組
頁面上有兩套邏輯上獨立的驗證控件集合: GridView的編輯界面和DetailsView新增界面上相應的兩組。默認情況下,當postback發生時頁面上所有的驗證都會生效。顯然,當編輯記錄時我們不希望DetailsView新增功能的驗證起作用,圖13說明了這種尴尬局面-當用戶在編輯product時輸入了有些有效數據,在點擊更新時卻由於新增功能中的name和price空白而產生驗證錯誤。
圖13:更新Product引發新增功能的驗證控件 (點擊放大)
ASP.NET 2.0中的驗證控件可以進行分組,這一功能是通過ValidationGroup屬性。為了將這些驗證控件關聯到一個組,只需把ValidationGroup屬性指定成同一個值。本教程中,將GridView模板中的ValidationGroup屬性統一設置為EditValidationControls,而DetailsView模板中的 ValidationGroup屬性則為InsertValidationControls。上述操作可以直接在代碼編輯窗口完成或者通過設計器模板編輯界面的屬性窗口修改。
ASP.NET 2.0中除了驗證控件,按鈕和按鈕相關控件也增加了ValidationGroup屬性。驗證組中的驗證控件只在有相同ValidationGroup屬性的按鈕產生 postback時才會進行有效性檢測,例如,為使DetailsView的新增按鈕可以觸發InsertValidationControls驗證組,我們給CommandField的 ValidationGroup屬性指定為InsertValidationControls(圖14),而GridView中CommandField的ValidationGroup屬性則指定為 EditValidationControls。
圖14:設置DetailsView中CommandField的 ValidationGroup屬性為InsertValidationControls
上述操作後,DetailsView和GridView的模板TemplateFields 和 CommandFields大致如下:DetailsView中的TemplateField模板和CommandField模板:
<asp:TemplateField HeaderText="ProductName" SortExpression="ProductName"> <InsertItemTemplate> <asp:TextBox ID="InsertProductName" runat="server" Text='<%# Bind("ProductName") %>'></asp:TextBox> <asp:RequiredFieldValidator ID="RequiredFieldValidator2" runat="server" ControlToValidate="InsertProductName" ErrorMessage="You must provide the product name" ValidationGroup="InsertValidationControls">* </asp:RequiredFieldValidator> </InsertItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="UnitPrice" SortExpression="UnitPrice"> <InsertItemTemplate> <asp:TextBox ID="InsertUnitPrice" runat="server" Text='<%# Bind("UnitPrice") %>' Columns="6"> </asp:TextBox> <asp:RequiredFieldValidator ID="RequiredFieldValidator3" runat="server" ControlToValidate="InsertUnitPrice" ErrorMessage="You must provide the product price" ValidationGroup="InsertValidationControls">* </asp:RequiredFieldValidator> <asp:CompareValidator ID="CompareValidator2" runat="server" ControlToValidate="InsertUnitPrice" ErrorMessage="The price must be greater than or equal to zero and cannot include the currency symbol" Operator="GreaterThanEqual" Type="Currency" ValueToCompare="0" ValidationGroup="InsertValidationControls">* </asp:CompareValidator> </InsertItemTemplate> </asp:TemplateField> <asp:CommandField ShowInsertButton="True" ValidationGroup="InsertValidationControls" /> GridView中的CommandField模板和TemplateFields模板: <asp:CommandField ShowEditButton="True" ValidationGroup="EditValidationControls" /> <asp:TemplateField HeaderText="ProductName" SortExpression="ProductName"> <EditItemTemplate> <asp:TextBox ID="EditProductName" runat="server" Text='<%# Bind("ProductName") %>'> </asp:TextBox> <asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" ControlToValidate="EditProductName" ErrorMessage="You must provide the product name" ValidationGroup="EditValidationControls">* </asp:RequiredFieldValidator> </EditItemTemplate> <ItemTemplate> <asp:Label ID="Label1" runat="server" Text='<%# Bind("ProductName") %>'></asp:Label> </ItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="UnitPrice" SortExpression="UnitPrice"> <EditItemTemplate> <asp:TextBox ID="EditUnitPrice" runat="server" Text='<%# Bind("UnitPrice", "{0:n2}") %>' Columns="6"></asp:TextBox> <asp:CompareValidator ID="CompareValidator1" runat="server" ControlToValidate="EditUnitPrice" ErrorMessage="The price must be greater than or equal to zero and cannot include the currency symbol" Operator="GreaterThanEqual" Type="Currency" ValueToCompare="0" ValidationGroup="EditValidationControls">* </asp:CompareValidator> </EditItemTemplate> <ItemTemplate> <asp:Label ID="Label2" runat="server" Text='<%# Bind("UnitPrice", "{0:c}") %>'> </asp:Label> </ItemTemplate> </asp:TemplateField>
當GridView的更新按鈕點擊時,編輯中特定的驗證控件將會開始檢測,而當DetailsView中的新增按鈕被點擊時,新增功能的相關驗證生效,圖13的高亮部分顯示了此舉解決的問題。但是這些改動之後,輸入無效數據時ValidationSummary驗證總結卻不再顯示了。這是由於 ValidationSummary控件也擁有ValidationGroup屬性並且只顯示來自於同一驗證組中驗證控件的信息。因此,我們需要使用兩個驗證控件,分別作為InsertValidationControls驗證組和EditValidationControls驗證組:
<asp:ValidationSummary ID="ValidationSummary1" runat="server" ShowMessageBox="True" ShowSummary="False" ValidationGroup="EditValidationControls" /> <asp:ValidationSummary ID="ValidationSummary2" runat="server" ShowMessageBox="True" ShowSummary="False" ValidationGroup="InsertValidationControls" />
寫到這裡,本節教程就可以畫上句號了。
小結
雖然綁定列BoundField可以提供了新增/編輯界面,卻不能對其進行定制。很多情況,我們要給新增/編輯增加驗證功能以確保用戶輸入合法有效的數據。為此,我們將BoundFields轉換成了TemplateField,並在相應模板中增加了驗證控件。本節教程擴展了《Examining the Events Associated with Inserting, Updating, and Deleting》中的代碼,為DetailsView的新增和GridView的編輯界面增加了驗證功能。此外,還演示了如何使用ValidationSummary控件顯示驗證總結以及如何對驗證控件進行分組。
正如本文所見,模板列允許為新增/編輯界面增加驗證控件,當然也可以擴展其他的Web控件。在下節教程中,將會演示如何用可數據綁定的DropDownList控件替換原有的TextBox,僅僅通過一個外鍵(如Products表中的CategoryID或SupplierID)。
祝編程快樂!
作者簡介
Scott Mitchell,著有六本ASP/ASP.NET方面的書,是4GuysFromRolla.com的創始人,自1998年以來一直應用微軟Web技術。Scott是個獨立的技術咨詢顧問,培訓師,作家,最近完成了將由Sams出版社出版的新作, 《24小時內精通ASP.NET 2.0》(英文) 。 他的聯系電郵為[email protected],也可以通過他的博客http://scottonwriting.net/與他聯系。