原文:通过扩展改善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. jquery经常使用事件(整理)

    Jquery事件 (一).事件列表. 1.blur() 当失去焦点时触发.包含鼠标点击离开和TAB键离开. 2.change() 当元素获取焦点后,值改变失去焦点事触发. 3.click() 当鼠标单 ...

  2. 于linux已安装moodle

    本文介绍了两个虚拟机的安装linux server 及相关服务,随着后这些基础.安装应用程序服务 moodle 2.7+  它是使用最广泛的平台,网络课程. 在安装过程中moodle之前,需要支持软件 ...

  3. zigbee、profile、cluster、 endpoint、

    1.引用ZigBee联盟的说法 Profile: a collection of device descriptions, which together form a cooperative appl ...

  4. 前端javascript模板

    doT.js——前端javascript模板引擎问题备忘录 我手里维护的一个项目,遇到一个问题:原项目的开发人员在Javascript中,大量的拼接HTML,导致代码极丑,极难维护.他们怎么能够忍受的 ...

  5. window.open的小技巧分享(转)

    今天再次谈起window.open是因为发现了一个比较好玩的小技巧,详细内容我们稍后详细说明.       聊到window.open,不得不说明一下他的使用方法,主要有两种形式:   window. ...

  6. [ExtJS5学习笔记]第22 Extjs5正在使用beforeLabelTpl添加所需的配置选项标注星号标记

    本文地址:http://blog.csdn.net/sushengmiyan/article/details/39395753 官方样例:http://docs.sencha.com/extjs/5. ...

  7. 谈论高并发(十二)分析java.util.concurrent.atomic.AtomicStampedReference看看如何解决源代码CAS的ABA问题

    于谈论高并发(十一)几个自旋锁的实现(五岁以下儿童)中使用了java.util.concurrent.atomic.AtomicStampedReference原子变量指向工作队列的队尾,为何使用At ...

  8. 【web开发学习笔记】Structs2 Action学习笔记(两)

    action学习笔记2-大约action method讨论 Action运行的时候并不一定要运行execute方法,能够在配置文件里配置Action的时候用method=来指定运行哪个方法 也能够在u ...

  9. Gradle增量学习建筑

    请在本系列下面的文章下载Github演示示例代码: git clone https://github.com/davenkin/gradle-learning.git       假设我们Gradle ...

  10. NSIS:静默释放文件并运行 制作绿色单文件软件

    原文 NSIS:静默释放文件并运行 制作绿色单文件软件 现在所谓的绿色单文件软件,大多与以下代码原理相似:把软件运行需要的文件封装为一个EXE文件,双击时释放到某个目录(大多是TEMP)并运行主程序文 ...