ASP.NET MVC的Model元数据提供机制的实现
2012-05-09 07:52
1006 查看
在前面的介绍中我们已经提到过表示Model元数据的ModelMetadata对象最终是通过一个名为ModelMetadataProvider的组件提供的,接下来我们着重讨论基于ModelMetadataProvider的Model元数据提供机制及其扩展。[本文已经同步到《How ASP.NET MVC Works?》中]
[/code]
注:在本文中提及的ModelMetadataProvider在大部分情况泛指直接或者间接继承自抽象类ModelMetadataProvider,用于提供Model元数据的提供者对象或者类型,请读者注意区分。
在ASP.NET MVC的元数据解析系统中使用的ModelMetadataProvider最终通过类型ModelMetadataProviders获取。如下面的代码片断所示,ModelMetadataProviders具有一个ModelMetadataProvider类型的静态可读可写属性Current用于获取和设置当前使用的ModelMetadataProvider。
[/code]
不过DataAnnotationsModelMetadataProvider并没有直接继承自ModelMetadataProvider,而是继承自抽象类AssociatedMetadataProvider,后者是ModelMetadataProvider的子类。AssociatedMetadataProvider的主要作用是对应用在Model类型或属性上所有“关联”的特性,这也是它命名的由来。如下面的代码片断所示,AssociatedMetadataProvider实现了定义在ModelMetadataProvider的三个方法。
[/code]
对于AssociatedMetadataProvider实现的三个方法,它并紧紧是通过反射将应用在Model类型和对应属性上的所有特性,并将这个特性列表作为参数(attributes)传入抽象方法CreateMetadata完成Model元数据的创建。值得一提的是,当通过调用CreateMetadata创建出ModelMetadata之后,会从特性列表中筛选出实现了IMetadataAware接口的特性,并将该ModelMetadata对象作为参数调用它们的OnMetadataCreated方法。
继承自AssociatedMetadataProvider的DataAnnotationsModelMetadataProvider实现了抽象方法,它根据传入的特性列表以及其他相关的信息(用于创建Model对象的委托、容器和Model类型以及属性名称)实现对Model元数据的最终创建。下面的代码片断就是整个DataAnnotationsModelMetadataProvider类型的定义。
[/code]
包含在Model元数据提供系统的ModelMetadataProvider、AssociatedMetadataProvider、DataAnnotationsModelMetadataProvider和ModelMetadataProviders与ModelMetadata之间的关系可以通过如下图所示的UML来体现。
DataAnnotationsModelMetadataProvider最终实现了基于标注特性的Model元数据的解析,但是在默认情况下使用的ModelMetadataProvider类型却不是DataAnnotationsModelMetadataProvider,而是CachedDataAnnotationsModelMetadataProvider,它对解析出来的元数据信息进行了相应的环村以提供性能,其实最终实现对Model元数据创建的还是DataAnnotationsModelMetadataProvider。
在《一个重要的接口:IMetadataAware》中我们创建了一个用于控制目标元素显示名称的DisplayTextAttribute特性。该特性支持基于资源文件的本地化,并且可以省去对资源条目名称和资源类型的显式指定。该DisplayTextAttribute特性是通过实现IMetadataAware接口的形式实现的,现在我们将它转换成基于自定义ModelMetadataProvider的实现方式。对于之前定义的DisplayTextAttribute特性,我们只需要对其进行简单的修改。如下面的代码片断所示,我们删除了它实现的IMetadataAware接口,将实现的OnMetadataCreated方法名改成SetMetadata。
[/code]
为了将DisplayTextAttribute应用到Model元数据的初始化过程中,我们通过继承DataAnnotationsModelMetadataProvider创建了如下一个ExtendedDataAnnotationsProvider。在重写的CreateMetadata方法中,我们先调用基类的同名方法得到一个ModelMetadata对象。如果该对象的DisplayName属性为空,在从特性列表中获取DisplayTextAttribute特性并调用其SetDisplayName方法对ModelMetadata的DisplayName属性进行设置。
[/code]
对于之前创建的演示实例,如果我们在Global.asax中通过如下的方式对我们自定义的ExtendedDataAnnotationsProvider进行注册,该实例应用同样可以正常运行。
[/code]
这个实例直接使用了扩展的DataAnnotationsModelMetadataProvider替换了默认的CachedDataAnnotationsModelMetadataProvider,意味着失去了对成功解析出来的元数据的缓存功能,会对性能造成一定的影响。但是由于CachedDataAnnotationsModelMetadataProvider已经将CreateMetadata方法封闭(Seal),又不能直接继承CachedDataAnnotationsModelMetadataProvider。如果项目里面确实需要使用到类似的用法,可以考虑自己实现缓存。
一、 ModelMetadataProvider
在ASP.NET MVC的Model元数据相关的应用编程接口中,用于创建Model元数据的ModelMetadataProvider接继承自抽象类ModelMetadataProvider。如下面的代码片断所示,ModelMetadataProvide具有三个抽象方法。GetMetadataForProperties方法用于获取表示针对指定容器对象和类型所有属性的Model元数据集合,GetMetadataForProperty获取针对指定容器对象和类型某个具体属性对象的Model元数据,而GetMetadataForType则直接返回针对容器对象和类型的Model元数据。[code] public abstract class ModelMetadataProvider { public abstract IEnumerable<ModelMetadata> GetMetadataForProperties( object container, Type containerType); public abstract ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName); public abstract ModelMetadata GetMetadataForType(Func<object> modelAccessor, Type modelType); }
[/code]
注:在本文中提及的ModelMetadataProvider在大部分情况泛指直接或者间接继承自抽象类ModelMetadataProvider,用于提供Model元数据的提供者对象或者类型,请读者注意区分。
在ASP.NET MVC的元数据解析系统中使用的ModelMetadataProvider最终通过类型ModelMetadataProviders获取。如下面的代码片断所示,ModelMetadataProviders具有一个ModelMetadataProvider类型的静态可读可写属性Current用于获取和设置当前使用的ModelMetadataProvider。
[code] public class ModelMetadataProviders { public static ModelMetadataProvider Current { get; set; } }
[/code]
二、DataAnnotationsModelMetadataProvider
通过前面的介绍我们知道Model元数据是通过定义在System.ComponentModel.DataAnnotations命名空间下的标注特性来定义的,Model元数据解析系统通过对应用在表示Model的数据类型及其属性成员的标注特性进行解析从而对创建的Model元数据进行对应的初始化,而这个工作是通过DataAnnotationsModelMetadataProvider来实现的。不过DataAnnotationsModelMetadataProvider并没有直接继承自ModelMetadataProvider,而是继承自抽象类AssociatedMetadataProvider,后者是ModelMetadataProvider的子类。AssociatedMetadataProvider的主要作用是对应用在Model类型或属性上所有“关联”的特性,这也是它命名的由来。如下面的代码片断所示,AssociatedMetadataProvider实现了定义在ModelMetadataProvider的三个方法。
[code] public abstract class AssociatedMetadataProvider : ModelMetadataProvider { protected abstract ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName); public override IEnumerable<ModelMetadata> GetMetadataForProperties(object container, Type containerType); public override ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName); public override ModelMetadata GetMetadataForType(Func<object> modelAccessor, Type modelType); }
[/code]
对于AssociatedMetadataProvider实现的三个方法,它并紧紧是通过反射将应用在Model类型和对应属性上的所有特性,并将这个特性列表作为参数(attributes)传入抽象方法CreateMetadata完成Model元数据的创建。值得一提的是,当通过调用CreateMetadata创建出ModelMetadata之后,会从特性列表中筛选出实现了IMetadataAware接口的特性,并将该ModelMetadata对象作为参数调用它们的OnMetadataCreated方法。
继承自AssociatedMetadataProvider的DataAnnotationsModelMetadataProvider实现了抽象方法,它根据传入的特性列表以及其他相关的信息(用于创建Model对象的委托、容器和Model类型以及属性名称)实现对Model元数据的最终创建。下面的代码片断就是整个DataAnnotationsModelMetadataProvider类型的定义。
[code] public class DataAnnotationsModelMetadataProvider : AssociatedMetadataProvider { public DataAnnotationsModelMetadataProvider(); protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName); }
[/code]
包含在Model元数据提供系统的ModelMetadataProvider、AssociatedMetadataProvider、DataAnnotationsModelMetadataProvider和ModelMetadataProviders与ModelMetadata之间的关系可以通过如下图所示的UML来体现。
DataAnnotationsModelMetadataProvider最终实现了基于标注特性的Model元数据的解析,但是在默认情况下使用的ModelMetadataProvider类型却不是DataAnnotationsModelMetadataProvider,而是CachedDataAnnotationsModelMetadataProvider,它对解析出来的元数据信息进行了相应的环村以提供性能,其实最终实现对Model元数据创建的还是DataAnnotationsModelMetadataProvider。
三、对Model元数据提供系统的扩展
对Model元数据提供系统的扩展主要体现在对ModelMetadataProvider自定义上。基于标注特性的元数据定义方式最终是通过DataAnnotationsModelMetadataProvider来实现,通过自定义ModelMetadataProvider我们完全可以提供一种全新的Model元数据定义方式。不过我们经常使用的方式还是通过继承DataAnnotationsModelMetadataProvider在现有的元数据提供机制上做一些扩展。在《一个重要的接口:IMetadataAware》中我们创建了一个用于控制目标元素显示名称的DisplayTextAttribute特性。该特性支持基于资源文件的本地化,并且可以省去对资源条目名称和资源类型的显式指定。该DisplayTextAttribute特性是通过实现IMetadataAware接口的形式实现的,现在我们将它转换成基于自定义ModelMetadataProvider的实现方式。对于之前定义的DisplayTextAttribute特性,我们只需要对其进行简单的修改。如下面的代码片断所示,我们删除了它实现的IMetadataAware接口,将实现的OnMetadataCreated方法名改成SetMetadata。
[code] [AttributeUsage(AttributeTargets.Class| AttributeTargets.Property)] public class DisplayTextAttribute: Attribute { private static Type staticResourceType; public string DisplayName { get; set; } public Type ResourceType { get; set; } public DisplayTextAttribute() { this.ResourceType = staticResourceType; } public void SetMetadata(ModelMetadata metadata) { this.DisplayName = this.DisplayName ?? (metadata.PropertyName ?? metadata.ModelType.Name); if (null == this.ResourceType) { metadata.DisplayName = this.DisplayName; return; } PropertyInfo property = this.ResourceType.GetProperty(this.DisplayName, BindingFlags.NonPublic|BindingFlags.Public| BindingFlags.Static); metadata.DisplayName = property.GetValue(null, null).ToString(); } public static void SetResourceType(Type resourceType) { staticResourceType = resourceType; } }
[/code]
为了将DisplayTextAttribute应用到Model元数据的初始化过程中,我们通过继承DataAnnotationsModelMetadataProvider创建了如下一个ExtendedDataAnnotationsProvider。在重写的CreateMetadata方法中,我们先调用基类的同名方法得到一个ModelMetadata对象。如果该对象的DisplayName属性为空,在从特性列表中获取DisplayTextAttribute特性并调用其SetDisplayName方法对ModelMetadata的DisplayName属性进行设置。
[code] public class ExtendedDataAnnotationsProvider : DataAnnotationsModelMetadataProvider { protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName) { ModelMetadata metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName); if(string.IsNullOrEmpty(metadata.DisplayName)) { DisplayTextAttribute displayTextAttribute = attributes.OfType<DisplayTextAttribute>().FirstOrDefault(); if (null != displayTextAttribute) { displayTextAttribute.SetDisplayName(metadata); } } return metadata; } }
[/code]
对于之前创建的演示实例,如果我们在Global.asax中通过如下的方式对我们自定义的ExtendedDataAnnotationsProvider进行注册,该实例应用同样可以正常运行。
[code] public class MvcApplication : System.Web.HttpApplication { //其他成员 protected void Application_Start() { //其他操作 DisplayTextAttribute.SetResourceType(typeof(Resources)); ModelMetadataProviders.Current = new ExtendedDataAnnotationsProvider(); } }
[/code]
这个实例直接使用了扩展的DataAnnotationsModelMetadataProvider替换了默认的CachedDataAnnotationsModelMetadataProvider,意味着失去了对成功解析出来的元数据的缓存功能,会对性能造成一定的影响。但是由于CachedDataAnnotationsModelMetadataProvider已经将CreateMetadata方法封闭(Seal),又不能直接继承CachedDataAnnotationsModelMetadataProvider。如果项目里面确实需要使用到类似的用法,可以考虑自己实现缓存。
相关文章推荐
- ASP.NET MVC的Model元数据提供机制的实现
- ASP.NET MVC传递Model到视图的多种方式总结(二)__关于ViewBag、ViewData和TempData的实现机制与区别
- 通过实例模拟ASP.NET MVC的Model绑定机制[上篇]
- [新手上路] [原创]使用ASP.NET MVC 4 Async Action + jQuery 实现消息通知机制
- 通过扩展改善ASP.NET MVC的验证机制[实现篇]
- .NET/ASP.NETMVC 深入剖析 Model元数据、HtmlHelper、自定义模板、模板的装饰者模式(二)
- 通过实例模拟ASP.NET MVC的Model绑定的机制:集合+字典
- 深入ASP.NET MVC之六:Model Binding的实现
- [笔记] ASP.NET本身的缓存实现机制,基于Cache的实现。提供按分类进行管理的缓存功能
- 深入ASP.NET MVC :Model Template与Html.Editor的实现
- ASP.NET MVC的客户端验证:jQuery验证在Model验证中的实现
- ASP.NET MVC:如何提供 Controller 继承体系使用的 ModelBinder?
- ASP.NET MVC的客户端验证:jQuery验证在Model验证中的实现
- .NET/ASP.NETMVC 深入剖析 Model元数据、HtmlHelper、自定义模板、模板的装饰者模式(一)
- 如何应用Asp.Net Mvc内建功能(DefaultModelBinder)实现简单类型、复杂类型、集合类型,以及字典类型的自动绑定
- Asp.net MVC中提交集合对象,实现Model绑定(转载)
- ASP.NET MVC(模型(Model),视图(View)和控制Controller)实践:实现身份验证权限管理
- ASP.NET MVC的客户端验证:jQuery验证在Model验证中的实现
- 扩展ASP.NET MVC三层框架并使用StructureMap实现依赖注入1-Model层的实现
- [新手上路] 使用ASP.NET MVC 4 Async Action + jQuery 实现消息通知机制