[转载]Asp.net MVC2 数据验证的扩展,结合Enterprise Library 将验证规则存入配置文件。 – Harold Shen – 博客园.
上周四写了一篇自己对ASP.NET MVC 2 数据验证的理解,觉得MVC2本身的数据验证不是很理想,如下:
1、过多的属性(Attribute)使得Model或者Action参数过于臃肿。
2、Model的复用性不好,几乎是一个Action就要建立一个Model,譬如CreateAction和 UpdateCreateAction,它们的Model大部分是类似的,但是如果这两个Action对某几项属性的验证不统一,就会导致我们不得不创建 两个Model, 这样造成了冗余和可维护性变差。
3、对Action中简单类型的参数不能做更进一步的验证,因为他们不是自定义的Model, 所以ASP.NET Mvc2 对这种类型的参数几乎只做了类型的验证,没法做更进一步的验证。
4、对于Action中简单类型的参数,如果用户的输入不能对应于参数类型,那么可能会造成ASP.NET MVC2的调用异常,譬如,Action中有一个简单类型的参数int age, Action1(int age),如果在form中用户的输入不是数字,这时会造成ASP.NET MVC2的调用异常。
5、对于Model中的某个简单类型的属性,如果用户的输入不能对应于参数类型,此时Asp.net Mvc2会自动为我们设置一个此简单类型的默认值,譬如class Model1 { Int Age { get; set;} },如果在form 中用户的输入不是数字, 这时不会造成ASP.NET MVC2的调用异常,但是它会使用MVC本身的错误消息,而开发者本身却不会知道这种情况,这其实也是数据绑定部分的一个小问题,但是这也没法避免。
6、对于Model中内嵌的复杂类型,Asp.net Mvc2不会去验证其内部的属性,譬如Class Model1 { Model2 { get;set; } } class Model2 { string P1 { get; set;} } 当验证Model1 时,Asp.net Mvc2不会去验证Model2内部P1的属性,这也意味着我们不能复用任何Model,这和问题一类似。
这些问题有很多的解决方案,在我的前一篇文章中,很多人对我提出的问题,大致都给了一些建议:
1、对于问题1 属性臃肿,有人建议使用Metadata。 对此,其实它还是要放很多的Attribute,只是放在了一个Partical类中。
2、对于问题2 Model的复用性,可能有些人都没理解我的意思,就让我去看 BO, VO, 呵呵,我真的很无奈,这里我根本就没有牵扯到什么业务逻辑,这里谈的一直
是Asp.net MVC, Asp.net MVC很多人都知道只是表现层的设计,所以这里的Model 指的是VO,当然 如果为了复用性,有些人将BO作为VO也不是不可以,因为在软件的架构中,Model是纵向的,它贯通了所有层,看下图。
3、对于问题3,有人还是说要建立Model, 不然就不是MVC了,因为没有Model了,我想你说的办法能解决问题,但是为只有一个参数的 Action就建立一个Model,有点浪费啊,还有说不用Model 就不是 MVC了,就有点言过其实了,难道只有复杂类型是Model吗?一个string 类型就不是Model?
4、对于问题4其实它也和Asp.net MVC的数据绑定有关系,有人也给出了解决方案,就是JS在客户端验证,这个办法听起来不错,但实际不是解决办法,第一,我们要搞清楚客户端验证和服务器端验证的目的,客户端验证实际是为了帮助用户填写表单,提醒用户那些该输入,该怎么输,输入数字还是日期之类,它的目的是帮助,服务器端的验证才是真正的数据验证,如果一个系统能够相信客户端JS 的验证,那么这个系统存在的问题绝对是巨大的,大家想想,如果客户端可信,那么在Asp.net中,为什么微软还要加密它的ViewState?
5、对于问题5,我们后面会说。
6、对于问题6、有人说如果验证内嵌的属性,那就不是面向对象了,看到这里我也很无奈,在Asp.net MVC的数据绑定中,如果有内嵌的属性,这些属性都会被赋值的,为什么数据绑定的时候就没人说这样就不是面向对象了,而到了验证部分,就说不是面向对象了?
后面我们来谈谈如何进行扩展
Asp.netMVC的数据验证是在绑定之后被调用的,而且只会对复杂属性进行验证,它是使用Attribute,简单类型没设置Attribute, 大家如果看过源代码应该能在DefaultModelBinder中看到如下函数:
2 // need to replace the property filter + model object and create an inner binding context
3 ModelBindingContext newBindingContext = CreateComplexElementalModelBindingContext(controllerContext, bindingContext, model);
4
5 // validation
6 if (OnModelUpdating(controllerContext, newBindingContext)) {
7 BindProperties(controllerContext, newBindingContext);
8 OnModelUpdated(controllerContext, newBindingContext);
9 }
10 }
在这个函数中,调用了OnModelUpdated,这个方法,如果想验证可以Override这个方法做验证,但是还是只能验证Action 的复杂类型参数,不能验证Action的简单类型参数,于是我就用了其它办法,大家可以再去看看DefaultModelBinder是被那个类调用的, 应该在 ControllerActionInvoker 中 能找到下面这个代码:
2 // collect all of the necessary binding properties
3 Type parameterType = parameterDescriptor.ParameterType;
4 IModelBinder binder = GetModelBinder(parameterDescriptor);
5 IValueProvider valueProvider = controllerContext.Controller.ValueProvider;
6 string parameterName = parameterDescriptor.BindingInfo.Prefix ?? parameterDescriptor.ParameterName;
7 Predicate<string> propertyFilter = GetPropertyFilter(parameterDescriptor);
8
9 // finally, call into the binder
10 ModelBindingContext bindingContext = new ModelBindingContext() {
11 FallbackToEmptyPrefix = (parameterDescriptor.BindingInfo.Prefix == null), // only fall back if prefix not specified
12 ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, parameterType),
13 ModelName = parameterName,
14 ModelState = controllerContext.Controller.ViewData.ModelState,
15 PropertyFilter = propertyFilter,
16 ValueProvider = valueProvider
17 };
18
19 object result = binder.BindModel(controllerContext, bindingContext);
20 return result ?? parameterDescriptor.DefaultValue;
21 }
看到这里,我就想在调用完这个函数之后再对每个参数做验证,经过验证,发现这完全可行,于是我就继承了 ControllerActionInvoker,但是要想让Asp.net MVC2使用我的ControllerActionInvoker,还不得不去创建一个BaseController,这样才可以,讲到这里其实我的大概 思路已经出来了,后面就是将那些Enterprise Library 的验证规则放在配置文件中了,当然遇到的问题,却不止一点点,后面还有很多,因为时间的问题,我来不及写了。我会将源代码附上,供大家参考去写你们自己的 验证。
后面是我的验证的一些效果图:
首先是一个配置文件:
2
3 <models>
4 <model name=”lm1″>
5 <properties>
6 <property name=”UserName”>
7 <validators>
8 <validator type=”Microsoft.Practices.EnterpriseLibrary.Validation.Validators.NotNullValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=null”
9 negated=”false” messageTemplate=”字段不能为空!” messageTemplateResourceName=””
10 messageTemplateResourceType=”” name=”Not Null Validator” />
11 <validator type=”Microsoft.Practices.EnterpriseLibrary.Validation.Validators.RangeValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=null”
12 culture=”zh-CN” lowerBound=”0″ lowerBoundType=”Inclusive”
13 upperBound=”10″ messageTemplate=”数值必须在0~10以内” name=”Range Validator” />
14 </validators>
15 </property>
16 <property name=”Password”>
17 <validators>
18 <validator type=”Microsoft.Practices.EnterpriseLibrary.Validation.Validators.NotNullValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=null”
19 negated=”false” messageTemplate=”字段不能为空!” messageTemplateResourceName=””
20 messageTemplateResourceType=”” name=”Not Null Validator” />
21 <validator type=”Microsoft.Practices.EnterpriseLibrary.Validation.Validators.RangeValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=null”
22 culture=”zh-CN” lowerBound=”0″ lowerBoundType=”Inclusive”
23 upperBound=”10″ messageTemplate=”数值必须在0~10以内” name=”Range Validator” />
24 </validators>
25 </property>
26 <property name=”RememberMe”>
27 <validators>
28 </validators>
29 </property>
30 <property name=”Embedded” ref=”em” />
31 </properties>
32 </model>
33
34 <model name=”em”>
35 <properties>
36 <property name=”TryCount”>
37 <validators>
38 <validator type=”Microsoft.Practices.EnterpriseLibrary.Validation.Validators.NotNullValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=null”
39 negated=”false” messageTemplate=”字段不能为空!” messageTemplateResourceName=””
40 messageTemplateResourceType=”” name=”Not Null Validator” />
41
42 <validator targetType=”System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089″ negated=”false” messageTemplate=”类型不匹配” messageTemplateResourceName=””
43 messageTemplateResourceType=”” tag=”” type=”Harold.Net.Web.Mvc.Extension.MvcTypeConversionValidator, Harold.Net.Web.Mvc.Extension” name=”Type Conversion Validator” />
44
45 <validator type=”Microsoft.Practices.EnterpriseLibrary.Validation.Validators.RangeValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=null”
46 culture=”zh-CN” lowerBound=”0″ lowerBoundType=”Inclusive”
47 upperBound=”10″ messageTemplate=”数值必须在0~10以内” name=”Range Validator” />
48 </validators>
49 </property>
50 <property name=”NonModel”>
51 <validators>
52 <validator type=”Microsoft.Practices.EnterpriseLibrary.Validation.Validators.NotNullValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=null”
53 negated=”false” messageTemplate=”字段不能为空!” messageTemplateResourceName=””
54 messageTemplateResourceType=”” name=”Not Null Validator” />
55 <validator type=”Microsoft.Practices.EnterpriseLibrary.Validation.Validators.RangeValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=null”
56 culture=”zh-CN” lowerBound=”0″ lowerBoundType=”Inclusive”
57 upperBound=”10″ messageTemplate=”数值必须在0~10以内” name=”Range Validator” />
58 </validators>
59 </property>
60 </properties>
61 </model>
62
63 </models>
64
65 <controllers>
66 <controller name=”AccountController”>
67 <actions>
68
69 <action name=”LogOn”>
70 <params>
71 <param name=”model” ref=”lm1″ />
72 <param name=”simpleArg”>
73 <validators>
74 <validator type=”Microsoft.Practices.EnterpriseLibrary.Validation.Validators.NotNullValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=null”
75 negated=”false”
76 messageTemplate=”字段不能为空!”
77 messageTemplateResourceName=””
78 messageTemplateResourceType=””
79 name=”Not Null Validator” />
80
81 <validator targetType=”System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089″ negated=”false” messageTemplate=”类型不匹配” messageTemplateResourceName=””
82 messageTemplateResourceType=”” tag=”” type=”Harold.Net.Web.Mvc.Extension.MvcTypeConversionValidator, Harold.Net.Web.Mvc.Extension” name=”Type Conversion Validator” />
83
84 <validator type=”Microsoft.Practices.EnterpriseLibrary.Validation.Validators.RangeValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=null”
85 culture=”zh-CN” lowerBound=”-1″ lowerBoundType=”Inclusive”
86 upperBound=”10″ messageTemplate=”数值必须在0~10以内” name=”Range Validator” />
87 </validators>
88 </param>
89 </params>
90 </action>
91
92 </actions>
93
94 </controller>
95 </controllers>
96
97 </aspnet.mvc.validation>
在 配置文件中,我 按照Controller -> Action -> Parameter(Reference) -> Property (Reference) -> Validators 来组织整个验证体系,这样就和我们的 Controller直接对应上了,并且,考虑到复用性,用户可以建立Model, 然后让参数去引用这个 Model。而且对简单类型的参数也能像复杂函数一样的验证了。
有人可能觉得配置文件很复杂,如果有时间我可以写一个配置器,因为Asp.net MVC 的Action函数是有规律的,很容易就能写出一个简单的配置器,我想大家应该都能做到。
下面是对 AccountController中的LogOnAction的 验证代码和效果图。
2 {
3 ….
4 }
5
6
7
8
9
10 [DisplayColumn(“UserName”, “Password”, true)]
11 public class LogOnModel
12 {
13 [DisplayName(“User name”)]
14 public string UserName { get; set; }
15
16 [DataType(DataType.Password)]
17 [DisplayName(“Password”)]
18 public string Password { get; set; }
19
20 [DisplayName(“Remember me?”)]
21 public bool RememberMe { get; set; }
22
23 public EmbeddedClass Embedded { get; set; }
24 }
25
26 public class EmbeddedClass
27 {
28 [DisplayName(“Try count”)]
29 public int TryCount { get; set; }
30
31 [DisplayName(“Non Model”)]
32 public string NonModel { get; set; }
33 }
效果图1:
效果图2:
还有最后谈一点对有些人的评论,评论可以,建议别人看某项技术也是好的,但是不要自以为是的让别人学学这个,学学哪个,那个口气让人听了很不舒服。抛砖引玉之文,希望活跃大家的想法。
源代码:http://files.cnblogs.com/harold/Harold.Net.Web.Mvc.Extension.zip