欢迎您光临深圳塔灯网络科技有限公司!
电话图标 余先生:13699882642

网站百科

为您解码网站建设的点点滴滴

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

发表日期:2019-09 文章编辑:小灯 浏览次数:2014

“基于某个规则的验证”是本解决方案一个最大的卖点。为了保持以验证规则名称为核心的上下文信息,我定义了如下一个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: }

本页内容由塔灯网络科技有限公司通过网络收集编辑所得,所有资料仅供用户学习参考,本站不拥有所有权,如您认为本网页中由涉嫌抄袭的内容,请及时与我们联系,并提供相关证据,工作人员会在5工作日内联系您,一经查实,本站立刻删除侵权内容。本文链接:https://dengtar.com/19692.html
相关开发语言
 八年  行业经验

多一份参考,总有益处

联系深圳网站公司塔灯网络,免费获得网站建设方案及报价

咨询相关问题或预约面谈,可以通过以下方式与我们联系

业务热线:余经理:13699882642

Copyright ? 2013-2018 Tadeng NetWork Technology Co., LTD. All Rights Reserved.    

  • QQ咨询
  • 在线咨询
  • 官方微信
  • 联系电话
    座机0755-29185426
    手机13699882642
  • 预约上门
  • 返回顶部