[转载]在MVC2.0 中 进行 LINQTOSQL 实体统一验证方法(上) – 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 }
这些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
的方法找到到答案。为了解决这个问题,我决定使用自定义的方法进行实体验证,代码如下:
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() 也调用了,很显然这不是我们想要的。该问题我会在下一篇文章提出解决方案。