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)(说明同上)