導言
在前面三節的樣本中,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 – 計算輸入控制項的值,以確定該值是否與某個Regex 所定義的模式相匹配
· 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》(英文) 。 他的聯絡電郵為mitchell@4guysfromrolla.com,也可以通過他的部落格http://scottonwriting.net/與他聯絡。