对于Model验证,理想的设计应该是场景驱动的,而不是Model(类型)驱动的,也就是对于同一个Model对象,在不同的使用场景中可能具有不同的验证规则。举个简单的例子,对于一个表示应聘者的数据对象来说,针对应聘的岗位不同,肯定对应聘者的年龄、性别、专业技能等方面有不同的要求。但是ASP.NET MVC的Model验证确是Model驱动的,因为验证规则以验证特性的形式应用到Model类型及其属性上。这样的验证方式实际上限制了Model类型在基于不同验证规则的使用场景中的重用。通过上一篇文章《将ValidationAttribute应用到参数上》的扩展我们将验证特性直接应用在参数上变成了可能,这从一定程度上解决了这个问题,但是只能解决部分问题,因为应用到参数的验证特性只能用于针对参数类型级别的验证,而不能用于针对参数类型属性级别的验证(源代码从这里下载)。[本文已经同步到《How ASP.NET MVC Works?》中]

目录
一、同一个Model在采用不同的验证规则
二、新的基类ValidatorAttribute
三、指定当前采用的验证规则:ValidationRuleAttribute
四、新的Controller基类:RuleBasedController
五、自定义ModelValidatorProvider:RuleBasedValidatorProvider

一、同一个Model在采用不同的验证规则

现在我们通过利用对ASP.NET MVC的扩展来实现一种基于不同验证规则的Model验证。为了让读者对这种认证方式有一个感官的认识,我们来看看这个扩展最终实现怎样的验证效果。在通过Visual Studio的ASP.NET MVC 项目模板创建的空Web应用中,我们定义了如下一个Person类型作为Model。

   1: public class Person
   2: {
   3:     [DisplayName("姓名")]
   4:     public string Name { get; set; }
   5:  
   6:     [DisplayName("性别")]
   7:     public string Gender { get; set; }
   8:  
   9:     [DisplayName("年龄")]
  10:     [RangeValidator(10, 20, RuleName = "Rule1",  ErrorMessage = "{0}必须在{1}和{2}之间!")]
  11:     [RangeValidator(20, 30, RuleName = "Rule2", ErrorMessage = "{0}必须在{1}和{2}之间!")]
  12:     [RangeValidator(30, 40, RuleName = "Rule3", ErrorMessage = "{0}必须在{1}和{2}之间!")]
  13:     public int Age { get; set; }     
  14: }

在表示年龄的Age属性上应用了三个RangeValidatorAttribute(不是RangeAttribute),它们对应针对年龄的三种不同的验证规则,RuleName属性表示规则名称。三种验证规则(Rule1、Rule2和Rule3)分别要求年龄分别在10到20、20到30和30到40岁之间。

然后我们定义了具有如下定义HomeController,它具有三组Action方法(Index、Rule1和Rule2)。方法Rule1、Rule2和HomeController类上应用了一个ValidationRuleAttribute特性用于指定了当前采用的验证规则。用于指定验证规则的ValidationRuleAttribute特性可以同时应用于Controller类型和Action方法上,应用于后者的ValidationRuleAttribute特性具有更高的优先级。针对HomeController的定义,Action方法Index、Rule1和Rule2分别采用的验证规则为Rule3、Rule1和Rule2。

   1: 
   2: public class HomeController : RuleBasedController
   3: {
   4:     public ActionResult Index()
   5:     {
   6:         return View("person", new Person());
   7:     }
   8:     [HttpPost]
   9:     public ActionResult Index(Person person)
  10:     {
  11:         return View("person", person);
  12:     }
  13:  
  14:     
  15:     public ActionResult Rule1()
  16:     {
  17:         return View("person", new Person());
  18:     }
  19:     [HttpPost]
  20:     
  21:     public ActionResult Rule1(Person person)
  22:     {
  23:         return View("person", person);
  24:     }
  25:  
  26:     
  27:     public ActionResult Rule2()
  28:     {
  29:         return View("person", new Person());
  30:     }
  31:     [HttpPost]
  32:     [ValidationRule("Rule2")]
  33:     public ActionResult Rule2(Person person)
  34:     {
  35:         return View("person", person);
  36:     }
  37: }

