【原文地址】ASP.NET MVC 2: Model Validation
【原文发表日期】 Friday, January 15, 2010 4:14 AM
【除了写博客外,我现在还使用Twitter发短贴和共享链接。请通过twitter.com/scottgu跟随我。】
这是我针对即将发布的ASP.NET MVC 2所撰写的贴子系列的第二篇,这个博客贴子将讨论 ASP.NET MVC 2中一些验证方面的改进。
ASP.NET MVC 2 验证
对用户输入的验证以及强制业务规则/逻辑是大多数web应用的核心需求。ASP.NET MVC 2包含了一堆新的特性,显著地简化了对用户输入的验证以及在模型/视图模型中对验证逻辑的强行实施。这些特性是这样设计的,验证逻辑总是在服务器上执行的,也可以选择在客户端通过JavaScript来执行。ASP.NET MVC 2中的验证设施和特性这般设计,以便:
1) 开发人员可以轻易地利用内置于.NET框架中的DataAnnotation验证支持。DataAnnotation提供了一个非常简便的方式,使用最少的代码在对象和属性上用声明的方式添加验证规则。
2) 开发人员可以集成他们自己的验证引擎,或者利用现有的验证框架,象Castle验证器或EntLib验证库。ASP.NET MVC 2的验证特性是设计来在利用新的 ASP.NET MVC 2的验证设施(包括客户端验证,模型绑定验证等等)的同时,简化任何类型的验证架构的插入的。
这意味着,在常见的应用场景中启用验证是极其容易的,同时对更高级的场景则还能保持极好的灵活性。
使用ASP.NET MVC 2 和 DataAnnotation来启用验证
让我们在ASP.NET MVC 2中来全程示范一个简单的CRUD场景,利用新的内置DataAnnotation验证支持。具体来说,让我们来实现一个“Create”表单来允许用户输入朋友的数据:
我们想要确保在保存到数据库之前,输入的信息是合法的,如果不合法,就显示合适的错误消息:
我 们想要使得这个验证同时在服务器端和客户端(通过 JavaScript)发生。我们还想要确保我们的代码遵守DRY原则(Don't Repeat Yourself,不重复自己),意味着我们应该只在一处实施验证规则,然后使得我们的控制器,action方法和视图来兑现这个承诺。
在下面,我将使用VS 2010,用ASP.NET MVC 2来实现上面的场景。你也可以使用VS 2008及ASP.NET MVC 2来实现完全一样的场景。
第一步: 实现FriendsController (一开始没有验证)
我们首先在一个新的ASP.NET MVC 2项目中加一个简单的“Person”类,象下面这样:
它有四个属性(是用C#的自动属性支持实现的, 在VS 2010中VB也支持自动属性了,哎!)。
然后在项目中加一个 “FriendsController” 控制器类,呈示2个 “Create” action方法。第一个action方法是在对/Friends/Create URL的HTTP-GET请求进来时调用的,它会显示一个空白的表单,用来输入个人数据。第二个action方法是在对/Friends/Create URL的HTTP-POST请求进来时调用的。它会将提交的表单输入映射到一个Person对象,核实没有绑定错误发生,如果是合法的,最终会将数据保存 到数据库中去(在本教程的后面我们会实现相关的数据库工作)。如果提交的表单输入是不合法的,该action方法会重新显示带有错误的表单:
在实现了控制器之后,可以在Visual Studio中在其中一个action方法中右击,选择 “添加视图”命令, 这会调出 “添加视图” 对话框。选择自动生成传入对象为Person的“Create”视图:
然后Visual Studio会在我们项目的\Views\Friends\目录中,生成一个含有框架代码(scaffolded)的Create.aspx视图文件。注意下面,它利用了ASP.NET MVC 2中新的强类型HTML辅助方法(促成了更好的intellisense和编译时检查支持):
现在,当我们运行该应用,访问 /Friends/Create URL时,我们将得到一张可以输入数据的空白表单:
但,因为我们还没有在应用中实现任何验证,谁也无法阻止我们在表单中键入假的输入,将其提交到服务器去。
第二步: 使用DataAnnotation来启用验证
现在,让我们来更新应用,执行一些基本的输入验证规则。我们将在我们的Person模型对象上实现这些规则,而不是在控制器或视图中实现。在Person对象上实现这些规则的好处是,这将确保这些验证在应用中任何使用Person对象的场景中都会被执行(例如,如果后来添加了编辑场景的话)。这将帮助确保我们将代码保持DRY,避免在多处重复这些规则。
ASP.NET MVC 2 允许开发人员轻松地在模型或视图模型类上添加声明式验证特性,然后ASP.NET MVC在应用中实施模型绑定操作时,这些验证规则就会被自动执行。为看其例子,让我们更新Person类,在其中加几个验证特性。这么做,在文件的顶部加 一个对“System.ComponentModel.DataAnnotations”命名空间的 “using” 语句,然后在Person的属性上饰于[Required], [StringLength], [Range], 和 [RegularExpression] 验证特性(这几个特性都是在那个命名空间中实现的):
注: 在上面我们明式指定了错误信息字符串,你也可以在资源文件中定义它们,或者按进来的用户的语言/文化做本地化,你可以在这里了解如何本地化验证错误消息。
既然我们加了验证特性到Person类上,让我们来重新运行我们的应用,看在键入假的数值,将其提交回服务器时会发生什么:
注意上面我们的应用现在有一个蛮好的出错体验了。带不合法输入的文本元素以红色高亮显示,我们指定的验证错误消息也显示给了用户。表单还保留用户原先输入的数据,这样他们不用重新填写什么。但,你也许会问,怎么会是这样?
要理解这个行为,让我们看一下处理我们表单的POST场景的Create action方法:
在 我们的HTML表单被提交回服务器时,上面的方法就会被调用。因为该action方法接受一个“Person” 对象为参数,ASP.NET MVC会创建一个Person对象,自动地将进来的表单输入数值映射到该对象上。作为该过程的一部分,ASP.NET MVC还会检查该Person对象上的DataAnnotation验证特性是否合法。如果一切都合法,那么我们代码中的 ModelState.IsValid检查就会返回真值,在这种情形下,我们(最终)将把该Person对象保存到数据库中,然后重新定向回到主页上去。
但如果Person对象上有任何验证错误的话,我们的action方法就会以该不合法Person对象的数据重新显示表单,这是通过上面代码片段中最后一行代码实现的。
然 后,错误消息就会显示在我们的视图中,因为我们的Create表单在每一个<%= Html.TextBoxFor() %>辅助方法的调用旁边都有一个<%= Html.ValidationMessageFor() %>辅助方法调用。Html.ValidationMessageFor() 辅助方法会针对传入视图的任何不合法的模型属性输出合适的错误消息:
这个模式/方式有一个好处,就是非常容易配置,它还允许我们轻松地添加或改变我们Person类上的验证规则,而不必改变控制器或视图中的任何代码。这个在一个地方指定验证规则,然后在所有的地方都会被承诺和遵守的能力,允许我们以最少的努力快速地发展我们的应用和规则,并且将代码保持在非常DRY的程度。
第三步: 启用客户端验证
目前我们的应用只能做服务器端的验证,这意味着我们的终端用户需要将表单提交到服务器才能看到任何验证错误消息。
ASP.NET MVC 2的验证架构中一样非常酷的东西是,它同时支持服务器端 和 客户端验证。为启用这个功能,我们要做的就是在视图中添加2个 JavaScript引用,编写一行代码:
在我们添加了这三行后,ASP.NET MVC 2 就会使用我们加到Person类上的验证元数据,为我们连接好客户端JavaScript验证逻辑。这意味着,当用户使用tab键跳出一个不合法的输入元素时,就会得到瞬时的验证错误。
要在我们的朋友应用中看客户端JavaScript支持的实战例子的话,让我们重新运行应用,在前三个文本框中填入合法的数值,然后尝试点击“Create(创建)”。注意,我们不必访问服务器就会得到遗漏值的瞬时错误消息:
如果我们输入一些不是合法的email的字符话,错误消息就会瞬时从“Email Required (Email是个必需值)” 变为 “Not a valid email (email不合法)”(这是我们将规则加到Person类上时指定的错误消息):
在输入一个合法的email时,错误消息就是瞬时消失,文本框背景色也会恢复到正常的状态:
好事是,我们不必编写自己的任何定制JavaScript就能启用上面的验证逻辑。我们的验证代码还是那么DRY,我们可以在一个地方指定规则,然后在整个应用中得到执行,同时在客户端和服务器端。
注意,为安全的原因,服务器端验证规则总是执行的,即时你启用了客户端支持。这避免黑客尝试绕过客户端规则,哄骗攻击(spoof)你的服务器。
ASP.NET MVC 2中的客户端JavaScript验证支持可与你在ASP.NET MVC应用中使用的任何验证框架/引擎协作,它并不要求你使用 DataAnnotation 验证方式,所有的基础设施是独立于 DataAnnotation的,可以与Castle验证器, EntLib验证应用块,或你选择使用的任何定制验证方案协作使用。
如果你不想使用我们的客户端JavaScript文件,你也可以将其替换成jQuery验证插件,而使用那个库。 ASP.NET MVC Futures下载还包括针对ASP.NET MVC 2服务器端验证框架启用jQuery验证的支持。
第四步: 创建自定义的[Email]验证特性
.NET 框架中的System.ComponentModel.DataAnnotations命名空间包括了众多可为你所用的内置验证特性。我们在上面的例子中 使用了其中的四个:[Required], [StringLength], [Range], 和 [RegularExpression]。
你 也可以定义自己的定制验证特性,然后应用它们。你可以通过继承自System.ComponentModel.DataAnnotations命名空间中 的ValidationAttribute基类,定义完全定制的特性。或者,你也可以选择继承自任何现有的验证特性,如果你只想要扩展它们的基本功能的 话。
例如,为帮助清理我们Person类中的代码,我们也许想要创建一个新的[Email]验证特性,将检查合法email的正则表达 式封装起来。要这么做的话,我们只要象这样继承自RegularExpressionAttribute基类,然后用合适的email正则表达式调用 RegularExpressionAttribute基类的构造器:
然后将Person类更新成使用我们新的[Email]验证属性,换掉我们先前使用的正则表达式,这使得我们的代码更干净,封装也更好:
在创建定制的验证特性时,你还可以指定在服务器端以及在客户端通过JavaScript执行的验证逻辑。
除 了创建可施用于对象上个别属性的验证特性外,你还可以将验证特性施用于类的层次,这允许你对一个对象中的多个属性实施验证逻辑。要看实战例子的话,你可以 参阅包含在默认ASP.NET MVC 2应用项目模板中AccountModels.cs/vb文件中的“PropertiesMustMatchAttribute” 定制特性(在VS 2010中做 文件->新ASP.NET MVC 2 Web项目,然后查询该类)。
第五步: 持久化到数据库中
现在让我们来实现将朋友数据保存到数据库所需的逻辑。
至 此,我们只用了平白的(plain-old)C#类(有时称为“POCO” 类, 即 “plain old CLR (or C#) object”)。我们可以使用的一个方案是,编写一些单独的持久代码,将这我们已经编写好的现有类映射到数据库去。目前象NHibernate这样的对 象关系映射(Object relational mapping - ORM)方案非常地好支持这样的POCO/PI风格的映射。随.NET 4发布的ADO.NET实体框架(Entity Framework - EF)也支持POCO / PI映射,而且就象NHibernate,EF也能启用以“只用代码(code only)”的方式(没有映射文件,也不需要设计器)定义持久性映射的能力。
如果我们的Person对象以这种方式映射到数据库的话,我们不用对Person类做任何改动,也不用改动任何验证规则,它还会继续完好地工作。
但假如我们要使用图形工具来做ORM映射的话,怎么办?
今天使用Visual Studio的许多开发人员并不编写他们自己的ORM映射/持久逻辑,而是使用Visual Studio中内置的设计器来帮助管理这样的映射逻辑。
使 用DataAnnotation(或者任何其他形式的基于特性的验证)时一个经常问起的问题是,“如果你手头的模型对象是由GUI设计器创建/维护的话, 你该如何施用这些特性?”。例如,假如与类似我们至此为止一直在使用的POCO风格的Person类不同,我们而是在Visual Studio中通过象LINQ to SQL 或 ADO.NET EF设计器这样的GUI映射工具定义/维护我们的Person类的话,该怎么办呢:
上 面是一张屏幕截图,展示了在VS 2010中使用ADO.NET EF设计器定义的一个Person类。上方的窗口定义了Person类,下方的窗口展示了该类的属性是如何映射到数据库中的“People”表的映射编辑 器。当你在设计器上点击保存时,它会自动为你在项目中生成一个Person类。这很棒,但每次你做了改动,点击保存时,它就会重新生成 Person 类,这会导致你在对象上面声明的任何验证特性的丢失。
将 额外的基于特性的元数据(象验证特性)施加到由VS设计器自动生成/维护的类的一个方法是,采用一个我们称之为“伙伴类(buddy classes)”的技术。基本上来说,你创建另外一个类,包含你的验证特性和元数据,然后通过将 “MetadataType”特性施加到一个与工具生成的类一起编译的partial类上,将其与由设计器生成的类连接起来。例如,如果我们想要将我们前 面用到的验证规则施加到由LINQ to SQL 或 ADO.NET EF设计器维护的Person类上,我们可以更新我们的验证代码,使其存在于一个单独的“Person_Validation”类上,使用象下面这样的代 码将其连接到由VS创建的“Person”类上:
上面的做法没有纯粹的POCO方法那么优雅,但其好处是,可以用于Visual Studio中任何工具或设计器生成的代码。
最后一步 – 将Friend保存到数据库中
最后一步,不管是否采用了POCO或工具生成的Person类,是将合法的朋友数据保存到数据库中去。
这只要求我们用三行代码将FriendsControlle类中的 “Todo”占位语句替换掉,这三行代码将新朋友保存到数据库。下面是整个FriendsController类的完整代码(使用了ADO.NET EF做数据库持久化):
现在,当我们访问 /Friends/Create URL时,我们可以轻松地添加新人到我们的朋友数据库中去:
对所有数据的验证都是同时在客户端和服务器端执行的。我们可以轻易地在一个地方添加/修改/删除验证规则,而由整个应用中的所有的控制器和视图来执行这些规则。
结语
ASP.NET MVC 2极大地简化了web应用的验证集成。它倡议一种基于模型的验证方式,允许你将你的应用保持DRY,帮助确保验证规则在整个应用中保持一致。 ASP.NET MVC 2中内置的DataAnnotation支持,原本就使得对常见的验证场景的支持非常容易。而且,ASP.NET MVC 2验证设施中的扩展性支持允许你支持更大范围的更高级的验证场景,可以插入任何现有的或者定制的验证框架/引擎。
希望本文对你有所帮助。