在ASP.NET MVC中如何应用多个相同类型的ValidationAttribute?

ASP.NET MVC采用System.ComponentModel.DataAnnotations提供的元数据验证机制对Model实施验证,我们可以在Model类型或者字段/属性上应用相应的ValidationAttribute。但是在默认情况下,对于同一个类型的ValidationAttribute特性只允许一个应用到目标元素上——即使我们将AllowMultiple属性设置为True。这篇文章的目的就是为了解决这个问题。[源代码从这里下载]

一、一个自定义ValidationAttribute:RangeIfAttribute

为了演示在相同的目标元素(类、属性或者字段)应用多个同类的ValidationAttribute,我定义了一个名称为RangeIfAttribute特性用于进行“有条件的区间验证”。如下面的代码片断所示,RangeIfAttribute是RangeAttribute的子类,应用在上面的AttributeUsageAttribute特性的AllowMultiple 属性被设置为True。RangeIfAttribute定义了Property和Value两个属性,分别表示被验证属性/字段所在类型的另一个属性名称和相应的值,只有当指定的属性值与通过Value属性值相等的情况下我们在真正进行验证。具体的验证逻辑定义在重写的IsValid方法中。

   1: [AttributeUsage( AttributeTargets.Field| AttributeTargets.Property, AllowMultiple = true)]
   2: public class RangeIfAttribute: RangeAttribute
   3: {
   4:     public string Property { get; set; }
   5:     public string Value { get; set; }
   6:     public RangeIfAttribute(string property, string value, double minimum, double maximum)
   7:         : base(minimum, maximum)
   8:     {
   9:         this.Property = property;
  10:         this.Value = value;
  11:     }
  12:     protected override ValidationResult IsValid(object value, ValidationContext validationContext)
  13:     {
  14:         object propertyValue = validationContext.ObjectType.GetProperty(this.Property).GetValue(validationContext.ObjectInstance,null);
  15:         propertyValue = propertyValue ?? "";
  16:         if (propertyValue.ToString()!= this.Value)
  17:         {
  18:             return null;
  19:         }
  20:         if (base.IsValid(value))
  21:         {
  22:             return null;
  23:         }
  24:  
  25:         string[] memberNames = (validationContext.MemberName != null) ? new string[] { validationContext.MemberName } : null;
  26:         return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName), memberNames);
  27:     }
  28: }

二、将RangeIfAttribute应用于Employee

我们将RangeIfAttribute特性应在具有如下定义的表示员工的Employee类型的Salary(表示薪水)属性上,另外一个属性Grade表示员工的级别。应用在Salary属性上的RangeIfAttribute特性体现了基于级别的薪水区间验证规则:对于G7、G8和G9的员工,其薪水分别在2000~3000,3000~4000和4000~5000范围内。

   1: public class Employee
   2: {
   3:     public string Name { get; set; }
   4:     public string Grad { get; set; }
   5:     [RangeIf("Grad", "G7", 2000, 3000)]
   6:     [RangeIf("Grad", "G8", 3000, 4000)]
   7:     [RangeIf("Grad", "G9", 4000, 5000)]
   8:     public decimal Salary { get; set; }
   9: }

现在我们创建如下一个EmployeeController,其默认的两个Index操作方法定义如下。在HttpPost的Index操作中,如果验证成功我们将“验证成功”字样作为ModelError添加到ModelState中。

   1: public class EmployeeController : Controller
   2: {
   3:     public ActionResult Index()
   4:     {
   5:         return View(new Employee());
   6:     }
   7:  
   8:     [HttpPost]
   9:     public ActionResult Index(Employee employee)
  10:     {
  11:         if (ModelState.IsValid)
  12:         {
  13:             ModelState.AddModelError("", "验证成功");
  14:             return View(new Employee());
  15:         }
  16:         else
  17:         {
  18:             return View(new Employee());
  19:         }
  20:     }
  21:  
  22: }

下面是Index操作默认的View的定义:

   1: @model MultipleValidator.Models.Employee
   2: @{
   3:     ViewBag.Title = "Employee Management";
   4: }
   5: @Html.ValidationSummary(true)
   6: @using (Html.BeginForm())
   7: { 
   8:     @Html.EditorForModel()
   9:     <input type="submit" value="Save" />
  10: }