定义在HomeController中的6个方法均将创建的/接收的Person对象呈现到名为Person的View中,该View的定义如下所示。这是一个将Person类型作为Model的强类型View,在该View中我们将作为Model的Person对象以编辑模式呈现在一个表单中,并在表单中提供一个提交按钮。

   1: @model Person
   2: @using (Html.BeginForm())
   3: { 
   4:     @Html.EditorForModel()
   5:     <input type="submit" value="保存" />
   6: }

现在运行我们的程序,并通过在浏览器中指定相应的地址分别访问定义在HomeController的三个Action(Index、Rule1和Rule2),一个用于编辑个人信息的表单会呈现出来。然后我们根据三个Action方法采用的验证规则输入不合法的年龄,然后点击“保存”按钮,我们会看到输入的年龄按照对应的规则被验证了,具体的验证效果如下图所示。

二、新的基类ValidatorAttribute

我们现在就来具体谈谈上面这个例子所展示的基于不同规则的Model验证是如何实现的。首先我们需要重建一套新的验证特性体系,只为我们能够指定具体的验证规则。为此我们定义了一个抽象的ValidatorAttribute类型,如下面的代码片断所示,ValidatorAttribute直接继承自ValidationAttribute,属性RuleName表示采用的验证规则名称。我们重写了TypeId属性,因为我们需要在相同的属性或者类型上应用多个同类的ValidatorAttribute。

   1: [AttributeUsage( AttributeTargets.Class| AttributeTargets.Property,AllowMultiple = true)]
   2: public abstract class ValidatorAttribute: ValidationAttribute
   3: {
   4:     private object typeId;
   5:     public string RuleName { get; set; }
   6:     public override object TypeId
   7:     { 
   8:         get{return typeId ?? (typeId = new object());}
   9:     }
  10: }

上面演示实例采用的RangeValidatorAttribute定义如下,我们可以看到它仅仅是对RangeAttribute的封装。RangeValidatorAttribute具有与RangeAttribute一致的构造函数定义,并直接使用被封装的RangeAttribute实施验证。除了能够通过RuleName指定具体采用的验证规则之外,其他的使用方式与RangeAttribute完全一致。

   1: [AttributeUsage( AttributeTargets.Property, AllowMultiple = true)]
   2: public class RangeValidatorAttribute:ValidatorAttribute
   3: {
   4:     private RangeAttribute rangeAttribute;
   5:     public RangeValidatorAttribute(int minimum, int maximum)
   6:     {
   7:         rangeAttribute = new RangeAttribute(minimum, maximum);
   8:     }
   9:     public RangeValidatorAttribute(double minimum, double maximum)
  10:     {
  11:         rangeAttribute = new RangeAttribute(minimum, maximum);
  12:     }
  13:     public RangeValidatorAttribute(Type type, string minimum, string maximum)
  14:     {
  15:         rangeAttribute = new RangeAttribute(type, minimum, maximum);
  16:     }
  17:     public override bool IsValid(object value)
  18:     {
  19:         return rangeAttribute.IsValid(value);
  20:     }
  21:  
  22:     public override string FormatErrorMessage(string name)
  23:     {
  24:         return string.Format(CultureInfo.CurrentCulture, base.ErrorMessageString, new object[] { name, rangeAttribute.Minimum, rangeAttribute.Maximum });
  25:     }
  26: }

三、指定当前采用的验证规则:ValidationRuleAttribute

ValidatorAttribte的RuleName属性仅仅指定了验证特性采用的验证规则名称,当前应在采用的验证规则通过应用在Action方法或者Controller类型上的ValidationRuleAttribute特性还指定。如下所示的就是ValidationRuleAttribute的定义,它仅仅包含一个表示当前采用的验证规则名称的RuleName属性的特性而已。

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

四、新的Controller基类:RuleBasedController

