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

原文:ASP.NET MVC基于标注特性的Model验证:一个Model,多种验证规则

对于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: [ValidationRule("Rule3")]
   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:     [ValidationRule("Rule1")]
  15:     public ActionResult Rule1()
  16:     {
  17:         return View("person", new Person());
  18:     }
  19:     [HttpPost]
  20:     [ValidationRule("Rule1")]
  21:     public ActionResult Rule1(Person person)
  22:     {
  23:         return View("person", person);
  24:     }
  25:  
  26:     [ValidationRule("Rule2")]
  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: }

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

时间: 2024-11-08 18:00:27

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

ASP.NET MVC基于标注特性的Model验证:将ValidationAttribute应用到参数上

原文:ASP.NET MVC基于标注特性的Model验证:将ValidationAttribute应用到参数上 ASP.NET MVC默认采用基于标准特性的Model验证机制,但是只有应用在Model类型及其属性上的ValidationAttribute才有效.如果我们能够将ValidationAttribute特性直接应用到参数上,我们不但可以实现简单类型(比如int.double等)数据的Model验证,还能够实现“一个Model类型,多种验证规则”,本篇文章将为你提供相关的解决方案(源代码

[转][译]ASP.NET MVC 4 移动特性

此教程将讨论ASP.NET MVC 4 Web应用程序里的移动特性.对于此教程,可以使用 Visual Studio Express 2012 或者 Visual Web Developer 2010 Express Service Pack 1 ("Visual Web Developer 或者 VWD").  如果你已经有了专业版本的 Visual Studio 你也可以使用. 开始之前,确保你已经安装了以下列出的必需项. Visual Studio Express 2012 (推

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

转发:微软MVP 卢建晖 的文章,希望对大家有帮助.原文:http://blog.csdn.net/kinfey/article/details/44459625 编者语 : 昨晚写好的文章居然csdn不审核,这个也难怪人,但自己比较忙没办法.分享继续,今天谈ASP.NET MVC 6. 我蛮喜欢Ruby On Rails 这种约定胜于配置的框架,在.NET 有ASP.NET MVC 和Java有Play! Framework .  ASP.NET MVC 版本基本上每年一更新,从不让你失望.我

ASP.net MVC 基于角色的权限控制系统的实现

一.引言 我们都知道ASP.net mvc权限控制都是实现AuthorizeAttribute类的OnAuthorization方法. 下面是最常见的实现方式: public class CustomAuthorizeAttribute : AuthorizeAttribute { public override void OnAuthorization(AuthorizationContext filterContext) { if (!filterContext.RequestContext

ASP.NET MVC 基于角色的权限控制系统的示例教程

上一次在 .NET MVC 用户权限管理示例教程中讲解了ASP.NET MVC 通过AuthorizeAttribute类的OnAuthorization方法讲解了粗粒度控制权限的方法,接下来讲解基于角色的权限控制方法. 基于角色的权限控制方法概述 基于角色的权限控制系统RBAC(Role Based Access Control)是目前最流行,也是最通用的权限控制系统.所谓基于角色的权限控制,就是将各个操作权限分组,每一个组就是一个角色,举个例子:管理员拥有所有的权限,编辑就只拥有写文章和发布

微软下一代网站开发框架:ASP.NET MVC 6 新特性揭秘

 国内第一个<微软下一代网站开发框架:ASP.NET MVC 6 新特性揭秘 >课程 微软特邀讲师 徐雷!周六晚8点YY预定:http://t.cn/RPKMLGF 微软Visual Studio 2014 即将发布!ASP.NET MVC 6有什么新特性? Web API 3.0有什么新变化? 什么是动态编译?什么是原生编译?@微软中国MSDN 收起|查看大图|向左转|向右转 微软下一代网站开发框架:ASP.NET MVC 6 新特性揭秘

ASP.NET MVC的Ajax.ActionLink 的HttpMethod=&quot;Get&quot; 一个重复请求的BUG

这段时间使用BootStrap+Asp.net Mvc5开发项目,Ajax.ActionLink遇到一个重复提交的BUG,代码如下: 1 @model IList<WFModel.WF_Temp> 2 @{ 3 Layout = null; 4 } 5 6 <!DOCTYPE html> 7 8 <html> 9 <head> 10 <meta name="viewport" content="width=device-w

ASP.NET MVC 使用Remote特性实现远程属性验证

RemoteAttribute是asp.net mvc 的一个验证特性,它位于System.Web.Mvc命名空间 下面通过例子来说明 很多系统中都有会员这个功能,会员在前台注册时,用户名不能与现有的用户名重复,还要求输入手机号码去注册,同时手机号码也需要验证是否重复,下面是实体类 /// <summary> /// 会员 /// </summary> public class Member { public int Id { get; set; } [Required(Error

ASP.NET MVC中MaxLength特性设置无效

在ASP.NET MVC项目中,给某个Model打上了MaxLength特性如下: public class SomeClass { [MaxLength(16, ErrorMessage = "最大长度16")] public string SomeProperty{get;set;} } 但在其对应的表单元素中并没有出现类似data-val-length属性. 解决办法:使用StringLength替代MaxLength. public class SomeClass { [Str