ASP.NET MVC 第五個預覽版和表單提交場景

iDotNetSpace發表於2009-11-30

上個星期四,ASP.NET MVC開發團隊釋出了ASP.NET MVC框架的“第五個預覽版”。你可以在這裡下載這個新版本。這“第五個預覽版” 可在.NET 3.5和最新發布的.NET 3.5 SP1下工作,也可在Visual Studio 2008以及免費的Visual Web Developer 2008 Express SP1版本(現在支援類庫專案和web應用專案了)下使用。

第五個預覽版包含了建立在“第四個預覽版”的新功能之上的一堆新功能和改進。你可以在這裡閱讀“第五個預覽版”的釋出說明,其中總結了所做變動和新添特性。在這個部落格貼子中,我將討論這個版本中一個最大的關注點:表單提交場景。你可以在這裡下載我將在下面建造的一個應用的完整版本。

Web MVC模式中的基本表單提交

讓我們看一個簡單的表單提交場景,往產品資料庫中加一個新產品:

 

上面的頁面是在使用者訪問我們應用的“/Products/Create”URL時返回的,該網頁的的HTML表單標識如下:

上面的標識是標準的HTML,在

元素中,我們有2個文字框,然後在表單的下部有一個HTML提交按鈕,點選該按鈕,會導致包含該按鈕的表單將表單輸入提交到伺服器,該表單會向由它的“action”屬性(在這裡是“/Products/Save”)表示的URL提交內容。

使用先前的“第四個預覽版”,我們也許會使用象下面這樣的ProductsController類來實現上面的場景,在其中實現2個action方法,“Create”和“Save”:

上面的“Create” action方法負責返回顯示了初始空白表單的HTML檢視,而“Save” action方法則負責處理表單提交回伺服器後的場景。ASP.NET MVC框架會自動地將“ProductName”和“UnitPrice”表單提交值對映到Save方法的同名引數上。 Save action方法然後使用LINQ to SQL建立一個新的Product物件,將使用者提交的值賦予它的ProductName和UnitPrice屬性, 然後試圖將新產品儲存到資料庫中。如果產品儲存成功的話,使用者就會被重新定向到一個“/ProductsAdded”URL,該URL會顯示一個成功訊息。如果有錯的話,我們會重新顯示“Create” HTML檢視,這樣使用者可以修正問題,再試著提交。

然後我們可以實現一個象下面這樣的“Create” HTML 檢視模板,該模板與上面的ProductsController協作產生適當的HTML。注意下面,我們使用了Html.TextBox輔助方法來生成元素,同時該方法自動地使用我們傳遞給檢視的Product模型產品的適當屬性值來填充這些文字框的值:

第五個預覽版中的表單提交方面的改進

上面的程式碼在先前的“第四個預覽版”下工作,在“第五個預覽版”中仍將繼續工作。但“第五個預覽版”新增了額外的特性,允許我們將表單場景做得更完善。

這些新的特性包括:

  • 釋出單個action URL,但根據HTTP動詞做不同的分派的能力
  • 模型繫結器(Model Binders),將允許從表單輸入值構建出豐富的引數物件,然後傳遞給action方法
  • 新的輔助方法,允許將提交進來的表單輸入值對映到action方法中的現有模型物件例項上
  • 對處理輸入和驗證錯誤的支援的改進(例如,在表單重新顯示給使用者時,自動高亮顯示有問題的域(field),保留使用者輸入的表單值)

我將用本貼子剩下的篇幅對這些特性進行仔細討論。

[AcceptVerbs] 和 [ActionName] 特性

在上面的例子中,我們用2個action方法,“Create” 和 “Save”,實現了新增產品的場景。象這樣將實現進行分割的一個動機在於,它使得我們的Controller程式碼既乾淨又易讀。

但在這個場景中使用2個action方法的壞處在於,我們最終在網站上釋出了2個URL,"/Products/Create" 和 "/Products/Save"。這在因為輸入錯誤,需要重新顯示HTML表單的場景時,會變得有點問題,因為在出錯的場景下,重新顯示的表單的URL會變成“/Products/Save”,而不是“/Products/Create”(因為“Save”是表單提交的目標URL)。如果使用者將這重新顯示的頁面收藏的話,或者將URL拷貝/貼上 email給朋友,他們儲存的是錯的URL,而且在之後再來訪問時,非常可能出錯。釋出2個URL也會給某些搜尋引擎造成問題,如果它們爬你的網站,試圖從你的action屬性自動連到下一個網頁的話。