对于这个用于实现针对不同验证规则的扩展来说,其核心是如何将通过ValidationRuleAttribute特性设置的验证规则应用到ModelValidator的提供机制中,使之筛选出与当前验证规则匹配的验证特性,在这里我们依然使用Controller上下文来保存这个这个验证规则名称。细心的读者应该留意到了上面演示实例中创建的HomeController不是继承自Controller,而是继承自RuleBasedController,这个自定义的Controller基类定义如下。

   1: public class RuleBasedController: Controller
   2: {
   3:     private static Dictionary<Type, ControllerDescriptor> controllerDescriptors = new Dictionary<Type, ControllerDescriptor>();
   4:     public ControllerDescriptor ControllerDescriptor
   5:     {
   6:         get
   7:         { 
   8:             ControllerDescriptor controllerDescriptor;
   9:             if (controllerDescriptors.TryGetValue(this.GetType(), out controllerDescriptor))
  10:             {
  11:                 return controllerDescriptor;
  12:             }
  13:             lock (controllerDescriptors)
  14:             {
  15:                 if (!controllerDescriptors.TryGetValue(this.GetType(), out controllerDescriptor))
  16:                 {
  17:                     controllerDescriptor = new ReflectedControllerDescriptor(this.GetType());
  18:                     controllerDescriptors.Add(this.GetType(), controllerDescriptor);
  19:                 }
  20:                 return controllerDescriptor;
  21:             }
  22:         }
  23:     }
  24:     protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
  25:     {
  26:         SetValidationRule();
  27:         return base.BeginExecuteCore(callback, state);
  28:     }        
  29:     protected override void ExecuteCore()
  30:     {
  31:         SetValidationRule();
  32:         base.ExecuteCore();
  33:     }
  34:     private void SetValidationRule()
  35:     {
  36:         string actionName = this.ControllerContext.RouteData.GetRequiredString("action");
  37:         ActionDescriptor actionDescriptor = this.ControllerDescriptor.FindAction(this.ControllerContext, actionName);
  38:         if (null != actionDescriptor)
  39:         {
  40:             ValidationRuleAttribute validationRuleAttribute = actionDescriptor.GetCustomAttributes(true).OfType<ValidationRuleAttribute>().FirstOrDefault() ??
  41:                 this.ControllerDescriptor.GetCustomAttributes(true).OfType<ValidationRuleAttribute>().FirstOrDefault() ??
  42:                 new ValidationRuleAttribute(string.Empty);
  43:             this.ControllerContext.RouteData.DataTokens.Add("ValidationRuleName", validationRuleAttribute.RuleName);
  44:         }
  45:     }
  46: }

在继承自Controller的RuleBasedController中,ExecuteCore和BeginExecuteCore方法被重写,在调用基类的同名方法之前,方法SetValidationRule方法被调用将应用在当前Action方法或者Controller类型上的ValidationRuleAttribute特性指定的验证规则名称保存到当前Controller上下文中。由于对Action方法和Controller类上特性的解析需要使用到用于描述Controller的ControllerDescriptor对象,处于性能考虑,我们对该对象进行了全局缓存。

五、自定义ModelValidatorProvider:RuleBasedValidatorProvider

