原文:通过扩展改善ASP.NET MVC的验证机制[使用篇]

ASP.NET MVC提供一种基于元数据的验证方式是我们可以将相应的验证特性应用到作为Model实体的类型或者属性/字段上,但是这依然具有很多的不足。在这篇文章中,我结合EntLib的VAB(Validation Application Block)的一些思想通过扩展为ASP.NET MVC提供一种更为完善的验证机制。[源代码从这里下载]

目录:
一、扩展旨在解决怎样的验证问题
二、一个简单的消息维护组件
三、多语言的支持
四、基于某个验证规则的验证
五、验证规则的一致性

一、扩展旨在解决怎样的验证问题

这个基于验证的扩展可以实现如下几个ASP.NET MVC无法实现验证问题:

  • 消息提供机制的分离:目前我们可以通过“硬编码”和“资源文件”两种验证错误消息的提供机制,但是如果能够提供一种独立的机制来提供验证的错误消息无疑是一种更好的选择。原因很简单,验证消息是呈现给最终的用户的,应该是可以单独进行维护的,当我们发现某个验证消息不够友好,应该以一种对现有应用毫无影响的方式进行修改。此外,消息的定义最好是基于“模板”,模板中定义相应的占位符,这样可以省去很多冗余消息的定义。比如对于某个区间的验证消息就可以定义成“{0}必须在{1}与{2}之间”;
  • 多语言的支持:和ASP.NET MVC基于资源文件(所有的ValidationAttribute可以通过指定属性Name和ResourceType使我们可以在资源文件中定义相应的消息)不同,消息模板对多语言的支持可以通过独立的消息维护组件/框架来解决,但是我们需要解决用于替换占位符的参数的多语言支持;
  • 多验证规则的支持:对于同一个实体对象,在不同的场景中具有不同的验证规则。比如说我们做一个招聘网站,针对不同工作岗位对应聘者的性别、年龄、学历、身高和体重等属性的要求都是不一样的,所以我们应该针对基于工作岗位的验证场景定义不同的验证规则,并针对某个具体的验证规则对实体对象实施验证。

二、一个简单的消息维护组件

为了演示消息提供机制的分离,我们定义了一个简单的消息维护组件MessageManager。如下面的代码所示,抽象类MessageManager具有唯一的FormatMessage方法用于获取一个经过格式化好的最终消息文本,参数category、id和args分别代表对应消息条目的类型、ID和作为替换占位符的参数。

   1: public abstract  class MessageManager

   2: {

   3:     public abstract string FormatMessage(string category, string id, params object[] args);

   4: }

我们定义了如下一个默认的DefaultMessageManager,它维护了一组代表消息条目的MessageEntry列表,而MessageEntry是支持多语言的。在重写的FormatMessage方法中,直接通过类型和ID在列表中找到相应的MessageEntry,并传输占位符参数根据当前线程的CurrentUICulture对消息文本进行格式。从如下的代码可以看出,我们仅仅定义了一个表示“必需字段”的消息,在en-US和zh-CN这两种语言文化下的文本分别是“{0} is mandatory!”和“请输入{0}!”。该MessageEntry得类型和ID分别是“Validation”和“MandatoryField”。

   1: public class DefaultMessageManager : MessageManager

   2: {

   3:     public DefaultMessageManager()

   4:     {

   5:         var messages = new List<MessageEntry>();

   6:         var messageEntry = new MessageEntry("Validation", "MandatoryField");

   7:         messageEntry.AddMessageText("{0} is mandatory!", new CultureInfo("en-US"));

   8:         messageEntry.AddMessageText("请输入{0}!", new CultureInfo("zh-CN"));

   9:         messages.Add(messageEntry);

  10:         this.Messages = messages;

  11:     }

  12:  

  13:     public IEnumerable<MessageEntry> Messages { get; private set; } 

  14:     public override string FormatMessage(string category, string id, params object[] args)

  15:     {

  16:         MessageEntry messageEntry = (from message in this.Messages

  17:                                      where message.Category == category && message.Id == id

  18:                                      select message).FirstOrDefault();

  19:         if (null == messageEntry)

  20:         {

  21:             throw new Exception("...");

  22:         }

  23:  

  24:         return messageEntry.Format(args);

  25:     }

  26: }

