原文:通过扩展改善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. 开源 自由 java CMS - FreeCMS2.0 举APP产生信息数据

    项目地址:http://www.freeteam.cn/ 生成信息数据 生成当前管理网站下同意移动APP訪问的栏目的信息页面. 从左側管理菜单点击生成信息数据进入. 您能够选择须要生成的栏目,然后点击 ...

  2. 《Lua游戏开发实践指南》读后感

    书籍地址:http://book.douban.com/subject/20392269/ 一句话点评该书:想用Lua作游戏脚本开发的同学值得一读! (一)本书特点 市面专门讲Lua的中文书籍非常少, ...

  3. SQL于union, EXCEPT 和 INTERSECT用法

    这三个放在一起是有道理的,因为它们运行两个或两个以上的结果集,而这些结果对例如设置以下限制: 列的数目和所有查询必须是相同的列顺序.  数据类型必须兼容.  而且它们都是处理于多个结果集中有反复数据的 ...

  4. 警告: git command could not be found. Please create an alias or add it to yo

    5 Answers active answertab=oldest#tab-top" title="Answers in the order they were provided& ...

  5. 批处理命令行CMD启动停止重启IIS的命令

    原文:批处理命令行CMD启动停止重启IIS的命令 启动IIS: net start iisadmin    (IIS的整个服务) net start w3svc       (WWW网页WEB服务) ...

  6. zju2676 Network Wars 分数规划+网络流

    题意:给定无向图,每条边有权值,求该图的一个割集,是的该割集的平均边权最小 Amber的<最小割模型在信息学竞赛中的应用>中讲的很清楚了. 二分答案k,对每条边进行重新赋值为原边权-k,求 ...

  7. cidaemon.exe是什么进程及怎样关闭cidaemon.exe进程

    问题描写叙述: 这段时间机器总是出现一个奇怪的问题:cidaemon.exe进程占用CUP率98%以上,大大影响了电脑的正常使用.资源管理器中出现多个cidaemon.exe进程,强制结束占用cpu率 ...

  8. oracle HA 高可用性具体解释(之中的一个)

    oracle HA 高可用性具体解释(之二,深入解析TAF,以及HA框架) :http://blog.csdn.net/panfelix/article/details/38436197 一.HA F ...

  9. 【转】angularjs指令中的compile与link函数详解

    这篇文章主要介绍了angularjs指令中的compile与link函数详解,本文同时诉大家complie,pre-link,post-link的用法与区别等内容,需要的朋友可以参考下   通常大家在 ...

  10. java自动转型

    /*2015-10-30*/ public class TypeAutoConvert { public static void main(String[] args) { int a = 5; Sy ...