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")的方式):

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"Inherits="System.Web.Mvc.ViewPage<ValidationTest.Models.Movie>" %>
<asp:Content 
ID="Content1" 
ContentPlaceHolderID="TitleContent" 
runat="server">
    
Create
</asp:Content>
<asp:Content 
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>
</asp:Content>

(2) Controller中Create Action代码

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

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(FormCollection collection)
        
{
           
//手动实例化
            Movie m = new Movie() 

                
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
 return 
Content("OK");
            
}
            
catch
            
{
                
return 
View();
            
}

(3) 运行结果

大家可以下载代码运行,结果如下:当不输入参数,提交表单时,我们希望这个时候能够提示“Title 不能为空!”和“Director 
不能为空!”。但是,很不幸,报错了。

 
3、传递FormCollection,使用UpdateModel

现在,View的代码不变,我们在Create 
Action中使用UpdateModel方法,代码如下(源代码下载):

[AcceptVerbs(HttpVerbs.Post)]     
public ActionResult Create(FormCollection collection)
        
{
           
Movie m = new Movie();
           
//使用UpdateModel方法
            UpdateModel<Movie>(m);

            
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();
            
}
        
}

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

4、 原因分析

下面我们来分析下造成这个问题的原因。

(1) 认识一下 System.Web.Mvc.ModelStateDictionarySystem.Web.Mvc.ModelState

我们知道,每个Controller都有一个类型为System.Web.Mvc.ModelStateDictionary的ModelState集合(后文中称为ModelState集合),该集合是一个System.Web.Mvc.ModelState对象的集合(MVC在这里取名存在严重的问题,Controller里面的ModelState既然是个集合,应该命名为ModelStates或者ModelStateCollection,以免被误会)。System.Web.Mvc.ModelState这个对象包含两个属性:

Errors类型为System.Web.Mvc.ModelErrorCollection的属性。

Value类型为System.Web.Mvc.ValueProviderResult的属性。

(2)UpdateModel方法与 System.Web.Mvc.ModelStateDictionarySystem.Web.Mvc.ModelState的关系

当调用UpdateModel方法时,它至少做了两件事情。

