专栏首页大内老AASP.NET MVC的Model元数据提供机制的实现

ASP.NET MVC的Model元数据提供机制的实现

在前面的介绍中我们已经提到过表示Model元数据的ModelMetadata对象最终是通过一个名为ModelMetadataProvider的组件提供的,接下来我们着重讨论基于ModelMetadataProvider的Model元数据提供机制及其扩展。[本文已经同步到《How ASP.NET MVC Works?》中]

一、 ModelMetadataProvider

在ASP.NET MVC的Model元数据相关的应用编程接口中,用于创建Model元数据的ModelMetadataProvider接继承自抽象类ModelMetadataProvider。如下面的代码片断所示,ModelMetadataProvide具有三个抽象方法。GetMetadataForProperties方法用于获取表示针对指定容器对象和类型所有属性的Model元数据集合,GetMetadataForProperty获取针对指定容器对象和类型某个具体属性对象的Model元数据,而GetMetadataForType则直接返回针对容器对象和类型的Model元数据。

   1: public abstract class ModelMetadataProvider
   2: {    
   3:     public abstract IEnumerable<ModelMetadata> GetMetadataForProperties( object container, Type containerType);
   4:     public abstract ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName);
   5:     public abstract ModelMetadata GetMetadataForType(Func<object> modelAccessor, Type modelType);
   6: }

注:在本文中提及的ModelMetadataProvider在大部分情况泛指直接或者间接继承自抽象类ModelMetadataProvider,用于提供Model元数据的提供者对象或者类型,请读者注意区分。

在ASP.NET MVC的元数据解析系统中使用的ModelMetadataProvider最终通过类型ModelMetadataProviders获取。如下面的代码片断所示,ModelMetadataProviders具有一个ModelMetadataProvider类型的静态可读可写属性Current用于获取和设置当前使用的ModelMetadataProvider。

   1: public class ModelMetadataProviders
   2: {
   3:     public static ModelMetadataProvider Current { get; set; }
   4: }

二、DataAnnotationsModelMetadataProvider

通过前面的介绍我们知道Model元数据是通过定义在System.ComponentModel.DataAnnotations命名空间下的标注特性来定义的,Model元数据解析系统通过对应用在表示Model的数据类型及其属性成员的标注特性进行解析从而对创建的Model元数据进行对应的初始化,而这个工作是通过DataAnnotationsModelMetadataProvider来实现的。

不过DataAnnotationsModelMetadataProvider并没有直接继承自ModelMetadataProvider,而是继承自抽象类AssociatedMetadataProvider,后者是ModelMetadataProvider的子类。AssociatedMetadataProvider的主要作用是对应用在Model类型或属性上所有“关联”的特性,这也是它命名的由来。如下面的代码片断所示,AssociatedMetadataProvider实现了定义在ModelMetadataProvider的三个方法。

   1: public abstract class AssociatedMetadataProvider : ModelMetadataProvider
   2: {
   3:     protected abstract ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName);
   4:     
   5:     public override IEnumerable<ModelMetadata> GetMetadataForProperties(object container, Type containerType);   
   6:     public override ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName);
   7:     public override ModelMetadata GetMetadataForType(Func<object> modelAccessor, Type modelType);
   8: }

对于AssociatedMetadataProvider实现的三个方法,它并紧紧是通过反射将应用在Model类型和对应属性上的所有特性,并将这个特性列表作为参数(attributes)传入抽象方法CreateMetadata完成Model元数据的创建。值得一提的是,当通过调用CreateMetadata创建出ModelMetadata之后,会从特性列表中筛选出实现了IMetadataAware接口的特性,并将该ModelMetadata对象作为参数调用它们的OnMetadataCreated方法。

继承自AssociatedMetadataProvider的DataAnnotationsModelMetadataProvider实现了抽象方法,它根据传入的特性列表以及其他相关的信息(用于创建Model对象的委托、容器和Model类型以及属性名称)实现对Model元数据的最终创建。下面的代码片断就是整个DataAnnotationsModelMetadataProvider类型的定义。

   1: public class DataAnnotationsModelMetadataProvider : AssociatedMetadataProvider
   2: {    
   3:     public DataAnnotationsModelMetadataProvider();
   4:     protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, 
   5:     Func<object> modelAccessor, Type modelType, string propertyName);
   6: }

包含在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。
   1: [AttributeUsage(AttributeTargets.Class| AttributeTargets.Property)]
   2: public class DisplayTextAttribute: Attribute
   3: {
   4:     private static Type staticResourceType;
   5:     public string DisplayName { get; set; }
   6:     public Type ResourceType { get; set; }
   7:  
   8:     public DisplayTextAttribute()
   9:     {
  10:         this.ResourceType = staticResourceType;
  11:     }
  12:  
  13:     public void SetMetadata(ModelMetadata metadata)
  14:     {
  15:         this.DisplayName = this.DisplayName ?? (metadata.PropertyName ?? metadata.ModelType.Name);
  16:         if (null == this.ResourceType)
  17:         {
  18:             metadata.DisplayName = this.DisplayName;
  19:             return;
  20:         }
  21:         PropertyInfo property = this.ResourceType.GetProperty(this.DisplayName, BindingFlags.NonPublic|BindingFlags.Public| BindingFlags.Static);
  22:         metadata.DisplayName = property.GetValue(null, null).ToString();
  23:     }
  24:  
  25:     public static void SetResourceType(Type resourceType)
  26:     {
  27:         staticResourceType = resourceType;
  28:     }
  29: }

