控件中国网现已改版,您看到的是老版本网站的镜像,系统正在为您跳转到新网站首页,请稍候.......
中国最专业的商业控件资讯网产品咨询电话:023-67870900 023-67871946
产品咨询EMAIL:SALES@COMPONENTCN.COM

Asp.Net MVC使用HtmlHelper渲染,并传递FormCollection参数的陷阱

作者:零零猪 出处:博客园 2010年02月03日 阅读:

在Asp.Net MVC 1.0编程中,我们经常遇见这样的场景,在新建一个对象时候,通过HtmlHelper的方式在View模型中渲染Html控件,当填写完相关内容后,通过Form把需要新建的内容Post回View对应Controller的Action(例如:Create),指定的Action可以通过接受FormCollection参数、值参数或者某个类的实例参数(比如:Movie类),完成新建的操作。

当我们通过传递FormCollection参数进行操作时,如果不使用UpdateModel方法,而利用ModelState.IsValid及ModelState.AddModelError实现错误校验提示等操作。这个时候,小心陷阱。
【注:本文章源代码通过VS2008创建】

传递FormCollection参数,不使用UpdateModel引起的异常

View模型中HtmlHelper绑定数据的顺序
 


1

 

开始前,让我们先了解下View模型中HtmlHelper绑定数据的顺序

我们知道,当View使用了HtmlHelper进行控件渲染的时候,HtmlHelper会通过键值尝试填充我们曾经填写过的数据,以防止用户从头填写。(比如:我们填写表单,提交,当出现验证错误的时候,我们希望表单刷新后曾经填写的内容依然存在,而不是全部要重新填写。而HtmlHelper就是这样帮助我们的)。HtmlHelper填充数据的顺序如下:

1   通过键值调用ModelState集合对应的System.Web.Mvc.ModelState实例的Value属性获取

2   通过HtmlHelper指定的值填充<%= Html.TextBox("Title",指定值) %>

3   通过键值获取ViewData内的对应数据

4   通过键值获取View中强类型的Model对象对应属性的数据

5   不填充

2、   

 

 

先看一个简单的例子源代码下载)。View通过Post传递FormCollection参数到对应Controller的Create Action,Create Action检验参数是否合法。如果合法,暂时什么都不做;如果不合法,则通过ModelState的AddModelError添加错误信息,并通过ModelState. IsValid判断,如果无效,重新返回该View。

 

(1)   View代码(没有任何特殊的地方,自动生成):

 

 

<%@ 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、   

原因分析

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

调用UpdateModel                                 l   调用UpdateModel
       


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

我们知道,每个Controller都有一个类型为System.Web.Mvc.ModelStateDictionaryModelState集合(后文中称为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的属性。

 

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

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

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

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

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

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

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

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

 

 


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

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


4UpdateModel方法与ModelState.AddModelErrorPK

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

l  

 

 

 

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

 

 

 

        [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进行渲染的时候,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){}

 

6、    小结

 如果通过Asp.Net MVC 1.0做数据验证的时候,我们通常不会直接在Controller中的Action里面做,提供几个开源的工具和几篇文章:

(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中的控件数据的时候,绑定顺序为:ModelState集合、指定值、ViewData内的数据、View中强类型Model对象对应属性的数据。

PS:

 

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 数据验证

那不会出现问题。因为当传递的是类或者参数时,默认的ModelBinder会根据键值填充ModelState集合,就像UpdateModel会帮你做这件事情一样。

热推产品

  • ActiveReport... 强大的.NET报表设计、浏览、打印、转换控件,可以同时用于WindowsForms谀坔攀戀Forms平台下......
  • AnyChart AnyChart使你可以创建出绚丽的交互式的Flash和HTML5的图表和仪表控件。可以用于仪表盘的创......
首页 | 新闻中心 | 产品中心 | 技术文档 | 友情连接 | 关于磐岩 | 技术支持中心 | 联系我们 | 帮助中心 Copyright-2006 ComponentCN.com all rights reserved.重庆磐岩科技有限公司(控件中国网) 版权所有 电话:023 - 67870900 传真:023 - 67870270 产品咨询:sales@componentcn.com 渝ICP备12000264号 法律顾问:元炳律师事务所 重庆市江北区塔坪36号维丰创意绿苑A座28-5 邮编:400020
在线客服
在线客服系统
在线客服
在线客服系统