[转载]ASP.NET MVC 2:实体外部元数据扩展 – 阿不 – 博客园.
在MVC的Model中,我们可以定义很多与视图相关的元数据,这些元数据对我们开发视图起着相当重要的作用,特别是在数据验证方面。这些元数据一 般情况下我们是不会定义在业务实体(或持久化实体)上面,所以很多情况下,我们会需要开发两种实体:View Model和Business Model。这样就造成,在Action与View的沟通当中,我们需要使用View Model,然后在业务逻辑处理时,我们需要再将View Model映射到Business Model,这将会使我们的开发框架变得繁琐。因为一般情况下,View Model和Business Model在很多情况下,都是很雷同的对象,只是View Model会有很多与视图相关的元数据。在这种情况下,只要我们能把View Model作为Business Model的元数据描述对象(MetadataType)来使用,而不直接参与Action与View的沟通,让这些工作都由Business Model来承担,这样就可以有效的避免很多重复工作。
在System.ComponentModel.DataAnnotations内部,提供了MetadataTypeAttribute这个标签,让我们可以为Business Model指定它对应的视图元数据类型。特别是当我们使用LINQ2SQL、EF等框架来生成实体框架时,我们可以以partial类的形式来提供它对应的视图元数据类型:
1 |
[MetadataType( typeof (Product_Metadata))] |
2 |
public partial class Product |
5 |
public class Product_Metadata |
这样做在大多数情况下是没有问题的。但是仅仅是这样,还不能解决所有问题。一般情况下Business Model和MetadataType是不在同一个Assembly里面,这时候你就无法以partial类的形式来扩展Business Model。所以我们就需要有一套机制来延迟注册Business Model与MetadataType的映射关系。通过MVC源码的分析,我们可以通过扩展 DataAnnotationsModelMetadataProvider的
GetTypeDescriptor方法来解决这个问题。
首先,我们先定义一个Business Model与MetadataType的映射容器:
01 |
public static class TypeDescriptorHelper |
03 |
static Hashtable hashtable = new Hashtable(); |
04 |
static ReaderWriterLockSlim locker = new ReaderWriterLockSlim(); |
05 |
static TypeDescriptorHelper() |
09 |
public static void RegisterMetadataType(Type type, Type metadataType) |
11 |
locker.EnterWriteLock(); |
13 |
hashtable[type] = metadataType; |
15 |
locker.ExitWriteLock(); |
17 |
public static ICustomTypeDescriptor Get(Type type) |
19 |
locker.EnterReadLock(); |
20 |
var metadataType = hashtable[type] as Type; |
21 |
ICustomTypeDescriptor descriptor = null ; |
22 |
if (metadataType != null ) |
24 |
descriptor = new AssociatedMetadataTypeTypeDescriptionProvider(type, metadataType).GetTypeDescriptor(type); |
26 |
locker.ExitReadLock(); |
这个容器非常简单,我们只是定义了一个hashtable来保存它们的映射关系。然后,我们从DataAnnotationsModelMetadataProvider继承一个新的类,重写GetTypeDescriptor方法:
01 |
public class CustomDataAnnotationsModelMetadataProvider : DataAnnotationsModelMetadataProvider |
03 |
protected override System.ComponentModel.ICustomTypeDescriptor GetTypeDescriptor(Type type) |
05 |
var descriptor = TypeDescriptorHelper.Get(type); |
06 |
if (descriptor == null ) |
08 |
descriptor = base .GetTypeDescriptor(type); |
接下来要做的事情就是把CustomDataAnnotationsModelMetadataProvider注册到系统中,用它来代替原来的DataAnnotationsModelMetadataProvider,在Global.asax添加如下代码:
1 |
ModelMetadataProviders.Current = new CustomDataAnnotationsModelMetadataProvider(); |
最后需要在映射容器中,添加你希望的映射关系:
1 |
TypeDescriptorHelper.RegisterMetadataType( typeof (Product), typeof (Product_Metadata)); |
通过这样的映射,我们就可以完全解放Business Model对MetadataType的依赖了。
后记: 在调试这个扩展的过程当中,出现了一个相当低级的错误,花了我几个小时的时间。话说提交的数据中有一个名为site的字符串(在Route.Values 里面),同时我在Action的参数中,定义的是一个名为site的复杂类型。程序在运行过程中,始终提示异常:类型无法转换。因为我怀疑是我扩展了 MetadataType引发的问题,所以拼命的在那边调试,始终没有找到原因。最后还原所有的代码,问题依旧,才让我走到正确的方向上,冤枉死我了。