繞過這些問題的一個方法是,釋出一個單一的“/Products/Create” URL,然後取決於請求是GET還是POST而使用不同的伺服器邏輯。一個在其他web MVC框架中過去常用的方法是,在action方法中有個巨大的if/else語句,根據不同動詞,執行不同分支:

但上面這個方法的壞處是,它使得action方法的實現難讀,而且難以測試。

ASP.NET MVC第五個預覽版現在提供了一個更好的選項來處理這個場景。你可以建立action方法的過載實現,使用一個新的[AcceptVerbs]特性,讓 ASP.NET MVC過濾這些方法是如何分派的。例如,在下面,我們可以宣告2個 Create action方法,一個會在GET場景下呼叫,另一個會在POST場景下呼叫:

這個方法避免了在action方法中使用巨大的“if/else”語句的需要,使得你的action邏輯結構更乾淨,還摒除了為測試這2個不同場景需要模擬Request物件的必要性。

你現在還可以使用一個新的[ActionName] 特性,以允許你的controller類的實現方法的名稱與釋出的URL有所不同。例如,在你的controller類中不是擁有2個過載的Create方法,你可以把在POST情形下呼叫的方法命名為“Save”,然後象這樣給這個方法加一個[ActionName]特性:

在上面,我們的方法(Create和Save)的簽名跟一開始的表單提交例子中的方法一模一樣,但其區別在於,我們現在只發布了一個單一的URL,/Products/Create,且會基於進來的HTTP的動詞自動地變換處理邏輯(而且對瀏覽器收藏夾和搜尋引擎比較友好)。

模型繫結器(Model Binders)

在上面的例子中,處理表單提交的Controller action方法的簽名接受一個String和一個Decimal引數,然後action方法建立一個新的Product物件,賦之以這些輸入值,然後試圖將其插入到資料庫中:

第五個預覽版中可以使得這個場景更乾淨的一個新功能是對“模型繫結器(Model Binders)”的支援,模型繫結器提供了一種方法,能將進來的HTTP輸入逆序列化成複雜的型別,然後作為引數傳遞給Controller的action方法。它們還提供了對處理輸入異常的支援,方便了在錯誤發生時表單的重新顯示(而不要求使用者重新輸入資料,後文對此有詳述)。

例如,使用模型繫結器支援,我們可以對上面的action方法進行重構,讓它接受一個Product物件作為方法引數,象這樣:

這使得程式碼變得更加簡明乾淨,還允許我們避免在多個controller類/action方法中重複表單分析的程式碼,允許我們堅守DRY原則:別重複自己(don't repeat yourself)。

註冊模型繫結器

ASP.NET MVC中的模型繫結器是實現了IModelBinder介面的類,可以用來幫助管理型別到輸入引數的繫結。模型繫結器可以編來針對一個特定的物件型別,也可以用來處理一堆型別。IModelBinder介面允許你獨立於web伺服器或任何特定的controller實現來單元測試繫結器。

在一個ASP.NET MVC應用中, 模型繫結器可以在4個不同的層次上註冊,於你如何使用它們,提供了巨大的靈活性:

1) ASP.NET MVC會先看action方法的引數上是否宣告瞭模型繫結器的特性,例如,我們可以象下面這樣,通過使用特性來註解產品引數,以表示我們想使用假想的“Bind”繫結器(注意我們是如何通過特性的一個引數來表示只應該繫結2個屬性):

注: 第五個預覽版目前還沒有象上面這樣的內建[Bind]特性 (雖然我們在考慮在將來將其加為ASP.NET MVC的一個內建功能),但實現象上面這樣的[Bind]特性所需的所有框架級基礎設施在第五個預覽版中都已經實現了。開源的MVCContrib專案也有一個你今天就可以使用的類似的DataBind特性。

