场景:
当我把项目从 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 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
的方法找到到答案。为了解决这个问题,我决定使用自定义的方法进行实体验证,代码如下:
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() 也调用了,很显然这不是我们想要的。该问题我会在下一篇文章提出解决方案。
作者:RyanDing
出处:http://www.cnblogs.com/ryanding/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如有疑问,可以通过 ryan.d@qq.com 联系作者本人。