[转]Asp.Net MVC使用HtmlHelper渲染,并传递FormCollection参数的陷阱
http://www.cnblogs.com/errorif/archive/2012/02/13/2349902.html
在Asp.Net MVC
1.0编程中,我们经常遇见这样的场景,在新建一个对象时候,通过HtmlHelper的方式在View模型中渲染Html控件,当填写完相关内容后,通过Form把需要新建的内容Post回View对应Controller的Action(例如:Create),指定的Action可以通过接受FormCollection参数、值参数或者某个类的实例参数(比如:Movie类),完成新建的操作。(主要指HtmlHelper.TextBox)
当我们通过传递FormCollection参数进行操作时,如果不使用UpdateModel方法,而利用ModelState.IsValid及ModelState.AddModelError实现错误校验提示等操作。这个时候,小心陷阱。
【注:本文章源代码通过VS2008创建】
1、View模型中HtmlHelper绑定数据的顺序
开始前,让我们先了解下View模型中HtmlHelper绑定数据的顺序(主要指HtmlHelper.TextBox,其它还未研究)
我们知道,当View使用了HtmlHelper进行控件渲染的时候,HtmlHelper会通过键值尝试填充我们曾经填写过的数据,以防止用户从头填写。(比如:我们填写表单,提交,当出现验证错误的时候,我们希望表单刷新后曾经填写的内容依然存在,而不是全部要重新填写。而HtmlHelper就是这样帮助我们的)。HtmlHelper填充数据的顺序如下:
(1) 通过键值调用ModelState集合对应的System.Web.Mvc.ModelState实例的Value属性获取
(2) 通过HtmlHelper指定的值填充(Html.TextBox("Title",指定值))
(3) 通过键值获取ViewData内的对应数据
(4) 通过键值获取View中强类型的Model对象对应属性的数据
(5) 不填充
2、 传递FormCollection参数,不使用UpdateModel引起的异常
先看一个简单的例子(源代码下载)。View通过Post传递FormCollection参数到对应Controller的Create
Action,Create
Action检验参数是否合法。如果合法,暂时什么都不做;如果不合法,则通过ModelState的AddModelError添加错误信息,并通过ModelState.
IsValid判断,如果无效,重新返回该View。
(1) View代码(没有任何特殊的地方,HtmlHelper使用Html.TextBox("Title")的方式):


ID="Content1"
ContentPlaceHolderID="TitleContent"
runat="server">

Create


ID="Content2"
ContentPlaceHolderID="MainContent"
runat="server">

<h2>Create</h2>