2) 如果action方法引數上沒有繫結器特性,ASP.NET MVC就會在傳遞給action方法的引數的型別上看是否註冊有繫結器特性。例如,我們可以通過在Product部分類上加象下面這樣的程式碼,為我們的LINQ to SQL "Product"物件註冊一個顯式的“ProductBinder”繫結器:

3) ASP.NET MVC還支援在應用啟動時,使用ModelBinders.Binders集合註冊繫結器的能力。這在你想要使用由第三方編寫的型別(因為你無法加特性)時,或者你不想直接在你的模型物件上加繫結器特性,尤其有用。下面的程式碼例子演示瞭如何在global.asax中在應用啟動時註冊2個特定型別的繫結器:

4) 除了註冊特定型別的全域性繫結器外,你還可以使用ModelBinders.DefaultBinder屬性來註冊一個預設的繫結器,該繫結器將在沒找到特定型別的繫結器時使用。MVCFutures程式集中(是當前MVC預覽版中預設引用的)包含了一個ComplexModelBinder實現,它根據進來的表單提交的名稱/數值,使用反射來設定物件的屬性。你可以使用下面的程式碼來註冊它,將它當作作為Controller action方法引數傳遞的複雜型別的後備繫結器:

注: MVC開發團隊計劃在下一個預覽版中對IModelBinder介面做進一步的調整 (因為他們最近發現了必須對IModelBinder介面做一些改動的幾個場景)。所以,如果你用第五個預覽版開發了定製的模型繫結器的話,預期在下一個版本出來時需要做些調整(也許不是什麼很大的變動,就是注意一下,我們知道它的方法的一些引數會有變動)。

UpdateModel和TryUpdateModel方法

上面的模型繫結器支援在你想要生成新的物件,將它們作為引數傳遞給controller action方法的場景下非常有用。但還有些場景,你想要能夠把輸入值繫結到在action方法中建立或獲取的現有的物件例項上。例如,在對資料庫中現有的產品的編輯場景中,你也許想要在action方法中,先使用ORM從資料庫中取得一個現有的產品物件,然後將新的輸入值繫結到這個獲取的產品例項上,然後將變動儲存回資料庫。

第五個預覽版在Controller基類中加了2個新的方法,UpdateModel() 和 TryUpdateModel(),來幫助促成這個場景。兩者都允許你將一個現有的物件例項作為第一個引數傳進去,然後作為第二個引數,你傳入你想要使用表單提交值來更新的屬性列表。 例如,下面,我使用LINQ to SQL取得了一個Product物件,然後使用UpdateModel方法來用表單資料更新產品的名稱和價格屬性。

UpdateModel方法會試圖更新你列出的所有的屬性(即使在更新列表前面某個屬性時出了錯)。如果它在更新一個屬性時遇上了錯誤(例如,你對型別是Decimal的UnitPrice屬性輸入了假的字串資料),它會在一個新的ModelState集合中儲存一個丟擲的異常物件,以及原先的表單提交值,這個集合是在第五個預覽版中新加的。我們將在稍後討論這個新的ModelState集合,但簡單地說,在出錯需要重新顯示錶單時,它方便我們用使用者輸入的值自動填充表單,讓使用者來修改。

在嘗試更新所有列出的屬性後,UpdateModel會丟擲一個異常,如果其中一個失敗了的話。TryUpdateModel的工作原理是一樣的,除了不是丟擲一個異常,而是返回一個true/false布林值,表示更新過程中是否有錯外。你可以任意選擇與你的錯誤處理偏好最相合的方法。

Product編輯例子

要看一個使用UpdateModel方法的實際例子,讓我們實現一個簡單的產品編輯表單。我們將使用/Products/Edit/{ProductId}的URL格式來表示我們要編輯哪個產品。例如,在下面,URL是/Products/Edit/4,意味著我們將編輯那個ProductId是4的產品:

使用者可以改變產品名稱或單價,然後點選“Save”按鈕,點選之後,我們的提交action方法將更新資料庫,如果成功的話,將給使用者顯示“產品更新完畢!”的訊息:

我們可以使用下面的2個Controller action方法來實現上面的功能。注意,我們使用了[AcceptVerbs]特性來區別顯示初始表單的Edit action方法,和處理表單提交的另一個Edit action方法:

