原文:通过扩展改善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. hdu4888 Redraw Beautiful Drawings

    14更多学校的第二个问题 网络流量   分别以行,列作为结点建图 i行表示的结点到j列表示的结点的流量便是(i, j)的值 跑遍最大流   若满流了便是有解   推断是否unique  就是在残余网络 ...

  2. [2011山东ACM省赛] Identifiers(模拟)

    Identifiers Time Limit: 1000ms   Memory limit: 65536K  有疑问?点这里^_^ 题目描写叙述  Identifier is an important ...

  3. Java使用串行编程操作继电器

    首先,我们必须建立一个良好的环境,那是,jdk并且tomcat.如果它不必须是web装了! 还有就是配置,也就是默认的comm.jar ,javax.comm.properties , win32co ...

  4. Team Foundation Server 2015使用教程--默认团队成员添加

  5. 例如找出令人信服的权威C++中间malloc与new

    例如找出令人信服的权威C++中间malloc与new 问题: 非常多人都知道malloc与new都是用来申请空间用的,开辟空间来源于堆中. 可是在C++中却非常少用malloc去申请空间,为什么? 以 ...

  6. 标准SVD和改进的SVD

    参考链接:http://www.cnblogs.com/yangxiao99/p/4752890.html 参考链接:http://www.cnblogs.com/yangxiao99/p/47529 ...

  7. 工厂模式IDAL具体解释

    IDAL 一. IDAL主要功能: 1.这全然是"工厂模式"的一部分实现而已 2.这是一组接口类,当中包含了每一个要公开的数据訪问方法.为每一个数据库产品单独编写的DAL(数据訪问 ...

  8. ORACLE在表中添加的目光凝视和读取列

    在ORACLE中给表.列添加凝视以及读取凝视 1.给表填加凝视:SQL>comment on table 表名 is '表凝视"; 2.给列加凝视:SQL>comment on ...

  9. 问题(bug)确实不在代码逻辑上面,往往是配置、权限或者业务逻辑之外的地方(转)

    不能说所有的bug都是纸老虎,但往往那种看似很奇葩的bug,导致的原因确实很简单,烦了你一段时间,找到真相又让你忍不住一笑.什么是奇葩的bug呢.我的定义是:代码逻辑都一样,但在A处是好的,到了B处就 ...

  10. 微软发布Win10开发者指南视频

    假设你是一个开发者,推荐你看看微软今天推出了一系列视频,标题是Win10开发人员指南,总体长度6时,多达22章内容,介绍很广泛.其实,即使你是编程新手或仅有兴趣,也值得一看. 开发人员Jerry Ni ...