<%=
Html.ValidationSummary("Create was unsuccessful.
Please correct the errors and try again.") %>

<% using (Html.BeginForm())
{%>

<fieldset>

<legend>Fields</legend>

<p>

<label for="Title">Title:</label>

<%= Html.TextBox("Title") %>

<%=
Html.ValidationMessage("Title", "*") %>

</p>

<p>

<label for="Director">Director:</label>

<%= Html.TextBox("Director") %>

<%=
Html.ValidationMessage("Director", "*") %>

</p>

<p>

<label for="Remark">Remark:</label>

<%= Html.TextBox("Remark") %>

<%=
Html.ValidationMessage("Remark", "*") %>

</p>

<p>

<input type="submit"
value="Create" />

</p>

</fieldset>

<% } %>

<div>

<%=Html.ActionLink("Back to List", "Index") %>

</div>


(2) Controller中Create Action代码
对应的Create
Action代码如下,我们通过UpdateModel来进行Movie类的填充,而是直接创建了一个Movie类的实例(我直接在Controller的Action中验证参数,虽然我知道这样做不对,这里只是个例子。):
public ActionResult Create(FormCollection collection)

{

//手动实例化

{

Title =
collection["Title"],

Director = collection["Director"],

Remark =
collection["Remark"]};

if
(m.Title.Trim().Length == 0)

{

ModelState.AddModelError("Title", "Title 不能为空!");

}

if
(m.Director.Trim().Length == 0)

{

ModelState.AddModelError("Director", "Director 不能为空!");

}

if
(!ModelState.IsValid)

{

return
View();

}

try

{

//TODO
SaveToDB

Content("OK");

}

catch

{

return
View();

}

(3) 运行结果
大家可以下载代码运行,结果如下:当不输入参数,提交表单时,我们希望这个时候能够提示“Title 不能为空!”和“Director
不能为空!”。但是,很不幸,报错了。
3、传递FormCollection,使用UpdateModel
现在,View的代码不变,我们在Create
Action中使用UpdateModel方法,代码如下(源代码下载):

public ActionResult Create(FormCollection collection)

{

Movie m = new Movie();

//使用UpdateModel方法



if
(m.Title.Trim().Length == 0)

{

ModelState.AddModelError("Title", "Title 不能为空!");

}

if
(m.Director.Trim().Length == 0)

{

ModelState.AddModelError("Director", "Director 不能为空!");

}

if
(!ModelState.IsValid)

{

return
View();

}

try

{

//TODO
SaveToDB

Content("OK");

}

catch

{

return
View();

}

}

大家可以下载代码,运行:当不输入参数时,提示“Title 不能为空!”和“Director
不能为空!”,一切正常。

4、 原因分析
下面我们来分析下造成这个问题的原因。
(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!(代码下载)
public ActionResult Create(FormCollection collection)

{

Movie m = new Movie() {

Title =
collection["Title"],

Director = collection["Director"],

Remark =
collection["Remark"] };


//手动添加数据到ModelState集合

{

Value =
collection.ToValueProvider()["Title"] });

ModelState.Add("Director", new ModelState() {
Value =
collection.ToValueProvider()["Director"] });
ModelState.Add("Remark", new ModelState() {
Value =
collection.ToValueProvider()["Remark"] });
if
(m.Title.Trim().Length == 0)
{
ModelState.AddModelError("Title", "Title 不能为空!");
}
if
(m.Director.Trim().Length == 0)
{
ModelState.AddModelError("Director", "Director 不能为空!");
}
if
(!ModelState.IsValid)
{
return
View();
}
try
{
//TODO
SaveToDB return
Content("OK");
}
catch
{
return
View();
}
}
现在,让我们再来分析下异常的原因:
当传递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。因此,出现了开篇的问题:“未将对象应用设置到对象值的实例”。
5、直接传递类参数、值参数
如果我们在Post的时候不传递FormatCollection,而是直接传递类或者值参数。
传递类


Create(Movie m){}
传递值参数


Create(string Title,string
Director,string Remark){}
那不会出现问题。因为当传递的是类或者参数时,默认的ModelBinder除了会实例化Movie类并匹配属性或给参数赋值外,还会根据键值填充ModelState集合,就像UpdateModel会帮你做这件事情一样。
6、 小结
(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对象对应属性的数据。
7、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 数据验证
8、补充:
根据回复补充:
一、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赋值有两种方法(二选一):
1、在Create Action中给ViewData.Model赋值
ViewData.Model = new Movie() (第一次进入Create Action调用)
ViewData.Model = m(Post回Create Action时候调用,m为手动、自动或者传递参数过来的Movie对象实例)
2、返回使用带TModel参数的重载函数View(TModel)
Return View(new Movie())(说明同上)
Return View(m)(说明同上)
[转]Asp.Net MVC使用HtmlHelper渲染,并传递FormCollection参数的陷阱的更多相关文章
- [转]Asp.Net MVC使用HtmlHelper渲染,并传递FormCollection参数的陷阱 【转】
在Asp.Net MVC 1.0编程中,我们经常遇见这样的场景,在新建一个对象时候,通过HtmlHelper的方式在View模型中渲染Html控件,当填写完相关内容后,通过Form把需要新建的内容Po ...
- 在ASP.NET MVC中以post方式传递数组参数的示例
最近在工作中用到了在ASP.NET MVC中以post方式传递数组参数的情况,记录下来,以供参考. 一.准备参数对象 在本例中,我会传递两个数组参数:一个字符串数组,一个自定义对象数组.这个自定义对象 ...
- 在ASP.NET MVC中以post方式传递数组参数的示例【转】
最近在工作中用到了在ASP.NET MVC中以post方式传递数组参数的情况,记录下来,以供参考. 一.准备参数对象 在本例中,我会传递两个数组参数:一个字符串数组,一个自定义对象数组.这个自定义对象 ...
- ASP.NET MVC中将数据从Controller传递到视图
ASP.NET MVC中将数据从Controller传递到视图方法 1.ViewData ViewData的类型是字典数据,key-value 如:ViewData["Data"] ...
- ASP.NET MVC Razor HtmlHelper扩展和自定义控件
先看示例代码: using System; using System.Collections.Generic; using System.Linq; using System.Web; using S ...
- 使用asp.net mvc部分视图渲染html
为了提升用户体验,一般我们采用ajax加载数据然后根据数据渲染html,渲染html可以使用前端渲染和服务器端渲染. 前端渲染 使用前端模版引擎或MVC框架,例如underscore.js的templ ...
- ASP.NET MVC使用RenderSection渲染节点
几天没有时间做ASP.NET mvc练习,忙于ERP的二次开发.忙里间,想起MVC还有很多基础的知识需要撑握与了解.记得以前有练习过<MVC母版页_Layout.cshtml> http: ...
- ASP.NET MVC 中将数据从View传递到控制器中的三种方法(表单数据绑定)
http://www.cnblogs.com/zyqgold/archive/2010/11/22/1884779.html 在ASP.NET MVC框架中,将视图中的数据传递到控制器中,主要通过发送 ...
- ASP.NET MVC中默认Model Binder绑定Action参数为List、Dictionary等集合的实例
在实际的ASP.NET mvc项目开发中,有时会遇到一个参数是一个List.Dictionary等集合类型的情况,默认的情况ASP.NET MVC框架是怎么为我们绑定ASP.NET MVC的Actio ...
随机推荐
- python—索引与切片总结
python中索引与切片的熟练掌握对于字符串的操作很有帮助,梳理如下: (1)索引 S = 'hello world' 1)正向索引 正向索引从0开始,向右依次递增. 2)反向索引 反向索引从-1开始 ...
- Workbook对象的方法总结(一)
import openpyxlwb=openpyxl.Workbook()print('1.添加前所有工作簿的名称是:',wb.get_sheet_names())wb.create_sheet('F ...
- 多tomcat 同一个浏览器 多个项目 会导致session覆盖
1,多tomcat 同一个浏览器 同一个项目 会导致session覆盖 个人猜测:一个服务器中有多个Tomcat服务器多个项目,每个服务器占用不同的端口号,当在同一个浏览器里面同时打开2个系统时,一个 ...
- 富文本(wangEditor框架)的使用教程
富文本的使用教程(wangEditor框架) 一,相信很多人用过很多富文本的框架,现在我向大家推荐一个很实用的一个富文本框架,具有丰富的功能项,容易使用. 所以本博客介绍这个富文本编辑器的使用哈!觉得 ...
- Django数据库 相关之select_related/prefetch_related
- 性能相关 user_list = models.UserInfo.objects.all() for row in user_list: # 只去取当前表数据 select_related,主动连 ...
- Vue 事件处理
原生的js事件处理 原生的js事件处理,可以分为:直接内联执行代码,或者绑定事件函数. 在内联的事件处理函数内部或者事件绑定的方法内部的作用域中的this都是指向当前的dom对象.如何在vue绑定的元 ...
- iOS 静态库生成(引用第三方SDK、开源库、资源包)
一.静态库创建 打开Xcode, 选择File ----> New ---> Project 选择iOS ----> Framework & Library ---> ...
- It isn't possible to write into a document from an asynchronously-loaded
It isn't possible to write into a document from an asynchronously-loaded 今天遇到了一个问题: 通过document.wri ...
- Windows下Visual Studio2017之AI环境搭建
本博客主要包含以下3点: AI简介及本博客主要目的 环境介绍及安装原因 搭建环境及检验是否安装成功 离线模型的训练 时间分配: 时间 时长(分钟) 收集资料+写博客 6.12 11:28-12:2 ...
- 第26次Scrum会议(11/14)【欢迎来怼】
一.小组信息 队名:欢迎来怼小组成员队长:田继平成员:李圆圆,葛美义,王伟东,姜珊,邵朔,阚博文 小组照片 二.开会信息 时间:2017/11/14 11:35~11:57,总计22min.地点:东北 ...