ASP.NET MVC基于标注特性的Model验证:将ValidationAttribute应用到参数上
原文:ASP.NET MVC基于标注特性的Model验证:将ValidationAttribute应用到参数上
ASP.NET MVC默认采用基于标准特性的Model验证机制,但是只有应用在Model类型及其属性上的ValidationAttribute才有效。如果我们能够将ValidationAttribute特性直接应用到参数上,我们不但可以实现简单类型(比如int、double等)数据的Model验证,还能够实现“一个Model类型,多种验证规则”,本篇文章将为你提供相关的解决方案(源代码从这里下载)。[本文已经同步到《How ASP.NET MVC Works?》中]
目录
一、ValidationAttribute本身是可以应用到参数上的
二、为什么需要基于参数的Model验证?
三、如何得到应用在参数上的ValidationAttribute?
四、自定义ModelValidatorProvider
五、自定义ModelBinder
六、实例演示
一、ValidationAttribute本身是可以应用到参数上的
如果你够细心应该会发现我们常用的验证特性都可以直接应用到方法的参数上。以如下所示的RangeAttribute的定义为例,应用在该类型上的AttributeUsageAttribute的定义表明可以标注该特性的目标元素包括参数、字段和属性。
1: [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property,AllowMultiple=false)]
2: public class RangeAttribute : ValidationAttribute
3: {
4: //省略成员
5: }
但是对于ASP.NET MVC的Model验证来说,应用在Action方法参数上的验证特性起不到任何作用,原因很简单:用于进行Model验证的ModelValidator对象是通过基于参数类型的Model元数据来创建的,根本不会去解析应用在参数本身上的验证特性。
二、为什么需要基于参数的Model验证?
但是在我看到,直接针对Action参数的Model验证具有很高的实用意义:
- 有些情况下我们不能对作为Model的数据类型进行修改(比如像int、double和字符串这样的原生类型);
- 相同的Model类型在不同的Action方法调用中需要采用不同的验证规则。
如果我们可以直接将验证特性应用到参数上面,这两个问题在一定程度上都可以得到解决。
三、如何得到应用在参数上的ValidationAttribute?
到目前为止,我们对ASP.NET MVC的可扩展的Model验证系统已经有了一个全面的了解,现在我们通过对它进行相应的扩展使直接应用到参数上的验证特性能够生效。我们需要自定义一个ModelValidatorProvider将提供基于应用到参数上的验证特性的ModelValidator,但在这之前需要解决的另一个问题是如何将应用于参数的特性提供给我们自定义的ModelValidatorProvider。在这里我们将当前ControllerContext作为这些特性的载体。
Action方法的执行通过ActionInvoker来实现,默认的ControllerActionInvoker和AsyncControllerActionInvoker都定义了一个受保护的虚方法GetParameterValue根据用于描述参数的ParameterDescriptor对象和当前的Controller上下文来绑定对应的参数值。那么我们就可以通过继承ControllerActionInvoker/AsyncControllerActionInvoker以重写该方法的方式将ParameterDescriptor保存当前的Controller上下文中。
为此我们定义了一个具有如下定义的两个自定义的ActionInvoker。ParameterValidationActionInvoker和ParameterValidationAsyncActionInvoker分别继承自ControllerActionInvoker和AsyncControllerActionInvoker。在重写的GetParameterValue方法中,我们在调用基类的同名方法之前将作为参数的ParameterDescriptor对象保存到当前Controller上下文中,具体来说是放到了表示当前路由数据的RouteDataDictionary对象的DataTokens集合中。在方法调用之后我们将它从Controller上下文中移除。
1: public class ParameterValidationActionInvoker : ControllerActionInvoker
2: {
3: protected override object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor)
4: {
5: try
6: {
7: controllerContext.RouteData.DataTokens.Add("ParameterDescriptor",parameterDescriptor);
8: return base.GetParameterValue(controllerContext, parameterDescriptor);
9: }
10: finally
11: {
12: controllerContext.RouteData.DataTokens.Remove("ParameterDescriptor");
13: }
14: }
15: }
16:
17: public class ParameterValidationAsyncActionInvoker : AsyncControllerActionInvoker
18: {
19: protected override object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor)
20: {
21: try
22: {
23: controllerContext.RouteData.DataTokens.Add("ParameterDescriptor", parameterDescriptor);
24: return base.GetParameterValue(controllerContext, parameterDescriptor);
25: }
26: finally
27: {
28: controllerContext.RouteData.DataTokens.Remove("ParameterDescriptor");
29: }
30: }
31: }
四、自定义ModelValidatorProvider
ParameterValidationActionInvoker/ParameterValidationAsyncActionInvoker存放到当前Controller上下文中的ParameterDescriptor被我们自定义的ModelValidatorProvider提取出来用于创建相应的ModelValidator。如下面的代码片断所示,我们自定义的ParameterValidationModelValidatorProvider直接继承自DataAnnotationsModelValidatorProvider,在重写的GetValidators方法中我们将ParameterDescriptor从Controller上下文中提取出来,然后得到应用在参数上的所有的特性并与当前的特性列表进行合并,最后将合并的特性列表作为参数调用积累的GetValidators方法。
1: public class ParameterValidationModelValidatorProvider : DataAnnotationsModelValidatorProvider
2: {
3: protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
4: {
5: object descriptor;
6: if (metadata.ContainerType == null && context.RouteData.DataTokens.TryGetValue("ParameterDescriptor", out descriptor))
7: {
8: ParameterDescriptor parameterDescriptor = (ParameterDescriptor)descriptor;
9: DisplayAttribute displayAttribute = parameterDescriptor.GetCustomAttributes(true).OfType<DisplayAttribute>().FirstOrDefault()
10: ?? new DisplayAttribute { Name = parameterDescriptor.ParameterName };
11: metadata.DisplayName = displayAttribute.Name;
12: var addedAttributes = parameterDescriptor.GetCustomAttributes(true).OfType<Attribute>();
13: return base.GetValidators(metadata, context, attributes.Union(addedAttributes));
14: }
15: else
16: {
17: return base.GetValidators(metadata, context, attributes);
18: }
19: }
20: }
值得一提的是,应用在参数上的特性是针对最外层的容器类型,而不是针对容器类型的属性的。比如所以我们在类型为Contact的参数上应用一个验证特性,该特性应该与应用在Contact类型上的特性具有相同的效果,但是与Address属性无关。所以ParameterDescriptor的提取以及特性的合并仅仅在当前Model元数据的ContainerType为Null的情况下才会进行。除此之外,我们还利用应用到参数的DisplayAttribute特性对Model元数据的DisplayName属性进行了相应的设置。
五、自定义ModelBinder
在默认的情况下,只有在针对复杂类型的Model绑定过程中才会进行Model验证。虽然我们通过ParameterValidationModelValidatorProvider能够根据应用在Action方法参数上的验证特性生成相应的ModelValidator,但是如果验证特性是应用在一个简单类型的参数上,生成出来的ModelValidator也是不会被使用的。为了使Model验证发生在针对简单类型的Model绑定过程中,我们不得不创建一个自定义的ModelBinder。为此我们定义了一个具有如下定义的ParameterValidationModelBinder,它直接继承自DefaultModelBinder,而针对简单类型的Model验证定义在重写的BindModel方法中。
1: public class ParameterValidationModelBinder : DefaultModelBinder
2: {
3: public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
4: {
5: object model = bindingContext.ModelMetadata.Model = base.BindModel(controllerContext, bindingContext);
6: ModelMetadata metadata = bindingContext.ModelMetadata;
7: if (metadata.IsComplexType || null == model)
8: {
9: return model;
10: }
11:
12: Dictionary<string, bool> dictionary = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
13: foreach (ModelValidationResult result in ModelValidator.GetModelValidator(metadata, controllerContext).Validate(null))
14: {
15: string key = bindingContext.ModelName;
16: if (!dictionary.ContainsKey(key))
17: {
18: dictionary[key] = bindingContext.ModelState.IsValidField(key);
19: }
20: if (dictionary[key])
21: {
22: bindingContext.ModelState.AddModelError(key, result.Message);
23: }
24: }
25: return model;
26: }
27: }
到此为止,为了能够将验证特性应用于Action方法的参数,我们创建了自定义的ActionInvoker、ModelValidatorProvider和ModelBinder。为了验证它们是否能够最终实现我们期望的验证效果,我们将它们应用到一个简单的ASP.NET MVC应用中。
六、实例演示
在通过Visual Studio的ASP.NET MVC项目模板创建的空的Web应用中,我们创建了一个具有如下定义的HomeController。我们重写了CreateActionInvoker方法,如果调用基类同名方法返回一个ControllerActionInvoker对象,那么我们返回一个ParameterValidationActionInvoker对象,否则返回一个ParameterValidationAsyncActionInvoker对象,这是与默认的同步/异步Action执行方式保持一致。
1: public class HomeController : Controller
2: {
3: protected override IActionInvoker CreateActionInvoker()
4: {
5: IActionInvoker actionInvoker = base.CreateActionInvoker();
6: if (actionInvoker is ControllerActionInvoker)
7: {
8: return new ParameterValidationActionInvoker();
9: }
10: else
11: {
12: return new ParameterValidationAsyncActionInvoker();
13: }
14: }
15:
16: public ActionResult Add(
17: [Range(10, 20, ErrorMessage="{0}必须在{1}和{2}之间!")]
18: [ModelBinder(typeof(ParameterValidationModelBinder))]
19: [Display(Name = "第一个操作数")]
20: double x,
21:
22: [Range(20, 30,ErrorMessage="{0}必须在{1}和{2}之间!")]
23: [ModelBinder(typeof(ParameterValidationModelBinder))]
24: [Display(Name = "第二个操作数")]
25: double y)
26: {
27: return View(x + y);
28: }
29: }
Action方法Add表示一个用于进行加法运算的操作,表示操作数的两个参数x和y分别应用了一个RangeAttribute特性将允许值得范围设置为10到20和20到30,并设置了相应的错误消息。此外,两个参数还通过应用ModelBinderAttribute特性使我们自定义的ParameterValidationModelBinder参与到这两个参数Model绑定中。DisplayAttribute特性也应用到这两个参数上对显示名称进行了相应的设置。作于执行加法运算后的结果通过默认的View呈现出来。下面的代码片断表示Action方法Add对应的View的定义,这是一个Model类型为double的强类型View。我们通过一个ValidationSummary来呈现验证的错误消息,只有在验证成功的情况下我们才真正显示运算的结果。
1: @model double
2: @Html.ValidationSummary()
3: @{
4: if(ViewData.ModelState.IsValid)
5: {
6: @:运算结果:@Model
7: }
8: }
然后我们在Global.asax中对自定义的ParameterValidationModelValidatorProvider进行注册。如下面的代码片断所示,在注册ParameterValidationModelValidatorProvider之前需要将现有的DataAnnotationsModelValidatorProvider移除。
1: public class MvcApplication : System.Web.HttpApplication
2: {
3: //其他成员
4: protected void Application_Start()
5: {
6: //其他操作
7: DataAnnotationsModelValidatorProvider validatorProvider = ModelValidatorProviders.Providers
8: .OfType<DataAnnotationsModelValidatorProvider>().FirstOrDefault();
9: if (null != validatorProvider)
10: {
11: ModelValidatorProviders.Providers.Remove(validatorProvider);
12: }
13: ModelValidatorProviders.Providers.Add(new ParameterValidationModelValidatorProvider());
14: }
15: }
我们运行该程序通过在浏览器中输入相应的地址来访问定义在HomeController中的Add操作,并以查询字符串的形式指定该Action方法的两个操作数(x=9,y=31)。由于提供的参数不服务应用在参数上的 RangeAttribute所定义的验证规则,如下图所示的错误消息会自动呈现出来。
ASP.NET MVC基于标注特性的Model验证:ValidationAttribute
ASP.NET MVC基于标注特性的Model验证:DataAnnotationsModelValidator
ASP.NET MVC基于标注特性的Model验证:DataAnnotationsModelValidatorProvider
ASP.NET MVC基于标注特性的Model验证:将ValidationAttribute应用到参数上
ASP.NET MVC基于标注特性的Model验证:一个Model,多种验证规则
ASP.NET MVC基于标注特性的Model验证:将ValidationAttribute应用到参数上的更多相关文章
- ASP.NET MVC基于标注特性的Model验证:一个Model,多种验证规则
原文:ASP.NET MVC基于标注特性的Model验证:一个Model,多种验证规则 对于Model验证,理想的设计应该是场景驱动的,而不是Model(类型)驱动的,也就是对于同一个Model对象, ...
- [ASP.NET MVC 小牛之路]16 - Model 验证
上一篇博文 [ASP.NET MVC 小牛之路]15 - Model Binding 中讲了MVC在Model Binding过程中如何根据用户提交HTTP请求数据创建Model对象.在实际的项目中, ...
- 【ASP.NET MVC 学习笔记】- 17 Model验证
本文参考:http://www.cnblogs.com/willick/p/3434483.html 1.Model验证用于在实际项目中对用户提交的表单的信息进行验证,MVC对其提供了很好的支持. 2 ...
- 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 5 学习教程:添加验证
原文 ASP.NET MVC 5 学习教程:添加验证 起飞网 ASP.NET MVC 5 学习教程目录: 添加控制器 添加视图 修改视图和布局页 控制器传递数据给视图 添加模型 创建连接字符串 通过控 ...
- 【译】ASP.NET MVC 5 教程 - 10:添加验证
原文:[译]ASP.NET MVC 5 教程 - 10:添加验证 在本节中,我们将为Movie模型添加验证逻辑,并确认验证规则在用户试图使用程序创建和编辑电影时有效. DRY 原则 ASP.NET M ...
- [ASP.NET MVC]如何定制Numeric属性/字段验证消息
原文:[ASP.NET MVC]如何定制Numeric属性/字段验证消息 对于一个Numeric属性/字段,ASP.NET MVC会自动进行数据类型的验证(客户端验证),以确保输入的是一个有效的数字, ...
- [转][ASP.NET MVC]如何定制Numeric属性/字段验证消息
本文转自:http://www.cnblogs.com/artech/archive/2012/02/13/NumericPropertyValidation.html 对于一个Numeric属性/字 ...
随机推荐
- Quartz.net开源作业调度
Quartz.net开源作业调度框架使用详解 前言 quartz.net作业调度框架是伟大组织OpenSymphony开发的quartz scheduler项目的.net延伸移植版本.支持 cron- ...
- 提升Mac os x 10.10+xcode6.1之后,Cocoapods发生故障的解决方案
提升Mac OS X 10.10+Xcode 6.1之后.Cocoapods图书馆管理也依赖于相应升级.现在最新的Release版本号是 0.34.在之前的版本号.当数据库更新和管理,你会遇到一个错误 ...
- 移动端 常见布局CSS3的细节
结合 Framework7 和ios UI系统,微信weUI,支付宝H5 我们在移动端一些css用法 细节的有了更深的了解: 高斯模糊的显示效果,ios8以上支持,ios8以上0.5px,bac ...
- 百度地图 iOS SDK - 坐标转换方法
百度地图 Android SDK 要么 iOS SDK 或各种 API 工具产品,我们使用百度自己的加密坐标系. 员在使用过程中,位置点都是通过 GPS 或者其它途径获取的.所以与百度地图所使用的坐标 ...
- SQL Server 作业监控
原文:SQL Server 作业监控 在讲解SQLServer Agent Jobs之前,先要讲解msdb. Msdb是SQLServer的系统数据库之一,用于存储SQLServer的配置.元数据等信 ...
- SAP ABAP 它已被释放TR(或任务),减少的变化TR(任务),删除释放TR(任务)
有时,我们会遇到将是一个TR以下任务task发布,然后想改变,或不想转移TR. 或想删除已释放TR. 研究发现后面,TR(任务)存储在数据库表汇的相应数据:E070(变化 & 交通系统: 求/ ...
- linux 使用外部设备的(光盘) 安装和更新库
1. 安装光盘(文件夹不存在,创建) mount -t auto /dev/cdrom /mnt/cdrom 2. 更改索引文件,指定读取本地文件 vi /etc/yum.repos.d/CentOS ...
- C#名单:一个简单的实现
C#它配备了一个泛型列表类,在很多情况下,足以.实际应用中遇到.最好的报价C#该链表,包装成自己的阶级需求. 该名单的努力的原则,基本实现探索实施一些简单的方法. 一个.(Node.cs文件)作为一类 ...
- Git常用操作汇总(转)
如果一个文件被删除了,可以使用切换版本号进行恢复.恢复方法: 先确定需要恢复的文件要恢复成哪一个历史版本(commit),假设那个版本号是: commit_id,那么 git checkout com ...
- Websocket实例
C#版Websocket实例 websocket有java.nodejs.python,Php等等版本,我使用的是C#版本,服务器端是Fleck,github地址:https://github.com ...