A、 把提交的数据(FormCollection中的数据)与Movie类实例的属性匹配并自动更新。(参考:有一天,WebForm 对 MVC 说:能否借你的UpdateModel方法来用用?

B、 将每个匹配的FormCollection中的数据实例化为System.Web.Mvc.ModelState类,并根据键值分别加入ModelState集合中。

通过调试发现,在调用UpdateModel方法前,ModelState集合没有数据;调用后,集合内是有数据的。

调用UpdateModel前                                 l 调用UpdateModel后

       

(3)不使用UpdateModel方法,AddModelErrorSystem.Web.Mvc.ModelStateDictionarySystem.Web.Mvc.ModelState的关系

当不使用UpdateModel方法,而在验证不通过时候调用ModelState.AddModelError方法时。通过调试发现,ModelState集合也是有数据的。

也就是说,AddModelError方法同样实例化了System.Web.Mvc.ModelState类,并根据键值将它加入ModelState集合。

通过图可以看到,集合内有两个System.Web.Mvc.ModelState对象的实例。

(4)UpdateModel方法与ModelState.AddModelError的PK

既然UpdateModelModelState.AddModelError都实例化了System.Web.Mvc.ModelState,并加入了ModelState集合,那有什么区别呢?

UpdateModel方法通过调试发现,当使用UpdateModel方法后,ModelState集合内的System.Web.Mvc.ModelState类的实例的Value属性是不为空的。

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!(代码下载

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(FormCollection collection)
        
{
            
Movie m = new Movie() { 
                
Title = 
collection["Title"],
                
Director = collection["Director"],
                
Remark = 
collection["Remark"] };

            
//手动添加数据到ModelState集合
            ModelState.Add("Title", new 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,而是直接传递类或者值参数。

传递类

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult 
Create(Movie m){}

传递值参数

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult 
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里面做,提供几个开源的工具和几篇文章:

FluentValidation

下载地址:http://www.codeplex.com/FluentValidation

文章:http://www.cnblogs.com/wintersun/archive/2009/02/15/1390990.html

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参数的陷阱的更多相关文章

  1. [转]Asp.Net MVC使用HtmlHelper渲染,并传递FormCollection参数的陷阱 【转】

    在Asp.Net MVC 1.0编程中,我们经常遇见这样的场景,在新建一个对象时候,通过HtmlHelper的方式在View模型中渲染Html控件,当填写完相关内容后,通过Form把需要新建的内容Po ...

  2. 在ASP.NET MVC中以post方式传递数组参数的示例

    最近在工作中用到了在ASP.NET MVC中以post方式传递数组参数的情况,记录下来,以供参考. 一.准备参数对象 在本例中,我会传递两个数组参数:一个字符串数组,一个自定义对象数组.这个自定义对象 ...

  3. 在ASP.NET MVC中以post方式传递数组参数的示例【转】

    最近在工作中用到了在ASP.NET MVC中以post方式传递数组参数的情况,记录下来,以供参考. 一.准备参数对象 在本例中,我会传递两个数组参数:一个字符串数组,一个自定义对象数组.这个自定义对象 ...

  4. ASP.NET MVC中将数据从Controller传递到视图

    ASP.NET MVC中将数据从Controller传递到视图方法 1.ViewData ViewData的类型是字典数据,key-value 如:ViewData["Data"] ...

  5. ASP.NET MVC Razor HtmlHelper扩展和自定义控件

    先看示例代码: using System; using System.Collections.Generic; using System.Linq; using System.Web; using S ...

  6. 使用asp.net mvc部分视图渲染html

    为了提升用户体验,一般我们采用ajax加载数据然后根据数据渲染html,渲染html可以使用前端渲染和服务器端渲染. 前端渲染 使用前端模版引擎或MVC框架,例如underscore.js的templ ...

  7. ASP.NET MVC使用RenderSection渲染节点

    几天没有时间做ASP.NET mvc练习,忙于ERP的二次开发.忙里间,想起MVC还有很多基础的知识需要撑握与了解.记得以前有练习过<MVC母版页_Layout.cshtml> http: ...

  8. ASP.NET MVC 中将数据从View传递到控制器中的三种方法(表单数据绑定)

    http://www.cnblogs.com/zyqgold/archive/2010/11/22/1884779.html 在ASP.NET MVC框架中,将视图中的数据传递到控制器中,主要通过发送 ...

  9. ASP.NET MVC中默认Model Binder绑定Action参数为List、Dictionary等集合的实例

    在实际的ASP.NET mvc项目开发中,有时会遇到一个参数是一个List.Dictionary等集合类型的情况,默认的情况ASP.NET MVC框架是怎么为我们绑定ASP.NET MVC的Actio ...

随机推荐

  1. 使用Python批量修改数据库执行Sql文件

    由于上篇文章中批量修改了文件,有的时候数据库也需要批量修改一下,之前的做法是使用宝塔的phpMyAdmin导出一个已经修改好了的sql文件,然后依次去其他数据库里导入,效率不说极低,也算低了,且都是些 ...

  2. [redis] 几种redis数据导出导入方式

    环境说明: 172.20.0.1 redis源实例 172.20.0.2 redis目标实例 172.20.0.3 任意linux系统 一.redis-dump方式 1.安装redis-dump工具 ...

  3. git push remote: User permission denied

    这种错误因为本地保存了一个错误的账号密码,只需要重新编辑成正确的账号密码 直接上方法

  4. PHP Filter 函数 日常可用

    PHP Filter 函数 PHP Filesystem PHP FTP PHP Filter 简介 PHP 过滤器用于对来自非安全来源的数据(比如用户输入)进行验证和过滤. 安装 filter 函数 ...

  5. 用C给小学生出题目

    用C给小学生出题目 一.预估与实际 PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟) Planning 计划 600 300 • Es ...

  6. 20162319 实验二 Java面对对象程序设计 实验报告

    实验二 Java面向对象程序设计 实验内容 1.初步掌握单元测试和TDD 2.理解并掌握面向对象三要素:封装.继承.多态 3.初步掌握UML建模 4.熟悉S.O.L.I.D原则 5.了解设计模式 实验 ...

  7. 图文转换NABCD

    作为图文转化还是有很多优点的,在这里我就分析一下它的方便快捷 Need:有些非电子版的文字不方便我们编辑,图文转换可以轻而易举达到目的. Approach:现在技术手段应该还有点难度,应该可以换个方法 ...

  8. 第二阶段Sprint3

    昨天:查看资料,开始视频录制部分的代码实现 今天:事实现保存到指定路径,并能够选择播放 遇到的问题:自动生成文件名,是否需要自己命名?怎么实现?

  9. Sprint会议2

    昨天:准备查找安卓APP开发的有关资料,安装有关软件 今天:自己制作一个安卓小程序,熟悉一下操作 遇到问题:安装遇到问题,环境配置出现问题

  10. Week2-作业1

    第一章:引用:如果一架民用飞机上有一个功能,用户使用它的概率是百万分之一,你还要做这个功能么?      选择之后,这个功能是什么呢?谜底是飞机的安全功能.        个人认为,飞机的安全功能这个 ...