原文: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,多种验证规则的更多相关文章
-
ASP.NET MVC基于标注特性的Model验证:将ValidationAttribute应用到参数上
原文:ASP.NET MVC基于标注特性的Model验证:将ValidationAttribute应用到参数上 ASP.NET MVC默认采用基于标准特性的Model验证机制,但是只有应用在Model ...
-
ASP.NET MVC 基于角色的权限控制系统的示例教程
上一次在 .NET MVC 用户权限管理示例教程中讲解了ASP.NET MVC 通过AuthorizeAttribute类的OnAuthorization方法讲解了粗粒度控制权限的方法,接下来讲解基于 ...
-
微软下一代站点开发框架:ASP.NET MVC 6 新特性揭秘
国内第一个<微软下一代站点开发框架:ASP.NET MVC 6 新特性揭秘 >课程 微软特邀讲师 徐雷!周六晚8点YY预定:id=28447" href="htt ...
-
ASP.NET MVC 使用Remote特性实现远程属性验证
RemoteAttribute是asp.net mvc 的一个验证特性,它位于System.Web.Mvc命名空间 下面通过例子来说明 很多系统中都有会员这个功能,会员在前台注册时,用户名不能与现有的 ...
-
(转) 一步一步学习ASP.NET 5 (四)- ASP.NET MVC 6四大特性
转发:微软MVP 卢建晖 的文章,希望对大家有帮助.原文:http://blog.csdn.net/kinfey/article/details/44459625 编者语 : 昨晚写好的文章居然csd ...
-
ASP.NET MVC中MaxLength特性设置无效
在ASP.NET MVC项目中,给某个Model打上了MaxLength特性如下: public class SomeClass { [MaxLength(16, ErrorMessage = &qu ...
-
[转][译]ASP.NET MVC 4 移动特性
此教程将讨论ASP.NET MVC 4 Web应用程序里的移动特性.对于此教程,可以使用 Visual Studio Express 2012 或者 Visual Web Developer 2010 ...
-
Asp.net MVC 基于Area的路由映射
对于一个较大规模的Web应用,我们可以从功能上通过Area将其划分为较小的单元.每个Area相当于一个独立的子系统,具有一套包含Models.Views和Controller在内的目录结构和配置文件. ...
-
ASP.net MVC 基于角色的权限控制系统的实现
一.引言 我们都知道ASP.net mvc权限控制都是实现AuthorizeAttribute类的OnAuthorization方法. 下面是最常见的实现方式: public class Custom ...
随机推荐
-
理解伪元素 :Before 和 :After
层叠样式表(CSS)的主要目的是给HTML元素添加样式,然而,在一些案例中给文档添加额外的元素是多余的或是不可能的.事实上CSS中有一个特性允许我们添加额外元素而不扰乱文档本身,这就是“伪元素”. 你 ...
-
c#反射获取常量属性名以及其值(真正可用)
最近因为要开发rpc平台的c#客户端,其中部分常量类为了自动加载的map,需要反射解析出静态常量,往上搜了一堆,都各种的不靠谱. 亲自研究了下,如下: Type t = typeof(SpiderEr ...
-
Oracle 错误码
Oracle作为一款比较优秀同时也比较难以掌握的大型数据库,在我们学习使用的过程中,不可避免的会遇到一些错误,为此 Oracle 给出了一套完备的错误消息提示机制 我们可以根据Oracle给出的消息提 ...
-
SQL数据库中查询中加N&#39;&#39; 前缀是什么意思
It's declaring the string as nvarchar data type, rather than varchar You may have seen Transact-SQL ...
-
web print
<!doctype html> <html lang="en"> <head> <meta charset="utf-8&quo ...
-
去除inline-block出现间距的几种方法
display:inline-block,简单来说就是将对象呈现为inline对象,但是对象的内容作为block对象呈现,之后的内联对象会排列在同一行 比如两个input,默认中间会产生一些间距 &l ...
-
翻新并行程序设计的认知整理版(state of the art parallel)
近几年,业内对并行和并发积累了丰富的经验.有了较深刻的理解.但之前积累的大量教材,在当今的软硬件体系下.反而都成了负面教材.所以,有必要加强宣传,翻新大家的认知. 首先.天地倒悬,结论先行:当你须要并 ...
-
Hg(Mercurial)版本管理学习
1.关闭分支,首先切到你要关闭的分支 hg commit --close-branch -m. 2.仓库ip地址改变之后,重设仓库ip 找到.hg文件夹 - hgrc文件 - 记事本打开重设 3.推分 ...
-
Tensorflow学习笔记一
今天开始正式学习Tensorflow, 首先从源码开始, 装好了CentOS 7 X64, 并且安装了桌面版本, 计划能够构建Tensorflow成功 首先从Github从Fork了一个版本到我的Gi ...
-
MySQL 事务没有提交导致 锁等待 Lock wait timeout exceeded
java.lang.Exception: ### Error updating database. Cause: java.sql.SQLException: Lock wait timeout e ...