对于应用在同一个属性或者类型上的多个基于不同验证规则的ValidatorAttribute,对应的验证规则名称并没有应用到具体的验证逻辑中。以上面定义的RangeValidatorAttribute为例,具体的验证逻辑通过被封装的RangeAttribute来实现,如果我们不做任何的处理,所有的基于不同规则的RangeValidatorAttribute都还参与到最终的Model验证过程中。我们必须作的是在根据验证特性创建ModelValidator的时候只选择那些与当前验证规则一直的ValidatorAttribute,这样的操作实现在具有如下定义的RuleBasedValidatorProvider中。

   1: public class RuleBasedValidatorProvider : DataAnnotationsModelValidatorProvider
   2: {
   3:     protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
   4:     {
   5:         object validationRuleName = string.Empty;
   6:         context.RouteData.DataTokens.TryGetValue("ValidationRuleName", out validationRuleName);
   7:         string ruleName = validationRuleName.ToString();
   8:         attributes = this.FilterAttributes(attributes, ruleName);
   9:         return base.GetValidators(metadata, context, attributes);
  10:     }
  11:  
  12:     private IEnumerable<Attribute> FilterAttributes(IEnumerable<Attribute> attributes, string validationRule)
  13:     {
  14:             var validatorAttributes = attributes.OfType<ValidatorAttribute>();
  15:             var nonValidatorAttributes = attributes.Except(validatorAttributes);
  16:             List<ValidatorAttribute> validValidatorAttributes = new List<ValidatorAttribute>();
  17:  
  18:         if (string.IsNullOrEmpty(validationRule))
  19:         {
  20:             validValidatorAttributes.AddRange(validatorAttributes.Where(v => string.IsNullOrEmpty(v.RuleName)));                    
  21:         }
  22:         else
  23:         {
  24:             var groups = from validator in validatorAttributesgroup validator by validator.GetType();
  25:             foreach (var group in groups)
  26:             {
  27:                 ValidatorAttribute validatorAttribute = group.Where(v => string.Compare(v.RuleName, validationRule, true) == 0).FirstOrDefault();
  28:                 if (null != validatorAttribute)
  29:                 {
  30:                     validValidatorAttributes.Add(validatorAttribute);
  31:                 }
  32:                 else
  33:                 {
  34:                     validatorAttribute = group.Where(v => string.IsNullOrEmpty(v.RuleName)).FirstOrDefault();
  35:                     if (null != validatorAttribute)
  36:                     {
  37:                         validValidatorAttributes.Add(validatorAttribute);
  38:                     }
  39:                 }
  40:             }
  41:         }
  42:         return nonValidatorAttributes.Union(validValidatorAttributes);
  43:     }
  44: }

如上面的代码所示,RuleBasedValidatorProvider继承自DataAnnotationsModelValidatorProvider,基于当前验证规则(从当前的Controller上下文中提取)对ValidatorAttribute的筛选,以及ModelValidator的创建通过重写的GetValidators方法实现。具体的筛选机制是:如果当前的验证规则存在,则选择与之具有相同规则名称的第一个ValidatorAttribute,如果这样的ValidatorAttribute找不到,则选择第一个没有指定验证规则的ValidatorAttribute;如果当前的验证规则没有指定,那么也选择第一个没有指定验证规则的ValidatorAttribute。

在让我们的Controller继承自RuleBasedController之后,我们需要在Global.asax中通过如下的方式对自定义的RuleBasedValidatorProvider进行注册,然后我们的应用就能按照我们期望的方式根据你指定的验证规则实施Model验证了。

   1: public class MvcApplication : System.Web.HttpApplication
   2: {
   3:     //其他成员
   4:     protected void Application_Start()
   5:     {
   6:         //其他操作
   7:         DataAnnotationsModelValidatorProvider validator = ModelValidatorProviders.Providers.OfType<DataAnnotationsModelValidatorProvider>().FirstOrDefault();
   8:         if(null != validator)
   9:         {
  10:             ModelValidatorProviders.Providers.Remove(validator);
  11:         }
  12:         ModelValidatorProviders.Providers.Add(new RuleBasedValidatorProvider());
  13:     }
  14: }

转载于:https://www.cnblogs.com/artech/archive/2012/06/12/data-annotations-model-validation-05.html