为了将DisplayTextAttribute应用到Model元数据的初始化过程中,我们通过继承DataAnnotationsModelMetadataProvider创建了如下一个ExtendedDataAnnotationsProvider。在重写的CreateMetadata方法中,我们先调用基类的同名方法得到一个ModelMetadata对象。如果该对象的DisplayName属性为空,在从特性列表中获取DisplayTextAttribute特性并调用其SetDisplayName方法对ModelMetadata的DisplayName属性进行设置。

   1: public class ExtendedDataAnnotationsProvider : DataAnnotationsModelMetadataProvider 
   2: {
   3:     protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType,  Func<object> modelAccessor, Type modelType, string propertyName)
   4:     {
   5:         ModelMetadata metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
   6:         if(string.IsNullOrEmpty(metadata.DisplayName))
   7:         {
   8:             DisplayTextAttribute displayTextAttribute =  attributes.OfType<DisplayTextAttribute>().FirstOrDefault();
   9:             if (null != displayTextAttribute)
  10:             {
  11:                 displayTextAttribute.SetDisplayName(metadata);
  12:             }
  13:         }
  14:         return metadata;
  15:     }
  16: }

对于之前创建的演示实例,如果我们在Global.asax中通过如下的方式对我们自定义的ExtendedDataAnnotationsProvider进行注册,该实例应用同样可以正常运行。

   1: public class MvcApplication : System.Web.HttpApplication
   2: {
   3:     //其他成员
   4:     protected void Application_Start()
   5:     {
   6:         //其他操作
   7:         DisplayTextAttribute.SetResourceType(typeof(Resources));
   8:         ModelMetadataProviders.Current = new ExtendedDataAnnotationsProvider();
   9:     }
  10: }

这个实例直接使用了扩展的DataAnnotationsModelMetadataProvider替换了默认的CachedDataAnnotationsModelMetadataProvider,意味着失去了对成功解析出来的元数据的缓存功能,会对性能造成一定的影响。但是由于CachedDataAnnotationsModelMetadataProvider已经将CreateMetadata方法封闭(Seal),又不能直接继承CachedDataAnnotationsModelMetadataProvider。如果项目里面确实需要使用到类似的用法,可以考虑自己实现缓存。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • ASP.NET MVC Model元数据及其定制: 初识Model元数据

    Contronoller激活之后,ASP.NET MVC会根据当前请求上下文得到目标Action的名称,然后解析出对应的方法并执行之。在整个Action方法的执...

    蒋金楠
  • .NET Core的日志[4]:将日志写入EventLog

    面向Windows的编程人员应该不会对Event Log感到陌生,以至于很多人提到日志,首先想到的就是EventLog。EventLog不仅仅记录了Window...

    蒋金楠
  • WCF技术剖析之二十一:WCF基本异常处理模式[下篇]

    从FaultContractAttribute的定义我们可以看出,该特性可以在同一个目标对象上面多次应用(AllowMultiple = true)。这也很好理...

    蒋金楠
  • Android跨进程通信IPC之14——其他IPC方式

    前面几篇文章,我们介绍了IPC的基础知识和Binder机制,本篇文章主要讲解各种跨进程的通信方式。

    隔壁老李头
  • Spring Cloud实战小贴士:Zuul统一异常处理(一)

    在上一篇《Spring Cloud源码分析(四)Zuul:核心过滤器》一文中,我们详细介绍了Spring Cloud Zuul中自己实现的一些核心过滤器,以及这...

    程序猿DD
  • 收集了740万生物特征信息,五角大楼正在打造一张全球生物识别监控网

    在过去的15年里,美军一直在壮大自己的力量,其中有一种特殊的武器被部署到世界各地,你几乎意识不到它的存在,但是它却十分厉害。

    大数据文摘
  • 深入入门系列--Data Structure--04树

    终于有机会重新回头学习一下一直困扰自身多年的数据结构了,赶脚棒棒哒。一直以来,对数据结构的掌握基本局限于线性表,稍微对树有一丢丢了解,而对于图那基本上就是不懂(...

    用户1216676
  • WinForm/MIS项目开发之中按钮级权限实践

    一、前言      AgileEAS.NET SOA 中间件平台是一款基于基于敏捷并行开发思想和Microsoft .Net构件(组件)开发技术而构建的一个快速...

    魏琼东
  • 重新定义代理的扩展性:介绍 Envoy 和 Istio 中的 WebAssembly 应用

    翻译 Istio 官网 blog 文章,原文:https://istio.io/blog/2020/wasm-announce/。

    黑光技术
  • 苹果系统编程必看书籍

    1.iOS Core Animation: Advanced Techniques ? 很全面的解释了Core Animation,动画原理说得很浅显易懂,结合...

    程序员互动联盟

扫码关注云+社区

领取腾讯云代金券