最近几天看了一下Asp.net MVC2的model验证,初始的感觉让我眼前一亮,于是去看了看它的源代码,Validation和Metadata部分应该是从Dynamic Data哪里得到的灵感,一切似乎都那么美好,网上关于如何使用的文章页很多,但是随后的一段经历让我对Validation部分的代码和设计产生了很大的不满,它所夸赞的可以很容易与其它验证框架的集成其实也并不是那么容易,下面我就来说说为什么我会对它不满:
当前的MVC的验证是在Model的属性上加入Attribute,如下:
代码
1
2 [DisplayColumn("UserName", "Password", true)]
3 public class LogOnModel
4 {
5 [Required]
6 [DisplayName("User name")]
7 public string UserName { get; set; }
8
9 [Required]
10 [DataType(DataType.Password)]
11 [DisplayName("Password")]
12 public string Password { get; set; }
13
14 [DisplayName("Remember me?")]
15 public bool RememberMe { get; set; }
16
17 public EmbeddedClass Embedded { get; set; }
18 }
19
20 public class EmbeddedClass
21 {
22 [DisplayName("Try count")]
23 [Range(3, 5)]
24 public int TryCount { get; set; }
25
26 [DisplayName("Non Model")]
27 public string NonModel { get; set; }
28 }
但是随之而来的问题也来了,那就是
1。 过多的属性(Attribute)使得Model过于臃肿,
2。一个Model代表的是一个类,它的实例应该有自己的Attribute, 如果是把Attribute加在了Model上,那么所有的实例只能使用同样的Attribute,在我们的程序中很多的类经常是在很多函数中使用,并且每个函数会有不同的验证要求,
3。 如果是简单类型的参数,就不能做一些验证了,譬如范围验证。
4。 如果类中的属性是非值类型的(非值类型,不包括string),那么验证并不会去验证这个属性实例内部的属性,譬如我上面的Embedded 属性。
这些让我有了一个自己的想法,所以于是想把这些验证放在配置文件中,于是仔细的看了MVC2绑定和验证部分的源代码,想看看如何扩展。我比较懒,DefaultModelBinder 这个类是asp.net mvc2 中用于绑定数据和调用验证的最重要的类,其中Model的验证部分是在 函数OnModelUpdated中被调用的,
1 protected virtual void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext) {
2 Dictionary<string, bool> startedValid = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
3
4 foreach (ModelValidationResult validationResult in ModelValidator.GetModelValidator(bindingContext.ModelMetadata, controllerContext).Validate(null)) {
5 string subPropertyName = CreateSubPropertyName(bindingContext.ModelName, validationResult.MemberName);
6
7 if (!startedValid.ContainsKey(subPropertyName)) {
8 startedValid[subPropertyName] = bindingContext.ModelState.IsValidField(subPropertyName);
9 }
10
11 if (startedValid[subPropertyName]) {
12 bindingContext.ModelState.AddModelError(subPropertyName, validationResult.Message);
13 }
14 }
15 }
在这个函数中,ModelValidator.GetModelValidator(bindingContext.ModelMetadata, controllerContext),有这么一句,也就是获得ModelValidator, 悲剧就在此函数中打开这个函数看看:
代码
1 public static ModelValidator GetModelValidator(ModelMetadata metadata, ControllerContext context) {
2 return new CompositeModelValidator(metadata, context);
3 }
4
5 public abstract IEnumerable<ModelValidationResult> Validate(object container);
6
7 private class CompositeModelValidator : ModelValidator {
8 public CompositeModelValidator(ModelMetadata metadata, ControllerContext controllerContext)
9 : base(metadata, controllerContext) {
10 }
11
12 public override IEnumerable<ModelValidationResult> Validate(object container) {
13 bool propertiesValid = true;
14
15 foreach (ModelMetadata propertyMetadata in Metadata.Properties) {
16 foreach (ModelValidator propertyValidator in propertyMetadata.GetValidators(ControllerContext)) {
17 foreach (ModelValidationResult propertyResult in propertyValidator.Validate(Metadata.Model)) {
18 propertiesValid = false;
19 yield return new ModelValidationResult {
20 MemberName = DefaultModelBinder.CreateSubPropertyName(propertyMetadata.PropertyName, propertyResult.MemberName),
21 Message = propertyResult.Message
22 };
23 }
24 }
25 }
26
27 if (propertiesValid) {
28 foreach (ModelValidator typeValidator in Metadata.GetValidators(ControllerContext)) {
29 foreach (ModelValidationResult typeResult in typeValidator.Validate(container)) {
30 yield return typeResult;
31 }
32 }
33 }
34 }
35 }
它尽然是直接构造了一个CompositeModelValidator,这个CompositeModelValidator还是私有的,没有用工厂模式,CompositeModelValidator不能继承重写,也就是说第一、如果想验证,那就只能是验证属性,不能验证简单类型(当然有其他办法可以解决这个问题),第二、也就是我上面提到的第四点,如果属性是非值类型,对不起不能验证其内部的属性,第三、对于验证一个Model那些部分我控制不了,只能是它的属性。这三点使我我本来想扩展MVC2的验证部分,也就是继承自ModelValidator,看来是不行了,只能自己写新的验证部分了,只觉得MVC2有点失败,比其MVC1的成功,它有点让我失望。
当然,这并不代表了,我上面所说的那些就做不了,我会在下一篇中给出利用Enterprise Library 的验证来实现 扩展。
关注技术文章飞秋:http://www.freeeim.com/,24小时专业转载。