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

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

在《使用篇》中我们谈到扩展的验证编程方式,并且演示了本解决方案的三大特性:消息提供机制的分离多语言的支持多验证规则的支持,我们现在来看看这样的验证解决方案最终是如何实现的。

目录:
一、为验证创建一个上下文:ValidatorContext
二、通过自定义ActionInvoker在进行操作执行之前初始化上下文
三、为Validator创建基类:ValidatorBaseAttribute
四、通过自定义ModelValidatorProvider在验证之前将不匹配Validator移除
五、RequiredValidatorAttribute的定义

一、为验证创建一个上下文:ValidatorContext

“基于某个规则的验证”是本解决方案一个最大的卖点。为了保持以验证规则名称为核心的上下文信息,我定义了如下一个ValidatorContext(我们本打算将其命名为ValidationContext,无奈这个类型已经存在)。ValidatorContext的属性RuleName和Culture表示当前的验证规则和语言文化(默认值为当前线程的CurrentUICulture),而字典类型的属性Properties用户存放一些额外信息。当前ValidationContext的获取与设置通过静态Current完成。

   1: public class ValidatorContext
   2: {
   3:     [ThreadStatic]
   4:     private static ValidatorContext current;
   5:  
   6:     public string RuleName { get; private set; }
   7:     public CultureInfo Culture { get; private set; }
   8:     public IDictionary<string, object> Properties { get; private set; }
   9:  
  10:     public ValidatorContext(string ruleName, CultureInfo culture=null)
  11:     {
  12:         this.RuleName = ruleName;
  13:         this.Properties = new Dictionary<string, object>();
  14:         this.Culture = culture??CultureInfo.CurrentUICulture;
  15:     }
  16:  
  17:     public static ValidatorContext Current
  18:     {
  19:         get { return current; }
  20:         set { current = value; }
  21:     }
  22: }

我们为ValidatorContext定义了如下一个匹配的ValidatorContextScope对象用于设置ValidatorContext的作用范围。

   1: public class ValidatorContextScope : IDisposable
   2: {
   3:     private ValidatorContext current = ValidatorContext.Current;
   4:     public ValidatorContextScope(string ruleName, CultureInfo culture = null)
   5:     {
   6:         ValidatorContext.Current = new ValidatorContext(ruleName, culture);
   7:     }
   8:     public void Dispose()
   9:     {
  10:         if (null == current)
  11:         {
  12:             foreach (object property in ValidatorContext.Current.Properties.Values)
  13:             {
  14:                 IDisposable disposable = property as IDisposable;
  15:                 if (null != disposable)
  16:                 {
  17:                     disposable.Dispose();
  18:                 }
  19:             }
  20:         }
  21:         ValidatorContext.Current = current;
  22:     }
  23: }

二、通过自定义ActionInvoker在进行操作执行之前初始化上下文

通过《使用篇》中我们知道当前的验证规则名称是通过ValidationRuleAttribute来设置的,该特性不仅仅可以应用在Action方法上,也可以应用在Controller类型上。当然Action方法上的ValidationRuleAttribute具有更高的优先级。如下面的代码片断所示,ValidationRuleAttribute就是一个包含Name属性的普通Attribute而已。

   1: [AttributeUsage( AttributeTargets.Class| AttributeTargets.Method)]
   2: public class ValidationRuleAttribute:Attribute
   3: {
   4:     public string Name { get; private set; }
   5:     public ValidationRuleAttribute(string name)
   6:     {
   7:         this.Name = name;
   8:     }
   9: }

