深圳的网站建设,东莞网络推广运营团队,什么叫网络服务商,网站建设拷贝软件通过扩展改善ASP.NET MVC的验证机制[实现篇] 原文:通过扩展改善ASP.NET MVC的验证机制[实现篇]在《使用篇》中我们谈到扩展的验证编程方式#xff0c;并且演示了本解决方案的三大特性#xff1a;消息提供机制的分离、多语言的支持和多验证规则的支持#xff0c;我们现在来看… 通过扩展改善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 IDictionarystring, object Properties { get; private set; } 9: 10: public ValidatorContext(string ruleName, CultureInfo culturenull) 11: { 12: this.RuleName ruleName; 13: this.Properties new Dictionarystring, 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 FuncControllerContext, 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).OfTypeValidationRuleAttribute().FirstOrDefault() as ValidationRuleAttribute; 34: if (null attribute) 35: { 36: attribute controllerDescriptor.GetCustomAttributes(true).OfTypeValidationRuleAttribute().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, IEnumerableValidatorBaseAttribute 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 IEnumerableModelClientValidationRule 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最终通过对应的ModelValidatorProviderDataAnnotationsModelValidatorProvider用于创建ModelValidatorDataAnnotationsModelValidator。我们必须在ModelValidator创建之前将不匹配的Validator特性移除才能确保只有与当前ValidatorContext相匹配的Validator特性参与验证。为此我们通过继承DataAnnotationsModelValidator自定义了如下一个ExtendedDataAnnotationsModelValidator。 1: public class ExtendedDataAnnotationsModelValidatorProvider : DataAnnotationsModelValidatorProvider 2: { 3: protected override IEnumerableModelValidator GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerableAttribute attributes) 4: { 5: var validators attributes.OfTypeValidatorBaseAttribute(); 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.OfTypeDataAnnotationsModelValidatorProvider().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 IEnumerableModelClientValidationRule 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