ASP.NET MVC基于标注特性的Model验证:一个Model,多种验证规则相关推荐

  1. ASP.NET MVC基于标注特性的Model验证:DataAnnotationsModelValidator

    对于ASP.NET MVC基于标注特性的Model验证,很多人只知道应用在数据类型及其属性上用于定义验证规则和错误消息的ValidationAttribute.通过<ASP.NET MVC以Mo ...

  2. (转) 一步一步学习ASP.NET 5 (四)- ASP.NET MVC 6四大特性

    转发:微软MVP 卢建晖 的文章,希望对大家有帮助.原文:http://blog.csdn.net/kinfey/article/details/44459625 编者语 : 昨晚写好的文章居然csd ...

  3. ASP.NET MVC Beta 新特性之 IValueProvider

    在刚发布的ASP.NET MVC Beta版中,在UpdataModel方法中提供了一个带有IValueProvider参数的重载.那么这个IValueProvider有什么用呢? 我们先来看一个简单 ...

  4. [导入]ASP.NET MVC框架开发系列课程(2):一个简单的ASP.NET MVC应用程序.zip(13.70 MB)...

    讲座内容: 使用ASP.NET MVC框架进行开发与ASP.NET WebForms截然不同.本次课程将通过官方的示例程序简单了解一下ASP.NET MVC应用程序的结构与特点. 课程讲师: 赵劼 M ...

  5. Asp.net MVC 4 Attributes特性

    Attributes特性 ActionFilterAttribute Represents the base class for filter attributes. 代表筛选器属性的基类. Acti ...

  6. Asp.Net Mvc基于Fleck开发的多人网页版即时聊天室

    一.项目的核心说明 1.Fleck这个是实现websocket一个比较简单第三方组件,它不需要安装额外的容器.本身也就几个接口可供调用. 2.项目是基于.net framework 4.7.2 ,在v ...

  7. How ASP.NET MVC Works?

    一.ASP.NET + MVC IIS与ASP.NET管道 MVC.MVP以及Model2[上篇] MVC.MVP以及Model2[下篇] ASP.NET MVC是如何运行的[1]: 建立在" ...

  8. ASP.NET MVC下的四种验证编程方式[续篇]

    ASP.NET MVC下的四种验证编程方式[续篇] 原文:ASP.NET MVC下的四种验证编程方式[续篇] 在<ASP.NET MVC下的四种验证编程方式>一文中我们介绍了ASP.NET ...

  9. ASP.NET MVC案例教程(基于ASP.NET MVC beta)——第一篇:准备工作

    摘要       本文将简要介绍这个文章系列的目的.形式及大体内容.并且完成开始学习这个系列前所必要的准备工作. 前言       ASP.NET MVC作为微软官方的MVC解决方案,推出有一段时间了 ...

最新文章

  1. sql server数据库定时自动备份
  2. 计算机组成原理学习笔记(一)
  3. 内核同步机制——完成量
  4. VTK:PolyData之EmbedPointsIntoVolume
  5. ESP8266在线视频网址
  6. 真希望永远用不到这些代码
  7. 【渝粤题库】陕西师范大学100091 现代教育技术学 作业(专升本)
  8. 二维map —— HDU1263
  9. 【转】C++学习四 冒泡排序法的一些改进
  10. 酷狗笔试题:补齐左括号(栈)
  11. Eclipse启动项目报启动上下文失败问题解决方案总结
  12. win11鼠标怎么在轮滑时只滚动一个屏幕 Windows11鼠标设置轮滑只滚动一个屏幕的步骤方法
  13. (原)U盘可见容量不能被识别的处理方法
  14. MySql学习之组合查询
  15. 大数据分析-实验五 pdfminer
  16. 凭借一句话获得图灵奖的Pascal之父——Nicklaus Wirth
  17. 使用Arcgis制作的专题地图
  18. Qt数据可视化QChart - QBarSet柱状图
  19. 十本Android开发学习书籍下载链接
  20. 基础SQL Server 操作问题——仅当使用了列表并且IDENTITY_INSERT为ON时,才能为表中的标识列制定显示值

热门文章

  1. MoveNet 姿态估计,树莓派上也能跑!
  2. ICCV2021|面向城市场景理解的大规模3D点云挑战赛
  3. 当深度学习遇上图: 图神经网络的兴起!
  4. ECCV 2020 论文大盘点-人员重识别(ReID)篇
  5. 项目合作 | 室内场景三维重建
  6. DivideMix: Salesforce提出使用半监督学习大幅改进含噪声标签的学习
  7. mysql less6教程_Sqli labs系列-less-56 报错注入法(上)
  8. stack java实现_java实现stack
  9. 总结 | 深度学习PyTorch神经网络箱使用
  10. Error:File read error (source insight 4.0错误)