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