http://www.cnblogs.com/errorif/archive/2012/02/13/2349902.htmljavascript
在Asp.Net MVC
1.0編程中,咱們常常碰見這樣的場景,在新建一個對象時候,經過HtmlHelper的方式在View模型中渲染Html控件,當填寫完相關內容後,經過Form把須要新建的內容Post回View對應Controller的Action(例如:Create),指定的Action能夠經過接受FormCollection參數、值參數或者某個類的實例參數(好比:Movie類),完成新建的操做。(主要指HtmlHelper.TextBox)html
當咱們經過傳遞FormCollection參數進行操做時,若是不使用UpdateModel方法,而利用ModelState.IsValid及ModelState.AddModelError實現錯誤校驗提示等操做。這個時候,當心陷阱。
【注:本文章源代碼經過VS2008建立】
java
1、View模型中HtmlHelper綁定數據的順序編程
開始前,讓咱們先了解下View模型中HtmlHelper綁定數據的順序(主要指HtmlHelper.TextBox,其它還未研究)mvc
咱們知道,當View使用了HtmlHelper進行控件渲染的時候,HtmlHelper會經過鍵值嘗試填充咱們曾經填寫過的數據,以防止用戶從頭填寫。(好比:咱們填寫表單,提交,當出現驗證錯誤的時候,咱們但願表單刷新後曾經填寫的內容依然存在,而不是所有要從新填寫。而HtmlHelper就是這樣幫助咱們的)。HtmlHelper填充數據的順序以下:asp.net
(1) 經過鍵值調用ModelState集合對應的System.Web.Mvc.ModelState實例的Value屬性獲取ide
(2) 經過HtmlHelper指定的值填充(Html.TextBox("Title",指定值))函數
(3) 經過鍵值獲取ViewData內的對應數據工具
(4) 經過鍵值獲取View中強類型的Model對象對應屬性的數據spa
(5) 不填充
二、 傳遞FormCollection參數,不使用UpdateModel引發的異常
先看一個簡單的例子(源代碼下載)。View經過Post傳遞FormCollection參數到對應Controller的Create
Action,Create
Action檢驗參數是否合法。若是合法,暫時什麼都不作;若是不合法,則經過ModelState的AddModelError添加錯誤信息,並經過ModelState.
IsValid判斷,若是無效,從新返回該View。
(1) View代碼(沒有任何特殊的地方,HtmlHelper使用Html.TextBox("Title")的方式):
(2) Controller中Create Action代碼
對應的Create
Action代碼以下,咱們經過UpdateModel來進行Movie類的填充,而是直接建立了一個Movie類的實例(我直接在Controller的Action中驗證參數,雖然我知道這樣作不對,這裏只是個例子。):
(3) 運行結果
你們能夠下載代碼運行,結果以下:當不輸入參數,提交表單時,咱們但願這個時候可以提示「Title 不能爲空!」和「Director
不能爲空!」。可是,很不幸,報錯了。
3、傳遞FormCollection,使用UpdateModel
如今,View的代碼不變,咱們在Create
Action中使用UpdateModel方法,代碼以下(源代碼下載):
你們能夠下載代碼,運行:當不輸入參數時,提示「Title 不能爲空!」和「Director
不能爲空!」,一切正常。
四、 緣由分析
下面咱們來分析下形成這個問題的緣由。
(1) 認識一下 System.Web.Mvc.ModelStateDictionary和System.Web.Mvc.ModelState
咱們知道,每一個Controller都有一個類型爲System.Web.Mvc.ModelStateDictionary的ModelState集合(後文中稱爲ModelState集合),該集合是一個System.Web.Mvc.ModelState對象的集合(MVC在這裏取名存在嚴重的問題,Controller裏面的ModelState既然是個集合,應該命名爲ModelStates或者ModelStateCollection,以避免被誤會)。System.Web.Mvc.ModelState這個對象包含兩個屬性:
l Errors:類型爲System.Web.Mvc.ModelErrorCollection的屬性。
l Value:類型爲System.Web.Mvc.ValueProviderResult的屬性。
(2)UpdateModel方法與 System.Web.Mvc.ModelStateDictionary和System.Web.Mvc.ModelState的關係
當調用UpdateModel方法時,它至少作了兩件事情。
A、 把提交的數據(FormCollection中的數據)與Movie類實例的屬性匹配並自動更新。(參考:有一天,WebForm 對 MVC 說:可否借你的UpdateModel方法來用用?)
B、 將每一個匹配的FormCollection中的數據實例化爲System.Web.Mvc.ModelState類,並根據鍵值分別加入ModelState集合中。
經過調試發現,在調用UpdateModel方法前,ModelState集合沒有數據;調用後,集合內是有數據的。
l 調用UpdateModel前 l 調用UpdateModel後
(3)不使用UpdateModel方法,AddModelError與System.Web.Mvc.ModelStateDictionary和System.Web.Mvc.ModelState的關係
當不使用UpdateModel方法,而在驗證不經過時候調用ModelState.AddModelError方法時。經過調試發現,ModelState集合也是有數據的。
也就是說,AddModelError方法一樣實例化了System.Web.Mvc.ModelState類,並根據鍵值將它加入ModelState集合。
經過圖能夠看到,集合內有兩個System.Web.Mvc.ModelState對象的實例。
(4)UpdateModel方法與ModelState.AddModelError的PK
既然UpdateModel和ModelState.AddModelError都實例化了System.Web.Mvc.ModelState,並加入了ModelState集合,那有什麼區別呢?
l UpdateModel方法:經過調試發現,當使用UpdateModel方法後,ModelState集合內的System.Web.Mvc.ModelState類的實例的Value屬性是不爲空的。
l ModelState.AddModelError方法:經過調試發現,當不使用UpdateModel而調用ModelState.AddModelError 方法後,ModelState集合的System.Web.Mvc.ModelState類的實例的Value屬性是空的。
就是說,當傳遞FormCollection參數時,若是不使用UpdateModel方法,而只使用ModelState.AddModelError方法,ModelState集合中System.Web.Mvc.ModelState類的實例的Value屬性並不會被賦值。
(5)不使用UpdateModel方法,手動向ModelState集合的System.Web.Mvc.ModelState實例的Value屬性賦值。
經過上面的分析,咱們知道,當傳遞FormCollection參數時,若是不使用UpdateModel方法,當咱們調用ModelState.AddModelError方法時,System.Web.Mvc.ModelState對象會被建立,並根據鍵值被加入到ModelState集合中了,但它的Value屬性是空的。那咱們就須要手動執行賦值這個操做。經過使用ModelState集合的「Add(string key, ModelState
value)」方法能夠搞定。如今,一切OK!(代碼下載)
如今,讓咱們再來分析下異常的緣由:
當傳遞FormCollection參數時,不使用UpdateModel方法,但在驗證失敗後調用ModelState.AddModelErro方法時,System.Web.Mvc.ModelState被實例化,並經過某個鍵值(好比「Title」)加入到了ModelState集合中。可是,該System.Web.Mvc.ModelState實例的Value屬性是NULL的。
當在View中使用HtmlHelper.TextBox("Title")進行渲染的時候,HtmlHelper試圖經過鍵值(「Title」)從新將輸入值與控件綁定(例如:TextBox)時,因爲ModelState集合的優先級最高,所以HtmlHelper試圖經過這個鍵值(「Title」)從ModelState集合中獲取數據(經過調用GetModelStateValue()方法)。因爲AddModelErro方法的「功勞」,HtmlHelper獲取到了這個鍵值(「Title」)對應的System.Web.Mvc.ModelState類的實例,但該實例的Value屬性是Null。所以,出現了開篇的問題:「未將對象應用設置到對象值的實例」。
五、直接傳遞類參數、值參數
若是咱們在Post的時候不傳遞FormatCollection,而是直接傳遞類或者值參數。
傳遞類
傳遞值參數
那不會出現問題。由於當傳遞的是類或者參數時,默認的ModelBinder除了會實例化Movie類並匹配屬性或給參數賦值外,還會根據鍵值填充ModelState集合,就像UpdateModel會幫你作這件事情同樣。
六、 小結
(1) Controller中的ModelState集合是個很重要的東西,它是System.Web.Mvc.ModelState類的集合,System.Web.Mvc.ModelState的實例會負責保存鍵值匹配的輸入值(Value屬性)、以及驗證錯誤信息(Errors屬性)。
(2) Post方式傳遞類參數、值參數時,會經過默認的ModelBinder來填充ModelState集合。
(3) UpdateModel方法也會填充ModelState集合。
(4) 若是使用HtmlHelper,並傳遞FormCollection參數,又須要經過ModelState.AddModelError添加錯誤驗證信息,則須要調用UpdateModel方法或經過ModelState.Add方法來填充ModelState集合。
(5) 使用HtmlHelper渲染View中的控件數據的時候(主要指HtmlHelper.TextBox,其它還未研究),綁定順序爲:ModelState集合、指定值、ViewData內的數據、View中強類型Model對象對應屬性的數據。
七、PS:
若是經過Asp.Net MVC 1.0作數據驗證的時候,咱們一般不會直接在Controller中的Action裏面作,提供幾個開源的工具和幾篇文章:
n FluentValidation
下載地址:http://www.codeplex.com/FluentValidation
文章:http://www.cnblogs.com/wintersun/archive/2009/02/15/1390990.html
n Data Annotation Model Binder
下載地址:http://aspnet.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=24471
文章:http://www.asp.net/learn/mvc/tutorial-39-cs.aspx
或者Google 4 : Asp.Net MVC 數據驗證
八、補充:
根據回覆補充:
1、View模型中採用了HtmlHelper("Title",Model.Title)的方式
若是View模型中採用了HtmlHelper("Title",Model.Title)的方式,在第一次進入Create Action的時候,須要給ViewData.Model賦值,若是是Post回的Create Action,若是還須要顯示這個View,也須要給ViewData.Model賦值,不然View模型中的Model爲NULL,也會提示未將對象應用設置到對象值的實例」。給ViewData.Model賦值有兩種方法(二選一):一、在Create Action中給ViewData.Model賦值 ViewData.Model = new Movie() (第一次進入Create Action調用) ViewData.Model = m(Post回Create Action時候調用,m爲手動、自動或者傳遞參數過來的Movie對象實例)二、返回使用帶TModel參數的重載函數View(TModel) Return View(new Movie())(說明同上) Return View(m)(說明同上)