原文:通过扩展改善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. C该结构变化 struct typedef

     这几天看代码,看到若干类型的结构,例如下列结构声明: struct    book{ string name; int price; int num; }; 此种结构定义结构变量的格式例如以下: ...

  2. oracle 解锁scott账户

    安装oracle数据库的时候忘了解锁账户,再加上一直配置不成功,链接不上,最后终于配置好了,发现没账户可用,oracle好难. oracle版本是 Oracle Database 11g Enterp ...

  3. Java学习路径:不走弯路,这是一条捷径

    1.如何学习编程? JAVA是一种平台.也是一种程序设计语言,怎样学好程序设计不只适用于JAVA,对C++等其它程序设计语言也一样管用.有编程高手觉得,JAVA也好C也好没什么分别,拿来就用.为什么他 ...

  4. QlikView一年计算,以最新的销售数据

    总销量的新财年后年初今天是非常需要的学生经常会遇到,有两种思路: 1. 能Load当数据是生成一个称为场YTDFlag.这是本财年的时刻,本场会1,除此以外,0.因此,在报告中可使用非常方便Sum(S ...

  5. 解决android模块化升级方法

    有关本机android升级版本必须是全apk更新安装,我们无法实现的一些模块化升级的解决思路: 本地人+web混合动力APP~ 查询详情,我们必须实现模块化升级,无论使用方法,我这样做.首页写在每个功 ...

  6. The C5 Generic Collection Library for C# and CLI

    The C5 Generic Collection Library for C# and CLI https://github.com/sestoft/C5/ The C5 Generic Colle ...

  7. Git现实(一个)版本控制概述

    从今天开始.我们了解的分布式版本控制系统Git相关内容.了解Git之前,我们的第一个版本控制系统,使宏观的描述. 什么是版本号控制 版本号控制是指通过对文件内容的变化进行记录,并为每次的变化进行编号, ...

  8. 原生js 样式的操作整理

    内联样式的获取 function getStyle(obj,attr){//简单的获取内联样式 return obj.currentStyle?obj.currentStyle[attr]:obj.g ...

  9. SharePoint 2013 中将 HTML文件转换为母版页

    原文:SharePoint 2013 中将 HTML文件转换为母版页 SharePoint 2013提供了很多新功能,下面我们看看将Html页面,转换为母版页的功能.这个功能更加方便设计人员设计母版页 ...

  10. Json.Net6.0入门学习试水篇

    原文:Json.Net6.0入门学习试水篇 前言 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.简单地说,JSON 可以将 JavaScript 对象中 ...