很显然,以当前验证规则验证规则为核心的ValidatorContext需要在Action操作之前设置(严格地说应该在进行Model绑定之前),而在Action操作完成后清除。很自然地,我们可以通过自定义ActionInvoker来完成,为此我定义了如下一个直接继承自ControllerActionInvoker的ExtendedControllerActionInvoker类。

   1: public class ExtendedControllerActionInvoker : ControllerActionInvoker
   2: {
   3:     public ExtendedControllerActionInvoker()
   4:     { 
   5:         this.CurrentCultureAccessor= (context=>
   6:             {
   7:                 string culture = context.RouteData.GetRequiredString("culture");
   8:                 if(string.IsNullOrEmpty(culture))
   9:                 {
  10:                     return null;
  11:                 }
  12:                 else
  13:                 {
  14:                     return new CultureInfo(culture);
  15:                 }
  16:             });
  17:     }
  18:     public virtual Func<ControllerContext, CultureInfo> CurrentCultureAccessor { get; set; }
  19:     public override bool InvokeAction(ControllerContext controllerContext, string actionName)
  20:     {
  21:         CultureInfo originalCulture = CultureInfo.CurrentCulture;
  22:         CultureInfo originalUICulture = CultureInfo.CurrentUICulture;
  23:         try
  24:         {
  25:             CultureInfo culture = this.CurrentCultureAccessor(controllerContext);
  26:             if (null != culture)
  27:             {
  28:                 Thread.CurrentThread.CurrentCulture = culture;
  29:                 Thread.CurrentThread.CurrentUICulture = culture;
  30:             }
  31:             var controllerDescriptor = this.GetControllerDescriptor(controllerContext);
  32:             var actionDescriptor = this.FindAction(controllerContext, controllerDescriptor, actionName);
  33:             ValidationRuleAttribute attribute = actionDescriptor.GetCustomAttributes(true).OfType<ValidationRuleAttribute>().FirstOrDefault() as ValidationRuleAttribute;
  34:             if (null == attribute)
  35:             {
  36:                 attribute = controllerDescriptor.GetCustomAttributes(true).OfType<ValidationRuleAttribute>().FirstOrDefault() as ValidationRuleAttribute;
  37:             }
  38:             string ruleName = (null == attribute) ? string.Empty : attribute.Name;
  39:             using (ValidatorContextScope contextScope = new ValidatorContextScope(ruleName))
  40:             {
  41:                 return base.InvokeAction(controllerContext, actionName);
  42:             }
  43:         }
  44:         catch
  45:         {
  46:             throw;
  47:         }
  48:         finally
  49:         {
  50:             Thread.CurrentThread.CurrentCulture = originalCulture;
  51:             Thread.CurrentThread.CurrentUICulture = originalUICulture;
  52:         }
  53:     }
  54: }

如上面的代码片断所示,在重写的InvokeAction方法中我们通过ControllerDescriptor/ActionDescriptor得到应用在Controller类型/Action方法上的ValidationRuleAttribute特性,并或者到设置的验证规则名称。然后我们创建ValidatorContextScope对象,而针对基类InvokeAction方法的执行就在该ValidatorContextScope中执行的。初次之外,我们还对当前线程的Culture进行了相应地设置,默认的Culture 信息来源于当前RouteData。

为了更方便地使用ExtendedControllerActionInvoker,我们定义了一个抽象的Controller基类:BaseController。BaseController是Controller的子类,在构造函数中我们将ActionInvoker属性设置成我们自定义的ExtendedControllerActionInvoker对象。

   1: public abstract class BaseController: Controller
   2: {
   3:     public BaseController()
   4:     {
   5:         this.ActionInvoker = new ExtendedControllerActionInvoker();
   6:     }
   7: }

三、为Validator创建基类:ValidatorBaseAttribute