在上面,我們的POST action方法使用了LINQ to SQL從資料庫中取得我們要編輯的產品物件的例項,然後使用UpdateModel試圖使用表單提交值來更新產品的ProductName 和 UnitPrice值,然後它呼叫了LINQ to SQL datacontext的SubmitChanges()將更新儲存回資料庫中。如果成功的話,我們將一個成功的資訊字串儲存到TempData集合中,然後用客戶端重新定向將使用者重新定向到GET action方法(將導致重新顯示剛儲存好的產品,以及表示更新成功了的TempData資訊字串)。如果出錯的話,無論是表單提交值的問題,還是資料庫更新的問題,一個異常會被丟擲,然後在catch塊中被捕捉住,然後我們會重新顯示錶單檢視,讓使用者作修改。

你也許會疑惑,成功後的這個重新定向是怎麼回事?為什麼不就地重新顯示錶單以及成功資訊呢?客戶端的重新定向的理由是為了確保,如果使用者在成功點選儲存按鈕後點選重新整理按鈕的話,他們不會重新提交表單,得到象這樣一個瀏覽器提示:

做一個重新定向回到action方法的GET版本,確保使用者點選重新整理按鈕會重新裝載頁面,而不是重新提交。這個方法被稱作“Post/Redirect/Get”(即PRG)模式。Tim Barcz 在這裡有一篇很棒的文章,對其在ASP.NET MVC中的應用有更詳細的討論。

上面的2個controller action方法是處理編輯和更新產品物件所需的全部實現。下面是跟上面的Controller相關的“Edit”檢視:

有用的小技巧: 在過去,一旦你開始往URL中新增引數(例如,/Products/Edit/4),你必須在檢視中編寫程式碼來更新form的action屬性,以包括提交URL中的引數。第五個預覽版中包括了一個方便的Html.Form()輔助方法。Html.Form()有許多過載的版本,允許你指定諸多引數選項。我們加了一個新的不接受引數的Html.Form()過載方法,會輸出與當前請求一樣的URL。

例如,如果顯示上面檢視的Controller的URL是 “/Products/Edit/5”,象上面那樣呼叫Html.Form(),作為標識輸出的一部分,會自動輸出

。如果顯示上面檢視的Controller的URL是“/Products/Edit/55”,象上面那樣呼叫Html.Form(),作為標識輸出的一部分,會自動輸出。這提供了一個很巧妙的方法來避免編寫任何定製的程式碼來構建URL或包括引數。

單元測試和UpdateModel

在這個星期的第五個預覽版中,UpdateModel方法總是從Request物件的Form提交集合中取得數值的,這意味著,要測試上面的表單提交action方法,你需要在你的單元測試裡模擬Request物件。

在下一個MVC版本中,我們還將加一個過載的UpdateModel方法,它將允許你傳入一個你自己的數值集合為它所用。例如,我們將能夠使用第五個預覽版中的新的FormCollection型別(有一個ModelBuilder物件會自動地使用所有的表單提交值來填充它),將它作為引數傳給UpdateModel方法,象這樣:

使用象上面這樣的方法將允許我們不用使用任何模擬,就可以單元測試我們的表單提交場景。下面是我們可以編寫的一個單元測試的例程,用來測試一個POST場景用新的數值成功地更新,然後重新定向到我們action方法的GET版本。 注意,為單元測試我們的controller的所有功能,我們不需要模擬任何東西(也不依賴任何特別的輔助方法):

處理錯誤場景 - 重新顯示帶錯誤訊息的表單

在處理表單提交場景時,一個需要注意的重要事情是處理出錯條件。這些情形包括,使用者提交了錯誤的輸入(例如,對型別是Decimal的單價,輸入了字串,而不是數字),以及輸入格式是合法的,但應用後面的業務規則不允許某些東西得到建立/更新/儲存(例如,對停賣了的產品的新的訂購)。

如果使用者在填表時犯了錯,表單應該重新顯示,並輸出清楚明確的錯誤資訊,引導使用者修正輸入。表單還應該保留使用者當初輸入的資料,這樣,他們就不用手工重填了。這個過程應該重複多次,直到表單成功完成為止。

ASP.NET MVC中的基本的表單錯誤處理和輸入驗證

