ASP.NET MVC基于标注特性的Model验证:将ValidationAttribute应
ASP.NET MVC默认采用基于标准特性的Model验证机制,但是只有应用在Model类型及其属性上的ValidationAttribute才有效。如果我们能够将ValidationAttribute特性直接应用到参数上,我们不但可以实现简单类型(比如int、double等)数据的Model验证,还能够实现“一个Model类型,多种验证规则”,本篇文章将为你提供相关的解决方案(源代码从这里下载)。[本文已经同步到《How ASP.NET MVC Works?》中]
一、ValidationAttribute本身是可以应用到参数上的如果你够细心应该会发现我们常用的验证特性都可以直接应用到方法的参数上。以如下所示的RangeAttribute的定义为例,应用在该类型上的AttributeUsageAttribute的定义表明可以标注该特性的目标元素包括参数、字段和属性。 1: [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property,AllowMultiple=false)] 3: { 5: } 但是对于ASP.NET MVC的Model验证来说,应用在Action方法参数上的验证特性起不到任何作用,原因很简单:用于进行Model验证的ModelValidator对象是通过基于参数类型的Model元数据来创建的,根本不会去解析应用在参数本身上的验证特性。 二、为什么需要基于参数的Model验证?但是在我看到,直接针对Action参数的Model验证具有很高的实用意义:
如果我们可以直接将验证特性应用到参数上面,这两个问题在一定程度上都可以得到解决。 三、如何得到应用在参数上的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上下文中移除。 2: { 4: { 6: { 8: return base.GetParameterValue(controllerContext,parameterDescriptor); 10: finally
12: controllerContext.RouteData.DataTokens.Remove("ParameterDescriptor");
14: } 16:? 18: { 21: 22: {
26: 27: {
30: } class ParameterValidationModelValidatorProvider : DataAnnotationsModelValidatorProvider 4: { 6: if (metadata.ContainerType == null && context.RouteData.DataTokens.TryGetValue(out descriptor)) 8: ParameterDescriptor parameterDescriptor = (ParameterDescriptor)descriptor; 10: ?? new DisplayAttribute { Name = parameterDescriptor.ParameterName };
12: var addedAttributes = parameterDescriptor.GetCustomAttributes(true).OfType<Attribute>();
14: } 16: { 18: } 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方法中。 object BindModel(ControllerContext controllerContext,ModelBindingContext bindingContext)
6: ModelMetadata metadata = bindingContext.ModelMetadata; 8: { 10: } 12: Dictionary<string,1)">bool> dictionary = new Dictionary<bool>(StringComparer.OrdinalIgnoreCase); 14: { 16: if (!dictionary.ContainsKey(key))
18: dictionary[key] = bindingContext.ModelState.IsValidField(key); 20: if (dictionary[key])
22: bindingContext.ModelState.AddModelError(key,result.Message); 24: } 27: } 到此为止,为了能够将验证特性应用于Action方法的参数,我们创建了自定义的ActionInvoker、ModelValidatorProvider和ModelBinder。为了验证它们是否能够最终实现我们期望的验证效果,我们将它们应用到一个简单的ASP.NET MVC应用中。 六、实例演示在通过Visual Studio的ASP.NET MVC项目模板创建的空的Web应用中,我们创建了一个具有如下定义的HomeController。我们重写了CreateActionInvoker方法,如果调用基类同名方法返回一个ControllerActionInvoker对象,那么我们返回一个ParameterValidationActionInvoker对象,否则返回一个ParameterValidationAsyncActionInvoker对象,这是与默认的同步/异步Action执行方式保持一致。 override IActionInvoker CreateActionInvoker()
if (actionInvoker is ControllerActionInvoker)
else 15:? 17: [Range(10,20,ErrorMessage="{0}必须在{1}和{2}之间!")]
19: [Display(Name = "第一个操作数")]
21:? 23: [ModelBinder(typeof(ParameterValidationModelBinder))]
double y) 27: return View(x + y);
29: } Action方法Add表示一个用于进行加法运算的操作,表示操作数的两个参数x和y分别应用了一个RangeAttribute特性将允许值得范围设置为10到20和20到30,并设置了相应的错误消息。此外,两个参数还通过应用ModelBinderAttribute特性使我们自定义的ParameterValidationModelBinder参与到这两个参数Model绑定中。DisplayAttribute特性也应用到这两个参数上对显示名称进行了相应的设置。作于执行加法运算后的结果通过默认的View呈现出来。下面的代码片断表示Action方法Add对应的View的定义,这是一个Model类型为double的强类型View。我们通过一个ValidationSummary来呈现验证的错误消息,只有在验证成功的情况下我们才真正显示运算的结果。 2: @Html.ValidationSummary()
4: if(ViewData.ModelState.IsValid) 6: @:运算结果:@Model 8: } 然后我们在Global.asax中对自定义的ParameterValidationModelValidatorProvider进行注册。如下面的代码片断所示,在注册ParameterValidationModelValidatorProvider之前需要将现有的DataAnnotationsModelValidatorProvider移除。 //其他成员
5: { 7: DataAnnotationsModelValidatorProvider validatorProvider = ModelValidatorProviders.Providers 9: if (null != validatorProvider) 11: ModelValidatorProviders.Providers.Remove(validatorProvider); 13: ModelValidatorProviders.Providers.Add(new ParameterValidationModelValidatorProvider());
相关内容
推荐文章
站长推荐
热点阅读
|