RyanDing

用编码抒写未来

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

    场景

      当我把项目从 MVC1.0 升级到 MVC2.0 时,原以为可以方便的使用 System.ComponentModel.DataAnnotations 结合 MVC2.O 的

      ModelState.IsValid 进行数据有效验证。比如以下验证:

1 public class SystemUserMetaData
2 {
3 [Required(ErrorMessage = "不能为空!")]
4 [StringLength(6, ErrorMessage = "用户名长度不能超过6!")]
5 public string UserName { get; set; }
6 [Required(ErrorMessage = "IsValid is required.")]
7 public string ChineseName { get; set; }
8 [Required(ErrorMessage = "IsValid is required.")]
9 public bool IsValid { get; set; }
10 [Required(ErrorMessage = "Department is required.")]
11 public int DepartmentID { get; set; }
12 [Required(ErrorMessage = "Password is required.")]
13 public string Password { get; set; }
14 [Required(ErrorMessage = "Rank is required.")]
15 public int RankID { get; set; }
16 [PhoneAttribute(ErrorMessage = "电话号码不正确")]
17 public string MobilePhone { get; set; }
18 public int UserID { get; set; }
19 }
代码1
1 public class SystemUserMetaData
2 {
3 [Required(ErrorMessage = "不能为空!")]
4 [StringLength(6, ErrorMessage = "用户名长度不能超过6!")]
5 public string UserName { get; set; }
6 [Required(ErrorMessage = "中文名不能为空")]
7 public string ChineseName { get; set; }
8 [Required(ErrorMessage = "部门不能为空")]
9 public int DepartmentID { get; set; }
10 [Required(ErrorMessage = "密码不能为空")]
11 public string Password { get; set; }
12 [Required(ErrorMessage = "职位不能为空")]
13 public int RankID { get; set; }
14 [PhoneAttribute(ErrorMessage = "电话号码不正确")]// 自定义ValidationAttribute “只能验证 MobilePhone 的值”
15   public string MobilePhone { get; set; }
16 public int UserID { get; set; }
17 }

     这些Annotation特性验证可以很轻松通过 mvc2.0  ViewData.ModelState.Values 获取到验证错误的提示信息。但是当我们的验证条件变得更加

复杂时,比如在修改一个LinqToSQL 实体时需通过该实体的主键和唯一索引进行验证实体是否唯一性时,此时需要两个字段同时验证,当这种验证出现时我

发现无法简单的使用 DataAnnotaion 进行同一实体的多字段验证。自定义 ValidationAttribute 特性重写 IsValid 时 无法根据当前的属性获取到其他属性

的值。因为ValidationAttribute 特性是附加在一个类的属性上的。可能聪明的你此刻已想到了将验证特性直接加载 LinqToSQL 的 类上。当你为这个特性

编写验证方法时就可以通过反射得到 LinqToSql 实体的所有属性的值,或许单一的 ValidationAttribute 属性验证特性不能完成的任务就可以得到解决。

        当我把LINQTOSQL 类的验证特性写完后附加到 LinqTOSQL partial 类上代码如下:

[UniqueName("UserID", "UserName", typeof(SystemUser), ErrorMessage = "该用户已存在。")]
[MetadataType(
typeof(SystemUserMetaData))]
public partial class SystemUser { }

在MVC2.0 中当我们使用 TryUpdateModel 方法时 发现 UniqueName 的 IsValid 方法始终没有被调用。但是当 MetadataType 移除除掉,我们再调用

TyUpdateaModel方法时UniqueName 特性的 IsValid 验证方法就被正常调用了。此时我明白了问题应该是由 MVC  TryUpdateModel 方法引起,将该方

法换成 UpdateModel 后问题依旧。MetadataType 特性覆盖了 UniqueName 特性,当然了如果想知道具体的原因,可以 Reflect 出 TryUpdateModel

的方法找到到答案。为了解决这个问题,我决定使用自定义的方法进行实体验证,代码如下:

代码3
public class Validation
{
public static void ValidateAttributes<TEntity>(TEntity entity)
{
var validationInstance
= new Validation();
validationInstance.ValidateAttributesInternal(entity);
}

public virtual void ValidateAttributesInternal<TEntity>(TEntity entity)
{
var validationIssues
= new List<ValidationIssue>();

var props
= typeof(TEntity).GetProperties();
var metatype
= typeof(TEntity).GetCustomAttributes(typeof(MetadataTypeAttribute), false).FirstOrDefault();
var type
= ((System.ComponentModel.DataAnnotations.MetadataTypeAttribute)(metatype)).MetadataClassType;
var s
= type.GetProperties();

var customAttrs
= typeof(TEntity).GetCustomAttributes(true).Where(t => t.GetType().Namespace.Contains("ValidationMeta"));
foreach (var attr in customAttrs)
{
var validate
= (ValidationAttribute)attr;
//执行 附加在 linqtosql partial 类 上的 ValidationAttribute 验证方法
bool valid = validate.IsValid(entity);
if (!valid)
{
validationIssues.Add(
new ValidationIssue(null, null, validate.ErrorMessage));
}
}

//执行附加在 linqtosql partial 类 属性上的 ValidationAttribute 验证方法
foreach (var prop in s)
ValidateProperty(validationIssues, entity, prop);

// throw exception?
if (validationIssues.Count > 0)
throw new ValidationIssueException(validationIssues);
}

protected virtual void ValidateProperty<TEntity>(List<ValidationIssue> validationIssues, TEntity entity, PropertyInfo property)
{
//得到验证特性的集合
var validators = property.GetCustomAttributes(typeof(ValidationAttribute), false);

foreach (ValidationAttribute validator in validators)
ValidateValidator(validationIssues, entity, property, validator);
}

protected virtual void ValidateValidator<TEntity>(List<ValidationIssue> validationIssues, TEntity entity, PropertyInfo property, ValidationAttribute validator)
{
var dataEntityProperty
= typeof(TEntity).GetProperties().FirstOrDefault(p => p.Name == property.Name);
var value
= dataEntityProperty.GetValue(entity, null);

if (!validator.IsValid(value))
{
validationIssues.Add(
new ValidationIssue(property.Name, value, validator.ErrorMessage));
}
}
}

大家留意一下代码3 中的注释,这样 Validation 这个类就就可以替代MVC TryUpdateModel 的验证功能同时让代码1的 UniqueName 和 MetaDataType 两个特性 “共存”。

MetadataType 的职责:验证实体的单一属性值的有效性。

LINQ实体类上的其他的自定义特性:如代码1中的 UniqueName 则可以进行复杂的属性验证如多属性值同时验证等。

这样我们就彻底的解决了开发过程中验证代码统一的编码规范。而不是同一个数据有效性验证的代码满天飞的局面。

小结

当我完成了以上代码似乎已经达到了预期的目的,但测试代码时候发现如果使用TryUpdateModel 更新另外一个LINQTOSQL 模型(Order表),这个被

更新的模型从数据库上来看它属于 SystemUser 的外键表。通过Order表中的UserID 字段关联到 SystemUser。当Order实体被MVC TryUpdateModel 时会同时把SystemUser 的 自定义的 [UniqueName] 特性的方法 IsValid() 也调用了,很显然这不是我们想要的。该问题我会在下一篇文章提出解决方案。

posted on 2010-11-07 15:02  ryanding  阅读(2667)  评论(20编辑  收藏