接下来我们才来看看真正用于验证的验证特性如何定义。我们的验证特性都直接或者间接地继承自具有如下定义的ValidatorBaseAttribute,而它使ValidationAttribute的子类。如下面的代码片断所示,ValidatorBaseAttribute还实现了IClientValidatable接口,以提供对客户端验证的支持。属性RuleName、MessageCategory、MessageId和Culture分别代表验证规则名称、错误消息的类别和ID号(通过这两个属性通过MessageManager这个独立的组件获取完整的错误消息)和基于的语言文化。

   1: [AttributeUsage(AttributeTargets.Class|AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
   2: public abstract class ValidatorBaseAttribute : ValidationAttribute, IClientValidatable
   3: {
   4:     
   5:     public string RuleName { get; set; }
   6:     public string MessageCategory { get; private set; }
   7:     public string MessageId { get; private set; }
   8:     public string Culture { get; set; }
   9:  
  10:     public ValidatorBaseAttribute(MessageManager messageManager, string messageCategory, string messageId, params object[] args)
  11:         : base(() => messageManager.FormatMessage(messageCategory, messageId, args))
  12:     {
  13:         this.MessageCategory = messageCategory;
  14:         this.MessageId = messageId;
  15:     }
  16:  
  17:     public ValidatorBaseAttribute(string messageCategory, string messageId, params object[] args)
  18:         : this(MessageManagerFactory.GetMessageManager(), messageCategory, messageId, args)
  19:     { }
  20:  
  21:     public virtual bool Match(ValidatorContext context, IEnumerable<ValidatorBaseAttribute> validators)
  22:     {
  23:         if (!string.IsNullOrEmpty(this.RuleName))
  24:         {
  25:             if (this.RuleName != context.RuleName)
  26:             {
  27:                 return false;
  28:             }
  29:         }
  30:  
  31:         if (!string.IsNullOrEmpty(this.Culture))
  32:         {                
  33:             if (string.Compare(this.Culture, context.Culture.Name, true) != 0)
  34:             {
  35:                 return false;
  36:             }
  37:         }
  38:  
  39:         if (string.IsNullOrEmpty(this.Culture))
  40:         {
  41:             if (validators.Any(validator => validator.GetType() == this.GetType() && string.Compare(validator.Culture, context.Culture.Name, true) == 0))
  42:             {
  43:                 return false;
  44:             }
  45:         }
  46:         return true;
  47:     }
  48:     public abstract IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context);
  49:     private object typeId;
  50:     public override object TypeId
  51:     {
  52:         get { return (null == typeId) ? (typeId = new object()) : typeId; }
  53:     }
  54: }

由于我们需要将多个相同类型的Validator特性应用到某个类型或者字段/属性上,我们需要通过AttributeUsageAttribute将AllowMultiple属性设置为True,此外需要重写TypeId属性。至于为什么需需要这么做,可以参考我的上一篇文章《在ASP.NET MVC中如何应用多个相同类型的ValidationAttribute?》。对于应用在同一个目标元素的多个相同类型的Validator特性,只有与当前ValidatorContext相匹配的才能执行,我们通过Match方法来进行匹配性的判断,具体的逻辑是这样的:

  • 在显式设置了RuleName属性情况下,如果不等于当前验证规则,直接返回False;
  • 在显式设置了Culture属性情况下,如果与当前语言文化不一致,直接返回False;
  • 在没有设置Culture属性(语言文化中性)情况下,如果存在另一个同类型的Validator与当前的语言文化一致,也返回False;
  • 其余情况返回True

四、通过自定义ModelValidatorProvider在验证之前将不匹配Validator移除

应用在Model类型或其属性/字段上的ValidationAttribute最终通过对应的ModelValidatorProvider(DataAnnotationsModelValidatorProvider)用于创建ModelValidator(DataAnnotationsModelValidator)。我们必须在ModelValidator创建之前将不匹配的Validator特性移除,才能确保只有与当前ValidatorContext相匹配的Validator特性参与验证。为此我们通过继承DataAnnotationsModelValidator自定义了如下一个ExtendedDataAnnotationsModelValidator。

   1: public class ExtendedDataAnnotationsModelValidatorProvider : DataAnnotationsModelValidatorProvider
   2: {
   3:     protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
   4:     {
   5:         var validators = attributes.OfType<ValidatorBaseAttribute>();
   6:         var allAttributes = attributes.Except(validators).ToList();
   7:         foreach (ValidatorBaseAttribute validator in validators)
   8:         {
   9:             if (validator.Match(ValidatorContext.Current, validators))
  10:             {
  11:                 allAttributes.Add(validator);
  12:             }
  13:         }
  14:         return base.GetValidators(metadata, context, allAttributes);
  15:     }
  16: }

如上面的代码片断所示,在重写的GetClientValidationRules方法中,输入参数attributes表示所有的ValidationAttribute,在这里我们根据调用ValidatorBaseAttribute的Match方法将不匹配的Validator特性移除,然后根据余下的ValidationAttribute列表调用基类GetValidators方法创建ModelValidator列表。值得一提的是,关于System.Attribute的Equals/GetHashCode方法的问题就从这个方法中发现的(详情参见《为什么System.Attribute的GetHashCode方法需要如此设计?》)。自定义ExtendedDataAnnotationsModelValidator在Global.asax的Application_Start方法中通过如下的方式进行注册。

   1: protected void Application_Start()
   2: {
   3:      //...
   4:     var provider = ModelValidatorProviders.Providers.OfType<DataAnnotationsModelValidatorProvider>().FirstOrDefault();
   5:     if (null != provider)
   6:     {
   7:         ModelValidatorProviders.Providers.Remove(provider);
   8:     }
   9:     ModelValidatorProviders.Providers.Add(new ExtendedDataAnnotationsModelValidatorProvider());
  10: }

五、RequiredValidatorAttribute的定义

最后我们来看看用于验证必需字段的RequiredValidatorAttribute如何定义。IsValid用于服务端验证,而GetClientValidationRules生成调用客户端验证规则。

   1: [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
   2: public class RequiredValidatorAttribute : ValidatorBaseAttribute
   3: {
   4:     public RequiredValidatorAttribute(string messageCategory, string messageId, params object[] args)
   5:         : base(messageCategory, messageId, args)
   6:     { }   
   7:  
   8:     public override bool IsValid(object value)
   9:     {
  10:         if (value == null)
  11:         {
  12:             return false;
  13:         }
  14:         string str = value as string;
  15:         if (str != null)
  16:         {
  17:             return (str.Trim().Length != 0);
  18:         }
  19:         return true;
  20:     }
  21:  
  22:     public override IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
  23:     {
  24:         return new ModelClientValidationRequiredRule[] { new ModelClientValidationRequiredRule(this.ErrorMessageString) };
  25:     }
  26: }
posted on 2014-06-28 15:30 NET未来之路 阅读(...) 评论(...) 编辑 收藏

转载于:https://www.cnblogs.com/lonelyxmas/p/3813416.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/258462.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

canopen和1939区别_CAN 和 CANopen的区别和联系

1、CAN与CANopen的共同点与不同点&#xff1a;CAN只定义了物理层与链路层&#xff0c;而没有定义用户层&#xff0c;用户可根据自己的需要定义一些网络上的通信约定&#xff1b; CANopen是在CAN的基础上定义了用户层&#xff0c;即规定了用户、软件、网络终端等之间用来进行信…

ONOS系统架构演进,实现高可用性解决方案

上一篇文章《ONOS高可用性和可扩展性实现初探》讲到了ONOS系统架构在高可用、可扩展方面技术概况&#xff0c;提到了系统在分布式集群中怎样保证数据的一致性。在数据终于一致性方面&#xff0c;ONOS採用了Gossip协议。这一部分的变化不大&#xff0c;而在强一致性方案的选择方…

Struts2_day01

Java Web开发常用框架 SSH(Struts2 Spring Hibernate)SSM(Struts2 Spring MyBatis)SSI(Struts2 Spring iBatis) 多种框架协同工作 Web层 -- Service层 -- Dao层 Struts2框架: Struts2是一个基于MVC设计模式的Web应用框架&#xff0c;它本质上相当于一个servlet&#xff0c;在MV…

使用 python 开发 Web Service

使用 python 开发 Web Service Python 是一种强大的面向对象脚本语言&#xff0c;用 python 开发应用程序往往十分快捷&#xff0c;非常适用于开发时间要求苛刻的原型产品。使用 python 开发 web service 同样有语言本身的简捷高速的特点&#xff0c;能使您快速地提供新的网络服…

python中输出n开始的5个奇数_送你99道Python经典练习题,练完直接上手做项目,免费送了来拿吧...

学python没练习题怎么行、今天&#xff0c;给大家准备一个项目&#xff1a; 99道编程练习&#xff0c;这些题如果能坚持每天至少完成一道&#xff0c;一定可以帮大家轻松 get Python 的编程技能。目前&#xff0c;这个项目已经获得了 2924 Stars&#xff0c;2468 Forks。首先&a…

java 基础5

一、 什么是数组及其作用&#xff1f; 定义&#xff1a;具有相同数据类型的一个集合 作用&#xff1a;存储连续的具有相同类型的数据 二、 java中如何声明和定义数组 2.1 声明和定义的语法&#xff1a; 数据类型[ ] 数组名&#xff1b;( int[ ] nums ; ) 或 数…

TFS(Team Foundation Server)介绍和入门

在本文的两个部分中&#xff0c;我将介绍Team Foundation Server的一些核心特征&#xff0c;重点介绍在本产品的日常应用中是怎样将这些特性结合在一起使用的。 作为一名软件开发者&#xff0c;在我的职业生涯中&#xff0c;我常常会用到支持软件开发过程的大量开发工具&#x…

逆函数求导公式_反函数求导法则

反函数的求导法则是&#xff1a;反函数的导数是原函数导数的倒数。例题&#xff1a;求yarcsinx的导函数。首先&#xff0c;函数yarcsinx的反函数为xsiny&#xff0c;所以&#xff1a;y‘1/sin’y1/cosy&#xff0c;因为xsiny&#xff0c;所以cosy√1-x2&#xff0c;所以y‘1/√…

SpringXML方式配置bean的懒加载lazy-init

lazy-init&#xff08;懒加载&#xff09;&#xff0c;表示该bean在容器初始化的时候不进行初始化。例如&#xff1a;<bean name"role1" class"com.fz.entity.Role" lazy-init"true">以上配置表示&#xff1a;spring容器在初始化的时候不会…

windows下system函数的使用

system函数 是可以调用一些DOS命令,比如system("cls");//清屏,等于在DOS上使用cls命令写可执行文件路径&#xff0c;可以运行它 下面列出常用的DOS命令,都可以用system函数调用: ASSOC 显示或修改文件扩展名关联。AT 计划在计算机上运行的命令和程序。ATTRIB 显示或更…

WWDC2017 笔记 - Cocoa Touch 中的新特性

这篇文章是 What’s New in Cocoa Touch / UIKit Session 201 的一些整理。【基于OC】 转自我的 Blog: Dannys Dream Drag Drop 新的交互方式 拖拽 Drag 需要 Drag 的对象要 add 一个 UIDragInteraction &#xff0c;用法类似于 UIGestureRecognizer 。UIDragInteraction 有一个…

[Hadoop] - 自定义Mapreduce InputFormatOutputFormat

在MR程序的开发过程中&#xff0c;经常会遇到输入数据不是HDFS或者数据输出目的地不是HDFS的&#xff0c;MapReduce的设计已经考虑到这种情况&#xff0c;它为我们提供了两个组建&#xff0c;只需要我们自定义适合的InputFormat和OutputFormat&#xff0c;就可以完成这个需求&a…

PS 色调——老照片效果

这就是通过调色使照片显得发黄。 R_new0.393*R0.769*G0.189*B; G_new0.349*R0.686*G0.168*B; B_new0.272*R0.534*G0.131*B; clc; clear all; Imageimread(9.jpg); Imagedouble(Image); Image_newImage; Image_new(:,:,1)0.393*Image(:,:,1)0.769*Image(:,:,2)0.189*Image(:,:,3…

jsp出现错误

昨天在调试页面时发生了如图显示的异常&#xff0c;它出现的原因是当<jsp:forward>或<jsp:include>标签没有参数时&#xff0c;开始标签和结束标签</jsp:forward>或</jsp:include>之间不能有空格&#xff0c;不能换行。解决办法&#xff1a;删除标签之…

门限回归模型的思想_Stata+R:门槛回归教程

来源 | 数量经济学综合整理转载请联系进行回归分析&#xff0c;一般需要研究系数的估计值是否稳定。很多经济变量都存在结构突变问题&#xff0c;使用普通回归的做法就是确定结构突变点&#xff0c;进行分段回归。这就像我们高中学习的分段函数。但是对于大样本、面板数据如何寻…

【数论】[CF258C]Little elephant and LCM

题目 分析&#xff1a;枚举最大数&#xff0c;然后找出它所有因数p1…….pk&#xff0c; 从中任意选取一些数&#xff0c;这些数的LCM|这个数且&#xff0c;这些数的最大LCM就是枚举的这个数&#xff0c;且若pi<aj<pi1则前i个数可以放在j这个位置&#xff0c;即j这个位置…

为普通Object添加类似AttachedProperty的属性

为普通Object添加类似AttachedProperty的属性 周银辉 我们知道&#xff0c;在WPF中对应一个DependencyObject&#xff0c;我们很容易通过AttachedProperty来为类型附加一个属性。但对于普通的Object而言&#xff0c;这就不可行了。 我现在遇到这样一个问题&#xff0c;下面有一…

python 操作RabbitMQ

pip install pika使用API操作RabbitMQ基于Queue实现生产者消费者模型View Code 对于RabbitMQ来说&#xff0c;生产和消费不再针对内存里的一个Queue对象&#xff0c;而是某台服务器上的RabbitMQ Server实现的消息队列。#!/usr/bin/env python import pika# ###################…

python和嵌入式哪个容易_嵌入式与python选哪个

从概念上来说&#xff0c;嵌入式和Python的区别还是比较明显的&#xff0c;嵌入式是一个开发领域&#xff0c;而Python则是一门编程语言。嵌入式开发是开发领域的一个重要分支&#xff0c;是物联网领域技术的重要组成部分&#xff0c;可以说有物联网的地方就离不开嵌入式开发。…

二阶传递函数的推导及几种求解方法的比较

二阶系统是指那些可用二阶微分方程描述的系统&#xff0c;其电路形式是由两个独立动态元器件组成的电路。 二阶系统电路包括二阶低通电路、二阶高通电路、二阶带通电路和二阶带阻电路。 下面分别给出以上二阶系统传递函数的推导过程&#xff0c;并以二阶低通电路的冲激响应为例…