我们并没有列出MessageEntry的定义,有兴趣的朋友可以下载本例的源代码。最终我们定义了如下静态工厂MessageManagerFactory来创建相应的MessageManager,简单起见,我们直接创建上述的DefaultMessageManager。

   1: public static class MessageManagerFactory

   2: {

   3:     public static MessageManager GetMessageManager()

   4:     {

   5:         return new DefaultMessageManager();

   6:     }

   7: }

三、多语言的支持

在本篇文章中我们不谈具体实现,只谈具体的使用方法。我们以登录场景为例,如下所示的LoginInfo类型表示包含代表用户名和密码的Model类型。应用在属性上的RequiredValidatorAttribute特性是我们自定义的ValidationAttribute,它实现了RequiredAttribute一样的验证功能。以应用在UserName属性上的RequiredValidatorAttribute为例([RequiredValidator("Validation", "MandatoryField", "用户名", Name = "RequiredValidator", Culture = "zh-CN")]),构造函数参数分别代表通过MessageManager维护的对应消息条目的类型(Validation)、ID(MandatoryField)以及占位符参数(用户名)。Culture属性则代表对应的语言文化,如果没有对该属性进行显式指定,则代表“语言文化中性”的验证器。

   1: public class LoginInfo

   2: {

   3:     [Display(ResourceType = typeof(Resources), Name = "UserName")]

   4:     [RequiredValidator("Validation", "MandatoryField", "User Name")]

   5:     [RequiredValidator("Validation", "MandatoryField", "用户名", Culture = "zh-CN")]

   6:     public string UserName { get; set; }

   7:  

   8:     [RequiredValidator("Validation", "MandatoryField", "Password")]

   9:     [RequiredValidator("Validation", "MandatoryField", "密码", Culture = "zh-CN")]

  10:     [DataType(DataType.Password)]

  11:     [Display(ResourceType = typeof(Resources), Name = "Password")]

  12:     public string Password { get; set; }

  13: }

在进行验证器的选择的过程中,总是会根据当前线程的CurrentUICulture选择相匹配的验证器。如果找不到完全匹配的验证器,则会选择语言文化中性验证器(这样的验证器只允许有一个)。对于本例来说,如果当前的语言文化为zh-CN,那么只有应用在UserName和Password属性上Culture属性为zh-CN的RequiredValidatorAttribute有效,而在其他的语言文化环境中则会选择没有对Culture属性进行显式设置的RequiredValidatorAttribute。我们来看看用于进行用户登录的AccountController的定义:

   1: public class AccountController : BaseController

   2:     {

   3:         public ActionResult SignIn()

   4:         {

   5:             return View(new LoginInfo());

   6:         }

   7:         [HttpPost]

   8:         public ActionResult SignIn(LoginInfo logInfo)

   9:         {

  10:             if (ModelState.IsValid)

  11:             {

  12:                 return this.View();

  13:             }

  14:             else

  15:             {

  16:                 return this.View();

  17:             }

  18:         }

  19:     }

下面是SignIn操作默认的View的所有内容:

   1: @using Artech.Mvc.Validation.Properties

   2: @using Artech.Mvc.Validation.Models

   3: @model LoginInfo

   4:  

   5: @{

   6:     ViewBag.Title = "SignIn";

   7: }

   8: @Html.ValidationSummary()

   9: @using(Html.BeginForm())

  10: {

  11:     @Html.EditorForModel()

  12:     <input type="submit" value="@Resources.SignIn"/>

  13: }

在我们的例子中语言的设置是通过URL来体现的,为了我们在Global.asax中进行了如下的路由映射,即controller之前的部分代表语言文化代码,默认为zh-CN。

   1: public class MvcApplication : System.Web.HttpApplication

   2: {

   3:     public static void RegisterGlobalFilters(GlobalFilterCollection filters)

   4:     {

   5:         filters.Add(new HandleErrorAttribute());

   6:     }

   7:  

   8:     public static void RegisterRoutes(RouteCollection routes)

   9:     {

  10:          //...

  11:          routes.MapRoute(

  12:             "Default", // Route name

  13:             "{culture}/{controller}/{action}/{id}", // URL with parameters

  14:             new {culture="zh-CN", controller = "Account", action = "SignIn", id = UrlParameter.Optional } // Parameter defaults

  15:         );

  16:  

  17:     }

  18:  

  19:     protected void Application_Start()

  20:     {   

  21:         //...

  22:         RegisterRoutes(RouteTable.Routes);

  23:     }

  24: }