在上面的產品編輯例程中,我們在Controller或View中都沒怎麼編寫錯誤處理程式碼。我們的Edit提交action方法只簡單地用了一個try/catch錯誤處理塊將UpdateModel()輸入對映呼叫以及資料庫儲存SubmitChanges()呼叫圍了起來。如果錯誤發生的話,controller將一個輸出訊息儲存到TempData集合中,然後返回/重新顯示我們的編輯檢視:

在ASP.NET MVC以前的版本中,上面的程式碼提供不了很好的使用者體驗(因為有錯的話,它不會高亮顯示問題所在,也不會保留使用者輸入)。

但在第五個預覽版中,你會發現,只用上面的程式碼,出錯時,你現在能得到一個不錯的使用者體驗。特別地,你會發現,當我們的編輯檢視因為輸入錯誤重新顯示時,它會高亮顯示所有有問題的輸入控制元件,同時保留它們的輸入值,讓我們來修正:

你也許會問,單價文字框是如何用紅色高亮顯示自己,知道輸出原先輸入的使用者值的呢?

第五個預覽版引進了一個新的“ModelState”集合,該集合是在顯示時,作為ViewData的一部分,從Controller傳遞給View的。 ModelState集合給Controllers提供了一個方式,來表示傳給View的模型物件或模型屬性中有錯存在,允許指定一個描述問題所在的對人類友好的錯誤訊息,以及由使用者輸入的原始資料。

開發人員可以在Controller action方法中,明確地編寫程式碼往ModelState集合中新增個項。 ASP.NET MVC 的模型繫結器和UpdateModel()輔助方法也會在遇上輸入錯誤時自動地預設填充這個集合。因為在上面的Edit action方法中,我們使用了UpdateModel()輔助方法,在它試圖將單價文字框的“gfgff23.02”輸入對映到Product.UnitPrice(型別是Decimal)屬性時失敗了的時候,它會自動地往ModelState集合中加一項。

View中的Html輔助方法在預設情形下,在顯示輸出時,現在會檢視ModelState集合。如果它們正顯示的一項有錯存在的話,它們現在會將原先輸入的使用者資料顯示出來,同時會將一個CSS錯誤類顯示到HTML輸入控制元件元素上去。例如,在上面的“Edit”檢視中,我們使用了 Html.TextBox() 輔助方法來顯示我們Product物件的UnitPrice:

在上面的出錯場景下,檢視被顯示時,Html.TextBox()會檢視ViewData.ModelState集合,看我們的Product物件的“UnitPrice”屬性是否有問題,當它看到有問題時,它會將原先輸入的使用者資料"gfgff23.02"輸出,同時將一個css類加到它輸出的上去:

你可以將出錯css類的外觀定製成你想要的任何外觀,在新的ASP.NET MVC專案中建立的樣式表中輸入控制元件元素預設的出錯CSS規則如下所示:

新增額外的驗證訊息

內建的HTML表單輔助方法對有問題的輸入域提供了基本的視覺識別外觀。現在讓我們來往頁面上再加一些更具描述性的出錯資訊,具體做法是,我們可以使用第五個預覽版中這個新的Html.ValidationMessage()輔助方法,這個方法會輸出與一個給定Model或Model屬性相關聯的ModelState集合中的錯誤資訊。

例如,我們可以更新我們的檢視,在文字框的右方使用Html.ValidationMessage()輔助方法,象這樣

當頁面因錯而顯示時,錯誤訊息就會顯示在有問題的控制元件域之後:

Html.ValidationMessage() 方法還有一個過載的版本,接受第二個引數,允許檢視指定要顯示的替換文字:

常見的一個用例是,在輸入控制元件域後面輸出 * 字元,然後將新的Html.ValidationSummary()輔助方法(也是第五個預覽版中新加的)加到頁面的頂部,來列出所有的錯誤訊息:

Html.ValidationSummary()輔助方法然後就會用

顯示出ModelState集合中的所有錯誤訊息,以及 * 和 紅邊框會表示每個有問題的輸入控制元件元素:

注意,我們完全不用改動ProductsController類就取得了如此效果。

支援業務規則的驗證

象上面那樣支援輸入驗證場景是很有用的,但對大多數應用來說是不夠的。在大多數場景下,你還想要能夠執行業務規則,讓你應用的使用者介面能與它們乾淨地結合起來。

