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属性/字 ...
随机推荐
- Android - 直线(line)画法
Android - 直线(line)画法 本文地址: http://blog.csdn.net/caroline_wendy 横线(horizontal line) <View android: ...
- Objective-C路成魔【2-Objective-C 规划】
郝萌主倾心贡献,尊重作者的劳动成果,请勿转载. 假设文章对您有所帮助,欢迎给作者捐赠,支持郝萌主,捐赠数额任意,重在心意^_^ 我要捐赠: 点击捐赠 Cocos2d-X源代码下载:点我传送 编译执行O ...
- SQLHlper意识
经过学习,通过线敲登录的三个例子,敲四行CRUD样品,因此,访问数据库多次,在这些链接库.打开都一样.只是不同的操作将针对不同的表进行.始学习面向对象的思想.当让要对这些不变的要内容要进行打包,提高代 ...
- Canvas的方法覆盖和实现新的API
有时候为了特定需求或者特殊目的,需要覆盖或者重写某个API,在Canvas中,就需要用到CanvasRenderingContext2D这个对象. CanvasRenderingContext2D为C ...
- Linux
介绍了相关的文件夹
学习Linux,先了解Linux标准的文件夹结构是非常重要的,学习的一个很好的铺垫之后的行动.去罗列大而全的所有文件夹. 本文主要说明.笔者本人接触,须要了解的一些文件文件夹,当然本文也会不断的更新或 ...
- .net卸载程序制作
原文:.net卸载程序制作 方法一: 在打包项目中添加文件msiexec.exe(一般在c:\windows\system32(系统目录中)找到). 在文件系统视图中选择应用程序文件,在msiexec ...
- robot framework环境搭建(转)
一. robot framework环境搭建: 官网:http://robotframework.org/ 序号 安装包名 安装方法 下载地址 备注 1 python exe文件,直接双击安装 htt ...
- Nyoj 吝啬的国度(图论&&双DFS)
描述在一个吝啬的国度里有N个城市,这N个城市间只有N-1条路把这个N个城市连接起来.现在,Tom在第S号城市,他有张该国地图,他想知道如果自己要去参观第T号城市,必须经过的前一个城市是几号城市(假设你 ...
- 小工具:内存监视器(SystemMonitor)
卸了360之后,与之捆绑的加速球也没了.加速球可以查看剩余内存量,清理残留进程,有的时候不觉得这小玩意有多大作用,卸了之后才知道后悔. 加速球的替代方案比比皆是,如Windows自带的任务管理器,窗口 ...
- C# 带用户密码访问网络共享
原文:C# 带用户密码访问网络共享 调用WNetUseConnection API 函数详细参数参考:https://msdn.microsoft.com/en-us/library/windows/ ...