RyanDing

用编码抒写未来

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

     上一篇文章中主要介绍了如何在MVC2.0中将验证方法统一,当验证都规范好后再测试代码发现还存在一些隐藏问题未解决。问题的产生请看下图:

         假设我们的数据库只有这两张表

因为项目建立在LinqToSQL基础上,所以当我们在MVC内调用 TryUpdateModel 将 UI 传递过来的 FormCollection 表单值赋值到LinqToSQL实体对象属性。如果该实体对象从数据库来说只是一张基础表此表会做为其他表的外键表。如上图中的 Department 表,当我们保存Department到数据库前,调用 MVC 的TryUpdateModel 时该方法将验证 MetadataType 指定的 ValidationAttribute 特性。代码如下:

[UniqueDepartmentName("DepartmentName")]
[MetadataType(
typeof(DepartmentMetaData))]
public partial class Department { }

上一篇文章有提及到MVC2.0默认验证只能执行DepartmentMetaData 类中的 ValidationAttribute 特性,是无法执行UniqueDepartmentName 这个自定义特性的IsValid方法。所以我自定义了独立的模型验证代码解决这个问题。随后我发现了更奇怪的问题,当我保存SystemUser 这个LinqToSql的对象时同样执行TryUpdateModel 方法给该对象赋值。作为Department 类的自定义ValidationAttribute 特性 UniqueDepartmentName 的 IsValid方法奇迹般的被执行了...

看起来有点痛苦,当你想执行时它不执行,不应该执行时它执行的却挺好!该如何解决这个问题呢?难道不用TryUpdateModel 这个方法了?当然不太现实,毕竟这个方法让我们少写了很多机械性的代码。于是我写了一个通用方法:

UpdateEntityValue
/// <summary>
/// 更新目标对象属性值,该值从FormCollection 获取。
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="targetObj"></param>
/// <param name="sourceObj"></param>
/// <returns></returns>
public static void UpdateEntityValue<T>(T targetObj, T sourceObj) where T : class
{
try
{
Type targetType
= targetObj.GetType();
PropertyInfo[] targetProps
= targetType.GetProperties();
targetProps
= targetProps.Where(p => p.GetCustomAttributes(typeof(ColumnAttribute), true).Count() > 0).ToArray();

Type sourceType
= targetObj.GetType();
PropertyInfo[] sourceProps
= sourceType.GetProperties();
sourceProps
= sourceProps.Where(p => p.GetCustomAttributes(typeof(ColumnAttribute), true).Count() > 0).ToArray();

foreach (var tProp in targetProps)
{
PropertyInfo sProp
= sourceProps.FirstOrDefault(s => s.Name == tProp.Name);
tProp.SetValue(targetObj, sProp.GetValue(sourceObj,
null), null);
}
}
catch
{

}
}

UpdateEntityValue 方法很简单,不作解释了。该方法调用如下:

代码
1 public JsonResult SaveSystemUser(FormCollection fc)
2 {
3 try
4 {
5 SystemUser user = null;
6 string strUserID = fc.Get("UserID");
7
8 if (String.IsNullOrEmpty(strUserID))//新增
9   {
10 user = new SystemUser();
11 TryUpdateModel(user);
12 _DataContext.SystemUsers.InsertOnSubmit(user);
13 }
14 else//修改
15   {
16 user = _DataContext.SystemUsers.FirstOrDefault(u => u.UserID == int.Parse(strUserID));
17 SystemUser sourceUser = new SystemUser();
18 //TryUpdateModel(user);//TryUpdateModel 该方法更新模型对象属性值时同时还进行了 Department 该类的ValidationAttribute验证。
19   TryUpdateModel(sourceUser);
20 UpdateEntityValue<SystemUser>(user, sourceUser);//这样做就不会执行Department 这个类的 UniqueDepartmentName 自定义 的ValidationAttribute 验证方法
21   }
22
23 //独立验证实体,具体见我的 在MVC2.0 中 进行 LINQTOSQL 实体统一验证方法(上)这篇博文。
24   Validation.ValidateAttributes<SystemUser>(user);
25
26 _DataContext.SubmitChanges();
27
28 return Json(new { Success = true, Msg = "保存成功。" }, JsonRequestBehavior.AllowGet);
29 }
30 catch
31 {
32 return Json(new { Success = false, Msg = "保存失败。" }, JsonRequestBehavior.AllowGet);
33 }
34 }

产生这问题的关键所在是由于TryUpdateModel SystemUser 这个LinqToSQL对象时,将这个对象的Department 属性上Annotation验证方法也给执行了。通过UpdateEntityValue这个方法解决了这个问题,貌似又“风平浪静”了,一切又正如我们所预期的那样。细心的你会发现如果这样子不是每次都要为TryUpdateModel 准备一个 source 对象。这样的代码会在工程内出现无数次,这也不是我们想见到的。话说MVC提供给开发人员很多自定义的特性。有没有办法改造以上代码呢?答案当然是肯定的。我们可以让TryUpdateModel只负责赋值模型属性值不负责验证,这做法在我的项目里是完全可以的,因为我只调用自定义的 Validation.ValidateAttributes<SystemUser>(user) 验证模型。不用MVC内置模型验证。那如何做到只赋值不验证呢,其实我们只要修改 MVC2.0 的 ModelBinders.Binders.DefaultBinder 即可。在Global.asax的 Application_Start() 方法内添加 ModelBinders.Binders.DefaultBinder = new CustomModelBinder(); CustomModelBinder 类是自定义的类,其基类是 DefaultModelBinder 我们只要重写这个基类的 OnPropertyValidated 虚方法。代码如下:

代码
public class CustomModelBinder : DefaultModelBinder
{

protected override void OnPropertyValidated(ControllerContext controllerContext,
ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor,
object value)
{

// Do Nothing;

}
}

这样先前 UpdateEntityValue 这个方法就可以剔除出去了,代码也得到了进一步优化。

posted on 2010-11-12 14:27  ryanding  阅读(2041)  评论(8编辑  收藏  举报