ASP.NET MVC支援任何的資料層抽象(無論是基於ORM與否),允許你對你的領域模型,以及相關聯的規則/限制進行任意的結構化。象模型繫結器, UpdateModel輔助方法,以及所有的錯誤顯示和驗證訊息支援這樣的功能,都是特意設計的,這樣你可以在你的MVC應用中使用你想要的任何首選資料訪問故事(包括LINQ to SQL, LINQ to Entities, NHibernate, SubSonic, CSLA.NET, LLBLGen Pro, WilsonORMapper, DataSets, ActiveRecord等等)。

往LINQ to SQL實體中新增業務規則

在上面的例子中,我一直在使用LINQ to SQL 來定義我的Product實體和進行資料訪問操作。迄今為止,在我的Product實體上使用的唯一領域規則/驗證是那些由LINQ to SQL從SQL Server後設資料(可空性,資料型別和長度等等)中推斷出來的那些規則,這會捉住象上面那樣的場景(我們試圖給一個Decimal賦一個有問題的輸入)。但是,它們無法構型那些使用SQL後設資料難以宣告的業務問題。例如,在一個產品停售之後,不能允許它的reorder(重訂購)水平大於0,或者不允許一個產品以小於供應商的價格出售,等等。對這樣的場景,我們需要在我們的模型中新增程式碼來表達和結合這些業務規則。

新增這些業務規則邏輯的錯誤地方是應用的UI層,在那裡加這些規則不好,有很多理由。特別值得一提的是,幾乎肯定會導致程式碼的重複,因為你最終會將這些規則從一個UI拷貝到另一個UI,從一個表單到另一個表單。除了費時外,這麼做,在你改變業務規則邏輯時,如果你忘了在所有的地方做更新的話,有很大的機率會導致缺陷。

一個融合這些業務規則的好得多的地方是你的模型或領域層,那麼做的話,不管是什麼型別的使用者介面或表單或服務,這些規則都可以被使用和執行。對規則的變動只要做一次,不用重複任何邏輯就可在任何地方使用。

我們有幾個模式和方法可用,來將豐富的業務規則整合到上面我們一直在用的Product模型物件中去:我們可以在物件中定義這些規則,也可在物件外面定義這些規則。我們可以使用宣告式規則,一個可重用的規則引擎框架,或者命令式程式碼(imperative code)。要點是,ASP.NET MVC允許我們使用任何一種方法或者所有這些方法(沒有一堆功能要求你總是採用一種方式做事,你擁有對它們進行變通的靈活性,MVC功能的擴充套件性之強大,幾乎可以與任何東西相結合)。

在本貼子中,我將使用一個比較簡單的規則的方法。首先,我將象下面這樣定義一個“RuleViolation”類,我們可以用它來捕捉住我們模型中正被違反的業務規則的資訊。這個類會呈現一個ErrorMessage字串屬性,內含錯誤的細節,以及呈現與之相關聯的造成規則違反的關鍵屬性名稱和屬性值:

(注:為簡單起見,我將只儲存一個屬性, 在更復雜的應用中,這也許會是一個列表,這樣就可以指定多個屬性。)

然後我將定義一個IRuleEntity介面,內含單個方法, GetRuleViolations(),將返回實體中所有業務規則違反的列表:

然後我可以讓我的Product類實現這個介面,為保持這個例子簡單起見,我將把規則定義和估算邏輯內嵌在方法之中。有更好的模式你可以用來促成可重用的規則,以及處理更復雜的規則。如果這個例程變大的話,我需要重構這個方法,這樣,規則以及它們的估算將被定義在其它地方,但目前為保持簡明,我們將象下面這樣估算三個業務規則:

 

我們的應用現在可以查詢Product(或任何其他IRuleEntity)例項來檢查它目前的驗證狀態,以及取回RuleViolation物件,這些物件可用來幫助顯示一個可以引導應用的使用者修正這些違反的使用者介面。它還允許我們獨立於應用的使用者介面來輕鬆地單元測試我們的業務規則。