遗憾的是,ASP.NET MVC并不能按照我们希望的方对我们的输入进行验证。如下面的截图所示,我们只有在输入G9的时候,系统才能实施成功地验证,对于G7和G8则被输入的Salary值(0.00)是合法的。

三、重写TypeId属性解决问题

之所以会发生上述的这种现象,原因在于被应用到Salary属性上的RangeIfAttribute特性,最终只有最后一个(Value=“G9”)被使用到。ASP.NET MVC在生成包括验证特性的Model的元数据的时候,针对某个元素的所有ValidationAttribute是被维护在一个字典上的,而这个字典的值就是Attribute的TypeId属性。在默认的情况下,Attribute的TypeId返回的是自身的类型,所以导致应用到相同目标元素的同类ValidationAttribute只能有一个。幸好Attribute的TypeId属性是可以被重写的,县在我们在RangeIfAttribute中按照如下的方式对这个属性进行重写:

   1: [AttributeUsage( AttributeTargets.Field| AttributeTargets.Property, AllowMultiple = true)]
   2: public class RangeIfAttribute: RangeAttribute
   3: {
   4:     //其他成员
   5:     private object typeId;
   6:     public override object TypeId
   7:     {
   8:         get
   9:         {
  10:             return (null == typeId) ? (typeId = new object()) : typeId;
  11:         }
  12:     }
  13: }

再次运行我们的程序则一切正常:

值得一提的是:重写TypeId属性的方式只能解决服务端验证的问题,对于客户端认证无效。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大内老A

通过一个模拟程序让你明白ASP.NET MVC是如何运行的

ASP.NET MVC的路由系统通过对HTTP请求的解析得到表示Controller、Action和其他相关的数据,并以此为依据激活Controller对象,调...

2886
来自专栏刘望舒

LeakCanary看这一篇文章就够了

LeakCanary是Square公司基于MAT开源的一个内存泄漏检测工具,在发生内存泄漏的时候LeakCanary会自动显示泄漏信息。

1.3K5
来自专栏屈定‘s Blog

造轮子--Excel报表工具

由于公司内部之前对于excel封装操作并不是很方便,而且对于特殊的需求不是很容易满足,这个月的任务是迁移部分业务小报表顺便重构下,因此这里造个轮子,便于导入和导...

1783
来自专栏逸鹏说道

我这么玩Web Api(二)

数据验证,全局数据验证与单元测试 目录 一、模型状态 - ModelState 二、数据注解 - Data Annotations 三、自定义数据注解 四、全局...

5146
来自专栏進无尽的文章

编码篇-学会小用宏和条件编译

宏定义在C系开发中可以说占有举足轻重的作用。底层框架自不必说,为了编译优化和方便,以及跨平台能力,宏被大量使用,可以说底层开发离开define将寸步难行。而在更...

1591
来自专栏跟着阿笨一起玩NET

以读取博客园随笔备份为例 将xml 序列化成json,再序列化成对象

资源下载:http://files.cnblogs.com/codealone/ConsoleApplication2.zip

841
来自专栏微服务那些事儿

关键数据变更监控

在经过了对mybatis的一番检索之后,没有发现对该需求的解决方式.在认知范围内,想到了使用mabatis拦截器解决该问题。

79819
来自专栏大内老A

Enterprise Library深入解析与灵活应用(6):自己动手创建迷你版AOP框架

基于Enterprise Library PIAB的AOP框架已经在公司项目开发中得到广泛的使用,但是最近同事维护一个老的项目,使用到了Enterprise L...

2098
来自专栏Flutter入门

Weex是如何在Android客户端上跑起来的

Weex可以通过自己设计的DSL,书写.we文件或者.vue文件来开发界面,整个页面书写分成了3段,template、style、script,借鉴了成熟的MV...

3985
来自专栏日常分享

Java 实现一个带提醒的定时器

定时闹钟预览版EXE下载链接:https://files.cnblogs.com/files/rekent/ReadytoRelax_jar.zip

2321

扫码关注云+社区

领取腾讯云代金券