运行我们的程序并在分别以en-US和zh-CN访问主页,在没有输入用户名和密码的情况下将会得到如下的验证消息。

四、基于某个验证规则的验证

现在我们来演示基于某个验证规则的验证方式。对于登录,我们都应该有这样的体会,在开发阶段为了测试的时候避免频繁地输入用户名和密码,我们会设置一个默认的密码。在这里我们可以通过定义验证规则来屏蔽对密码的验证。为此我们我们对应用在LoginInfo的Password属性上的RequiredValidatorAttribute特性稍加改动,对其RuleName属性进行了显式设置(RuleName = "Production"),意味着只有当前验证规则为“Production”(产品阶段)的时候,基于它们的验证才会生效。

   1: public class LoginInfo

   2: {

   3:     //...

   4:     [RequiredValidator("Validation", "MandatoryField", "Password",RuleName = "Production")]

   5:     [RequiredValidator("Validation", "MandatoryField", "密码", Culture = "zh-CN", RuleName = "Production")]

   6:     [DataType(DataType.Password)]

   7:     [Display(ResourceType = typeof(Resources), Name = "Password")]

   8:     public string Password { get; set; }

   9: }

而对当前采用怎样地验证规则,则可以在Controller或者Action方法上应用我们自定义的RuleNameAttribute来设定。如下面的代码片断所示,我们在AccountController上直接应用了RuleNameAttribute特性并将当前的验证规则设置为“Dev”(开发阶段)。

   1: [ValidationRule("Dev")]

   2: public class AccountController : BaseController

   3: {

   4:     //...

   5: }

那么在程序运行的时候就不会对密码进行任何验证,这可以通过如下的截图可以看出来:

如果我们通过应用在AccountController上的RuleNameAttribute将验证规则设置为“Production”

   1: [ValidationRule("Production")]

   2: public class AccountController : BaseController

   3: {

   4:     //...

   5: }

那么针对密码的验证就会生效了:

五、验证规则的一致性

值得一提的是:我们扩展的验证体系依然也为客户端认证提供支持,但是在进行基于验证规则的验证是确有一个小小的机关。同样以AccountController的两个SignIn操作为例,进行客户端验证的规则是基于第一个SignIn操作(HttpGet)生成的,服务端验证则是基于第二个SignIn操作(HttpPost)的验证规则进行的,如果我们将RuleNameAttribute应用到两个SignIn操作上,比如确保它们的规则名称一致方能保证客户端验证和服务端认证的一致性。

   1: public class AccountController : BaseController

   2: {

   3:     [ValidationRule("Production")]

   4:     public ActionResult SignIn()

   5:     {

   6:         //...

   7:     }

   8:     [HttpPost]

   9:     [ValidationRule("Production")]

  10:     public ActionResult SignIn(LoginInfo logInfo)

  11:     {

  12:          //...

  13:     }

  14: }