在這個特定的例子裡,我將選擇確保,我們的Product物件在不合法的狀態下是不能儲存到資料庫中去的(意味著在Product物件可儲存到資料庫之前,所有的RuleViolations都必須被修正)。在LINQ to SQL中,我們可以這麼做,在Product部分類中,加一個OnValidate部分方法。這個方法會在資料庫持久化時,被LINQ to SQL自動呼叫。在下面我將呼叫我們在上面新增的GetRuleViolations()方法,如果有未解決的錯誤,我將丟擲一個異常。這會中止事務,防止資料庫被更新:

那樣,除了有個友好的輔助方法允許我們從一個Product中取出RuleViolations外,我們還確保在資料庫被更新之前那些RuleViolations必須被修正。

將上述規則整合進ASP.NET MVC使用者介面中

一旦象上面那樣實現了業務規則,呈示RuleViolations後,將其整合進我們的ASP.NET MVC例程則是比較容易的事情。

因為我們往Product類中加了OnValidate部分方法,呼叫northwind.SubmitChanges()會丟擲一個異常,如果我們試圖儲存的Product物件中有任何業餘規則驗證問題的話。這個異常會終止任何資料庫事務,在下面的catch塊中被捕捉住:

我們要往我們的錯誤catch塊中加的的一行額外程式碼是呼叫定義在下面的UpdateModelStateWithViolations()輔助方法的邏輯。這個方法從一個實體中取出所有的規則違反的列表,然後將適當的模型錯誤(包括對實體物件上導致錯誤的屬性的引用)更新ModelState集合:

完成之後,我們可以重新執行我們的應用。現在,除了看到與輸入格式相關的錯誤資訊外,ASP.NET MVC的驗證輔助方法還將顯示我們的業務規則的違反情形。

例如,我們可以將單價設成小於$1,將Reorder水平設成-1(從輸入格式的角度來看,這2個值都是合法的,但兩者都違反了業務規則)。當我們這麼做,點選儲存按鈕時,我們將看到Html.ValidationSummary()列表中的錯誤資訊,以及相關的文字框被特別標出了:

我們的業務規則可以跨越多個Product屬性,例如,你也許注意到了,我在上面加了一條規則說,如果產品被中止的話,reorder水平不能大於0:

在這整個業務規則過程中,對我們的“Edit”檢視模板要做的唯一變動是往檔案中再加2個Product屬性(Reorder和Discontinued):

現在我們可以往我們的Product實體中新增任何數目的額外業務驗證規則,而且為使得介面支援它們,我們不需要更新Edit檢視,也不要更新ProductsController。

我們還可以獨立於我們的Controller和View,對我們的模型和業務規則進行單獨的單元測試。我們也可以獨立於我們的Controller, Views 和 Models對URL路徑選擇規則進行單獨的單元測試。我們可以獨立於我們的Views單獨單元測試我們的Controller。本部落格貼子展示的所有場景都不要求使用任何模擬(mocking)或替補(stubbing)就支援單元測試,最終的結果是極其容易測試的應用,而且支援很好的TDD工作流程。

結語

本貼子對錶單提交場景在ASP.NET MVC第五個預覽版中的工作原理做了一個簡單的介紹,希望在閱讀之後,你對如何使用MVC模型處理表單和輸入場景有了更好的認識,你可以在這裡下載我在上面建造的應用的完整C#版本。本星期稍後,我將釋出相應的VB版本(不幸的是,在我鍵入該文字的時候,已經是臨晨4:30了,我需要在幾個短短的小時後跳上一架飛機,但現在還沒有開始收拾行裝呢)。

重要事項: 如果你不喜歡MVC模型,或者發現它與你的開發風格相違的話,你不一定要用它。這產品完全是個可選項,它並不替代現有的WebForms模型。WebForms和MVC模型以後都會被同時支援和增強(下一個ASP.NET WebForms版本將增加更豐富的URL路由特性,更好的HTML標識/客戶端ID/CSS支援等等)。所以,如果在閱讀了上面的貼子之後,你認為“呃,我感覺這不是很自然”,那麼不用擔心,也不用覺得你應該或者需要使用它,你不用那麼想。

在下一個MVC貼子裡,我將討論如何將AJAX整合進你的ASP.NET MVC應用。

希望本文對你有所幫助,

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-621004/,如需轉載,請註明出處,否則將追究法律責任。

相關文章