ASP.NET Web API Model-ModelMetadata
前言
前面的几个篇幅主要围绕控制器的执行过程,奈何执行过程中包含的知识点太庞大了,只能一部分一部分的去讲解,在上两篇中我们看到在控制器方法选择器根据请求选定了控制器方法后会生成对应的描述对象之后进入过滤器执行过程中,之后也是我们所讲的在授权过滤器执行之后会执行对Model的系列操作,中间包括Model元数据解析、Model绑定、Model验证,最后会通过Web API框架的独有的方式也就是ParameterBinding参数绑定来执行,在这些操作完毕之后会开始执行行为过滤器,可在控制器方法执行前后进行拦截,从技术方向来说这是AOP思想的一种实现,从业务逻辑上来讲也是一种依赖注入,扯远了,本篇就来谈谈在ASP.NET Web API框架中的Model元数据,也就是ModelMetadata.
Model-ModelMetadata(对象介绍、基础知识篇)
在讲解Model绑定之前我还是先来讲解一下在Web API中的Model元数据,在我前面的ASP.NET MVC随笔系列汇总中对MVC框架中的Model的元数据有过讲解,对MVC有了解的朋友可以去看看,在ASP.NET Web API框架中的Model元数据的设计思想和MVC框架中的是一样的,都是对Model进行类型解析,然后已树形结构呈现或者是给需要它的地方使用。为什么说是树形结构呢?我们来看一下示例,后面再结合对象类型这么一讲解大家就差不多该明白了。
我们先不管在框架中是怎么表示的,先来看一下定义的Model:
示例代码1-1
publicclassEmployeesInfo { [Display(Description="员工姓名")] publicstringName { get; set; } [Display(Description="员工年龄")] publicintAge { get; set; } [Display(Description="员工性别")] publicstringSex { get; set; } [Display(Description="员工地址信息")] publicAddressAddressInfo { get; set; } } publicclassAddress { [Display(Description="地址信息")] publicstringAddressInfo { get; set; } [Display(Description="邮编")] publicstringZipCode { get; set; } }
在代码1-1中我定义了一个Model类型为EmployeesInfo,表示员工的信息,在这其中有几个属性,其中AddressInfo属性是下面的Address类型,这里是为了说明Model元数据所表示的对象存在两种类型,一种是简单类型,另一种是复杂类型。至于这个类型怎么判断下面会说,我们还是先来看示例代码,看下Model经过框架生成为Model元数据过后包含的信息。
示例代码1-2
publicclassMetadataController : ApiController { publicstringGet() { EmployeesInfoemployeesInfo=newEmployeesInfo() { Name="JinYuan", Age=24, Sex="男", AddressInfo=newAddress() { AddressInfo="南京市", ZipCode="210000" } }; ModelMetadatamodelMetadata=this.Configuration.Services.GetModelMetadataProvider().GetMetadataForType(()=>employeesInfo, employeesInfo.GetType()); StringBuilderstrBuilder=newStringBuilder(); ModelMetadataAnalysis(strBuilder, modelMetadata); returnstrBuilder.ToString(); } privatevoidModelMetadataAnalysis(StringBuilderstringBuilder, ModelMetadatamodelMetadata) { if (modelMetadata.IsComplexType==true) { foreach (varmetadatainmodelMetadata.Properties) { ModelMetadataAnalysis(stringBuilder, metadata); } } else { stringBuilder.AppendLine(modelMetadata.Description).AppendLine("Value:"+modelMetadata.Model); } } }
在代码1-2中,我首先定义了一个控制器,并且定义了一个Get()方法,返回类型为string类型,然后在Get()方法中实例化Model,也就是我们代码1-1中的定义,在这之后我根据当前控制器基类所包含的HttpConfiguration类型的属性Configuration,从ServicesContainer 类型的Services属性中获取到Model元数据提供程序,从而使用这个提供程序来根据Model类型生成Model元数据,这里起初生成好的Model元数据也就是modelMetadata变量,它暂时只是一个类型的的表示,也就是EmployeesInfo类型,并且其中并不包含EmployeesInfo类型中属性的元数据,这里要注意的是不包含只是暂时的。
然后下面使用了一个方法ModelMetadataAnalysis(),首先判断当前的Model元数据表示的类型是不是复杂类型,如果是的话就读取这个Model元数据中的Properties属性,注意了在读取的时候,也就是ModelMetadataAnalysis()方法中的参数modelMetadata就会开始使用自身的Model元数据提供程序来生成自身所表示类型下属性的Model元数据。由此可以看到上面的代码实现中只是对简单类型进行了输出。我们看一下示意图。
图1
根据图1所示的这样,然后我们再看代码1-2的实现,最后我们看一下执行的结果。
图2
图2中所示就是从客户端和浏览器共同访问返回的结果值,都是一样的。
下面我们来讲解一下相关的对象类型。
图3
我们先来看ModelMetadata类型,从图3中我们可以看到ModelMetadata类型在命名空间System.Web.Http.Metadata下,我们就先来看一下ModelMetadata的定义,
示例代码1-3
publicclassModelMetadata { publicModelMetadata(ModelMetadataProviderprovider, TypecontainerType, Func
代码1-3中定义了ModelMetadata类型,我们就从构造函数开始讲解。
在构造函数中有五个参数,这些参数有的是跟属性对应的,就拿第一个参数ModelMetadataProvider类型来说,它对应的就是ModelMetadata类型中的Provider属性,有的朋友会问这个属性干什么的?还记得上面的示例中讲过,在Model元数据是复杂类型的时候从Properties属性中获取当前所表示类型下的Model元数据,就是在获取的时候拿什么生成?就是用的这个Provider属性对应的元数据提供程序,这里也讲一下Properties属性,从它的定义中可以看到,是个IEnumerable
下面说说ModelMetadata类型中的其他属性值,AdditionalValues表示容器属性,可自行添加任何额外值在其中以键值队的方式表示,这个很多框架中设计对象时都会有,可以是object类型,不过这里使用键值队来表示。
IsComplexType属性表示当前Model元数据对象所表示的类型是否是复杂类型,这个怎么判断的呢?
publicvirtualboolIsComplexType { get { return!TypeHelper.HasStringConverter(this.ModelType); } }
就是看类型是否可以转换为String类型。
IsReadOnly属性下面再讲,因为在初始化一个Model元数据的时候得到的信息只有这么多,而IsReadOnly属性则是通过ModelAttribute来控制的。
我们再来看一下ModelMetadataProvider
示例代码1-4
publicabstractclassModelMetadataProvider { protectedModelMetadataProvider(); publicabstractIEnumerableGetMetadataForProperties(objectcontainer, TypecontainerType); publicabstractModelMetadataGetMetadataForProperty(Func
在代码1-4中我们看到Model元数据提供程序ModelMetadataProvider类型中有三个抽象方法,本身也是抽象类,这三个方法的含义来给大家解释一下。
GetMetadataForProperties()方法是根据容器实例、容器的类型来获取容器中所有属性的元数据类型。
GetMetadataForProperty()方法则是根据一个获取容器实例的委托、容器类型,和要返回的属性元数据的属性名称。
GetMetadataForType()方法就是根据一个类型来获取这个类型所所表示的元数据,不过委托参数是要能获取到这个类型的实例。
下面我们回到代码1-2中,在我们获取Model元数据提供程序的地方,上面也说过了我们是从哪里获取到的,现在我们就来看看具体的类型,
示例代码1-5
this.SetSingle(newDataAnnotationsModelMetadataProvider());
那我们就来看看DataAnnotationsModelMetadataProvider类型中的定义。
示例代码1-6
publicclassDataAnnotationsModelMetadataProvider : AssociatedMetadataProvider{ publicDataAnnotationsModelMetadataProvider(); protectedoverrideCachedDataAnnotationsModelMetadataCreateMetadataFromPrototype(CachedDataAnnotationsModelMetadataprototype, Func modelAccessor); protectedoverrideCachedDataAnnotationsModelMetadataCreateMetadataPrototype(IEnumerable attributes, TypecontainerType, TypemodelType, stringpropertyName); }
我们先不管DataAnnotationsModelMetadataProvider类型,而是看它中定义的函数的返回类型CachedDataAnnotationsModelMetadata类型。
CachedDataAnnotationsModelMetadata类型
示例代码1-7
publicclassCachedDataAnnotationsModelMetadata : CachedModelMetadata{ publicCachedDataAnnotationsModelMetadata(CachedDataAnnotationsModelMetadataprototype, Func modelAccessor); publicCachedDataAnnotationsModelMetadata(DataAnnotationsModelMetadataProviderprovider, TypecontainerType, TypemodelType, stringpropertyName, IEnumerable attributes); [SecuritySafeCritical] protectedoverrideboolComputeConvertEmptyStringToNull(); [SecuritySafeCritical] protectedoverridestringComputeDescription(); [SecuritySafeCritical] protectedoverrideboolComputeIsReadOnly(); }
从代码1-7中我们可以看到CachedDataAnnotationsModelMetadata类型继承自CachedModelMetadata
让我们先来看一下CachedModelMetadata
代码1-8
publicabstractclassCachedModelMetadata: ModelMetadata { protectedCachedModelMetadata(CachedModelMetadata prototype, Func modelAccessor); protectedCachedModelMetadata(DataAnnotationsModelMetadataProviderprovider, TypecontainerType, TypemodelType, stringpropertyName, TPrototypeCache prototypeCache); publicoverridesealedboolConvertEmptyStringToNull { get; set; } publicoverridesealedstringDescription { get; set; } publicoverridesealedboolIsComplexType { get; } publicoverridesealedboolIsReadOnly { get; set; } protected TPrototypeCache PrototypeCache { get; set; } protectedvirtualboolComputeConvertEmptyStringToNull(); protectedvirtualstringComputeDescription(); protectedvirtualboolComputeIsComplexType(); protectedvirtualboolComputeIsReadOnly(); }
看到这里怎么感觉这么乱的呢乱七八糟的,比较烦躁啊!!!!大家莫慌。
前面说到了Model元数据在初始化的时候便会初始了很多的值,可是大家有没有想过Model元数据的真正的作用?在初始化的时候可以说我们的Model元数据已经包含了所表示对象的对象类型和值一些基本的信息,但是我们要给Model上附加额外的动作,比如说控制这个某某属性是只读的或者是让某某属性换一个显示的方式,这种事情是在初始化的时候Model元数据做不了的,那咋整?
看代码1-8中最下面的Compute开头的四个方法,这四个方法就是用来获取我们设置的动作然后设置到Model元数据上的,当然看了一下实现并不是我们想要的结果,而是通过代码1-7中定义的CachedDataAnnotationsModelMetadata类型来实现的,在代码1-7中CachedDataAnnotationsModelMetadata类型继承的是CachedModelMetadata
CachedDataAnnotationsMetadataAttributes类型
示例代码1-9
publicclassCachedDataAnnotationsMetadataAttributes { publicCachedDataAnnotationsMetadataAttributes(IEnumerableattributes); publicSystem.ComponentModel.DataAnnotations.DisplayAttributeDisplay { get; protectedset; } publicSystem.ComponentModel.DataAnnotations.DisplayFormatAttributeDisplayFormat { get; protectedset; } publicSystem.ComponentModel.DataAnnotations.EditableAttributeEditable { get; protectedset; } publicReadOnlyAttributeReadOnly { get; protectedset; } }
代码1-9中的CachedDataAnnotationsMetadataAttributes类型就是应用于元数据特性多个类型的封装,这里我们暂且不管,知道CachedDataAnnotationsMetadataAttributes类型是个啥就行了。现在我们回到代码1-7中看看那几个方法的具体实现方式。
代码1-10
[SecuritySafeCritical] protectedoverridestringComputeDescription() { if (base.PrototypeCache.Display==null) { returnbase.ComputeDescription(); } returnbase.PrototypeCache.Display.GetDescription(); }
我挑了其中一个,这里可以清楚的看到是要调用基类当中的PrototypeCache属性,那么这里的PrototypeCache属性对应的是什么类型?
大家可以看一下代码1-8中PrototypeCache属性对应的是什么类型,是一个泛型类型,而在CachedDataAnnotationsModelMetadata类型继承的时候泛型类型我们可以在代码1-7中看到,也就是代码1-9定义的类型。
最后我们看下示意图。
图4
最后总结一句在Model元数据初始化的时候便会完成一些初始值的初始化,而对于Model上的行为设置,则需要通过CachedDataAnnotationsMetadataAttributes类型来执行设置了,而CachedDataAnnotationsMetadataAttributes类型中对应的几个属性的类型大家一试便知。