通过扩展改善ASP.NET MVC的验证机制[使用篇]的更多相关文章

  1. 通过扩展改善ASP.NET MVC的验证机制[实现篇]

    原文:通过扩展改善ASP.NET MVC的验证机制[实现篇] 在<使用篇>中我们谈到扩展的验证编程方式,并且演示了本解决方案的三大特性:消息提供机制的分离.多语言的支持和多验证规则的支持, ...

  2. 【MVC】ASP.NET MVC Forms验证机制

    http://www.cnblogs.com/bomo/p/3309766.html 随笔 - 121  文章 - 0  评论 - 92 [MVC]ASP.NET MVC Forms验证机制 ASP. ...

  3. ASP.NET MVC Forms验证机制

    ASP.NET MVC 3 使用Forms身份验证 身份验证流程 一.用户登录 1.验证表单:ModelState.IsValid 2.验证用户名和密码:通过查询数据库验证 3.如果用户名和密码正确, ...

  4. ASP.NET MVC 5 - 验证编辑方法(Edit method)和编辑视图(Edit view)

    在本节中,您将验证电影控制器生成的编辑方法(Edit action methods)和视图.但是首先将修改点代码,使得发布日期属性(ReleaseDate)看上去更好.打开Models \ Movie ...

  5. ASP.NET MVC 2 验证

    来源:http://www.cnblogs.com/jhxk/articles/2612885.html  只为把自己觉的好的存起来 对用户输入的验证以及强制业务规则/逻辑是大多数web应用的核心需求 ...

  6. Asp.Net MVC 身份验证-Forms

    Asp.Net MVC 身份验证-Forms 在MVC中对于需要登录才可以访问的页面,只需要在对应的Controller或Action上添加特性[Authorize]就可以限制非登录用户访问该页面.那 ...

  7. ASP.NET MVC异步验证是如何工作的03,jquery.validate.unobtrusive.js是如何工作的

    在上一篇"ASP.NET MVC异步验证是如何工作的02,异步验证表单元素的创建"中了解了ASP.NET异步验证是如何创建表单元素的,本篇体验jquery.validate.uno ...

  8. [转]ASP.NET MVC 5 - 验证编辑方法(Edit method)和编辑视图(Edit view)

    在本节中,您将验证电影控制器生成的编辑方法(Edit action methods)和视图.但是首先将修改点代码,使得发布日期属性(ReleaseDate)看上去更好.打开Models \ Movie ...

  9. ASP.NET MVC Model验证(五)

    ASP.NET MVC Model验证(五) 前言 上篇主要讲解ModelValidatorProvider 和ModelValidator两种类型的自定义实现, 然而在MVC框架中还给我们提供了其它 ...

随机推荐

  1. Android于JNI调用列出的程序

    1.安装和下载cygwin,下载Android NDK: 2.于ndk工程JNI接口设计: 3.采用C/C++实现本地方法. 4.JNI生成动态链接库.so档: 5.动态链接库副本javaprojec ...

  2. HDU 2460 Network(双连通+树链剖分+线段树)

    HDU 2460 Network 题目链接 题意:给定一个无向图,问每次增加一条边,问个图中还剩多少桥 思路:先双连通缩点,然后形成一棵树,每次增加一条边,相当于询问这两点路径上有多少条边,这个用树链 ...

  3. Gradle 载入中 Android 下一个.so档

    1.在project下新建 jni/libs  目录   . jni 是和原来的libs  同级 ,将全部的.so文件放入 新建的libs文件下 2.在build.gradle 文件里新增下面内容到a ...

  4. C、C++用指针引用的差异

    1:并引述之间的区别在概念的指针 参考是可变的别名.例如 int m; int &n=m; 引用作为一个别名.它在逻辑上不是独立的.它的存在具有依附性.所以引用必须在一開始就被初始化.并且其引 ...

  5. 我的java学习笔记(一)

    第一个java程序,还是熟悉的hello world public class FirstSample { //类 public static void main(String[] args) //方 ...

  6. mount命令使用具体解释(Linux)

    linux是一个优秀的开放源代码的操作系统,能够执行在大到巨型小到掌上型各类计算机系统上,随着 linux系统的日渐成熟和稳定以及它开放源代码特有的优越性,linux在全世界得到了越来越广泛的应用. ...

  7. SharePoint 2013 母版页修改后,无法添加应用程序

    原文:SharePoint 2013 母版页修改后,无法添加应用程序 问题描述:前一段时间尝试了一下将HTML文件转换为母版页,但是,用着用着又发现新的问题,我们转换的母版页,设置成默认母版页以后,无 ...

  8. cocos2dx 制作单机麻将(二)

    cocos2dx 制作单机麻将(二) 打乱麻将顺序2 前面解说了怎样打乱初始给定的麻将牌堆, 另一种是打乱随意给定的麻将牌堆 //混乱扑克2 void RandAppointCardData(BYTE ...

  9. 概率图形模型(PGM)学习笔记(四)-贝叶斯网络-伯努利贝叶斯-贝叶斯多项式

    之前忘记强调重要的差异:链式法则的条件概率和贝叶斯网络的链式法则之间的差异 条件概率链式法则 P\left({D,I,G,S,L} \right) = P\left( D \right)P\left( ...

  10. JAVA学习第五十九课 — 网络编程概述

    网络模型 OSI(Open System Interconnection)开放系统互连:參考模型 TCP/IP 网络通讯要素 IP地址 port号 传输协议 网络參考模型 七层OSI模型的基本概念要了 ...