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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<code>publicclassEmployeesInfo</code>
<code> </code><code>{</code>
<code> </code><code>[Display(Description=</code><code>"員工姓名"</code><code>)]</code>
<code> </code><code>publicstringName { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code>
<code> </code><code>[Display(Description=</code><code>"員工年齡"</code><code>)]</code>
<code> </code><code>publicintAge { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>[Display(Description=</code><code>"員工性别"</code><code>)]</code>
<code> </code><code>publicstringSex { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>[Display(Description=</code><code>"員工位址資訊"</code><code>)]</code>
<code> </code><code>publicAddressAddressInfo { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>}</code>
<code> </code>
<code> </code><code>publicclassAddress</code>
<code> </code><code>[Display(Description=</code><code>"位址資訊"</code><code>)]</code>
<code> </code><code>publicstringAddressInfo { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>[Display(Description=</code><code>"郵編"</code><code>)]</code>
<code> </code><code>publicstringZipCode { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code>}</code>
在代碼1-1中我定義了一個Model類型為EmployeesInfo,表示員工的資訊,在這其中有幾個屬性,其中AddressInfo屬性是下面的Address類型,這裡是為了說明Model中繼資料所表示的對象存在兩種類型,一種是簡單類型,另一種是複雜類型。至于這個類型怎麼判斷下面會說,我們還是先來看示例代碼,看下Model經過架構生成為Model中繼資料過後包含的資訊。
示例代碼1-2
25
26
27
28
29
30
31
32
33
34
35
36
37
<code>publicclassMetadataController : ApiController</code>
<code> </code><code>publicstringGet()</code>
<code> </code><code>{</code>
<code> </code><code>EmployeesInfoemployeesInfo=newEmployeesInfo()</code>
<code> </code><code>{</code>
<code> </code><code>Name=</code><code>"JinYuan"</code><code>,</code>
<code> </code><code>Age=24,</code>
<code> </code><code>Sex=</code><code>"男"</code><code>,</code>
<code> </code><code>AddressInfo=newAddress()</code>
<code> </code><code>{</code>
<code> </code><code>AddressInfo=</code><code>"南京市"</code><code>,</code>
<code> </code><code>ZipCode=</code><code>"210000"</code>
<code> </code><code>}</code>
<code> </code><code>};</code>
<code> </code><code>ModelMetadatamodelMetadata=</code><code>this</code><code>.Configuration.Services.GetModelMetadataProvider().GetMetadataForType(()=>employeesInfo, employeesInfo.GetType());</code>
<code> </code><code>StringBuilderstrBuilder=newStringBuilder();</code>
<code> </code><code>ModelMetadataAnalysis(strBuilder, modelMetadata);</code>
<code> </code><code>returnstrBuilder.ToString();</code>
<code> </code><code>}</code>
<code> </code><code>privatevoidModelMetadataAnalysis(StringBuilderstringBuilder, ModelMetadatamodelMetadata)</code>
<code> </code><code>if</code> <code>(modelMetadata.IsComplexType==</code><code>true</code><code>)</code>
<code> </code><code>foreach</code> <code>(varmetadatainmodelMetadata.Properties)</code>
<code> </code><code>ModelMetadataAnalysis(stringBuilder, metadata);</code>
<code> </code><code>}</code>
<code> </code><code>else</code>
<code> </code><code>stringBuilder.AppendLine(modelMetadata.Description).AppendLine(</code><code>"Value:"</code><code>+modelMetadata.Model);</code>
在代碼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
<a href="http://s3.51cto.com/wyfs02/M00/49/A0/wKioL1QW37Tyt0-IAAIr2f90HhA068.jpg" target="_blank"></a>
根據圖1所示的這樣,然後我們再看代碼1-2的實作,最後我們看一下執行的結果。
圖2
<a href="http://s3.51cto.com/wyfs02/M02/49/9E/wKiom1QW37WTid81AALB5hbdWqU648.jpg" target="_blank"></a>
圖2中所示就是從用戶端和浏覽器共同通路傳回的結果值,都是一樣的。
下面我們來講解一下相關的對象類型。
圖3
<a href="http://s3.51cto.com/wyfs02/M00/49/A0/wKioL1QW39-zv8zFAAEPClHIkmE572.jpg" target="_blank"></a>
我們先來看ModelMetadata類型,從圖3中我們可以看到ModelMetadata類型在命名空間System.Web.Http.Metadata下,我們就先來看一下ModelMetadata的定義,
示例代碼1-3
<code>publicclassModelMetadata</code>
<code> </code><code>publicModelMetadata(ModelMetadataProviderprovider, TypecontainerType, Func<</code><code>object</code><code>>modelAccessor, TypemodelType, stringpropertyName);</code>
<code> </code><code>publicvirtualDictionary<</code><code>string</code><code>, </code><code>object</code><code>>AdditionalValues { </code><code>get</code><code>; }</code>
<code> </code><code>publicTypeContainerType { </code><code>get</code><code>; }</code>
<code> </code><code>publicvirtualboolConvertEmptyStringToNull { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>publicvirtualstringDescription { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>publicvirtualboolIsComplexType { </code><code>get</code><code>; }</code>
<code> </code><code>publicboolIsNullableValueType { </code><code>get</code><code>; }</code>
<code> </code><code>publicvirtualboolIsReadOnly { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>publicobjectModel { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>publicTypeModelType { </code><code>get</code><code>; }</code>
<code> </code><code>publicvirtualIEnumerable<ModelMetadata>Properties { </code><code>get</code><code>; }</code>
<code> </code><code>publicstringPropertyName { </code><code>get</code><code>; }</code>
<code> </code><code>protectedModelMetadataProviderProvider { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>publicstringGetDisplayName();</code>
<code> </code><code>publicvirtualIEnumerable<System.Web.Http.Validation.ModelValidator>GetValidators(IEnumerable<System.Web.Http.Validation.ModelValidatorProvider>validatorProviders);</code>
代碼1-3中定義了ModelMetadata類型,我們就從構造函數開始講解。
在構造函數中有五個參數,這些參數有的是跟屬性對應的,就拿第一個參數ModelMetadataProvider類型來說,它對應的就是ModelMetadata類型中的Provider屬性,有的朋友會問這個屬性幹什麼的?還記得上面的示例中講過,在Model中繼資料是複雜類型的時候從Properties屬性中擷取目前所表示類型下的Model中繼資料,就是在擷取的時候拿什麼生成?就是用的這個Provider屬性對應的中繼資料提供程式,這裡也講一下Properties屬性,從它的定義中可以看到,是個IEnumerable<ModelMetadata>類型的屬性,也就是為什麼上面我所說的以樹形結構的方式展現給我們看的原因。下面說到構造函數中的第二個參數執行個體類型,就是這個中繼資料對象所表示的、所對應的類型的容器類型,第三個參數比較有意思,是個Func<Object>委托,它是用來擷取目前中繼資料對象所表示類型的執行個體值,這也就是ModelMetadata類型中Model屬性的屬性值的由來,第四個參數就是目前中繼資料對應的對象類型,也就是對應着ModelMetadata類型中ModelType屬性值,最後一個參數表示屬性名稱,也對應着ModelMetadata類型中的PropertyName屬性值。
下面說說ModelMetadata類型中的其他屬性值,AdditionalValues表示容器屬性,可自行添加任何額外值在其中以鍵值隊的方式表示,這個很多架構中設計對象時都會有,可以是object類型,不過這裡使用鍵值隊來表示。
IsComplexType屬性表示目前Model中繼資料對象所表示的類型是否是複雜類型,這個怎麼判斷的呢?
<code> </code><code>publicvirtualboolIsComplexType</code>
<code> </code><code>get</code>
<code> </code><code>return</code><code>!TypeHelper.HasStringConverter(</code><code>this</code><code>.ModelType);</code>
就是看類型是否可以轉換為String類型。
IsReadOnly屬性下面再講,因為在初始化一個Model中繼資料的時候得到的資訊隻有這麼多,而IsReadOnly屬性則是通過ModelAttribute來控制的。
我們再來看一下ModelMetadataProvider
示例代碼1-4
<code>publicabstractclassModelMetadataProvider</code>
<code> </code><code>protectedModelMetadataProvider();</code>
<code> </code><code>publicabstractIEnumerable<ModelMetadata>GetMetadataForProperties(objectcontainer, TypecontainerType);</code>
<code> </code><code>publicabstractModelMetadataGetMetadataForProperty(Func<</code><code>object</code><code>>modelAccessor, TypecontainerType, stringpropertyName);</code>
<code> </code><code>publicabstractModelMetadataGetMetadataForType(Func<</code><code>object</code><code>>modelAccessor, TypemodelType);</code>
<code> </code><code>}</code>
在代碼1-4中我們看到Model中繼資料提供程式ModelMetadataProvider類型中有三個抽象方法,本身也是抽象類,這三個方法的含義來給大家解釋一下。
GetMetadataForProperties()方法是根據容器執行個體、容器的類型來擷取容器中所有屬性的中繼資料類型。
GetMetadataForProperty()方法則是根據一個擷取容器執行個體的委托、容器類型,和要傳回的屬性中繼資料的屬性名稱。
GetMetadataForType()方法就是根據一個類型來擷取這個類型所所表示的中繼資料,不過委托參數是要能擷取到這個類型的執行個體。
下面我們回到代碼1-2中,在我們擷取Model中繼資料提供程式的地方,上面也說過了我們是從哪裡擷取到的,現在我們就來看看具體的類型,
示例代碼1-5
<code>this</code><code>.SetSingle<ModelMetadataProvider>(newDataAnnotationsModelMetadataProvider());</code>
那我們就來看看DataAnnotationsModelMetadataProvider類型中的定義。
示例代碼1-6
<code>publicclassDataAnnotationsModelMetadataProvider : AssociatedMetadataProvider<CachedDataAnnotationsModelMetadata></code>
<code> </code><code>publicDataAnnotationsModelMetadataProvider();</code>
<code> </code><code>protectedoverrideCachedDataAnnotationsModelMetadataCreateMetadataFromPrototype(CachedDataAnnotationsModelMetadataprototype, Func<</code><code>object</code><code>>modelAccessor);</code>
<code> </code><code>protectedoverrideCachedDataAnnotationsModelMetadataCreateMetadataPrototype(IEnumerable<Attribute>attributes, TypecontainerType, TypemodelType, stringpropertyName);</code>
我們先不管DataAnnotationsModelMetadataProvider類型,而是看它中定義的函數的傳回類型CachedDataAnnotationsModelMetadata類型。
CachedDataAnnotationsModelMetadata類型
示例代碼1-7
<code>publicclassCachedDataAnnotationsModelMetadata : CachedModelMetadata<CachedDataAnnotationsMetadataAttributes></code>
<code> </code><code>publicCachedDataAnnotationsModelMetadata(CachedDataAnnotationsModelMetadataprototype, Func<</code><code>object</code><code>>modelAccessor);</code>
<code> </code><code>publicCachedDataAnnotationsModelMetadata(DataAnnotationsModelMetadataProviderprovider, TypecontainerType, TypemodelType, stringpropertyName, IEnumerable<Attribute>attributes);</code>
<code> </code><code>[SecuritySafeCritical]</code>
<code> </code><code>protectedoverrideboolComputeConvertEmptyStringToNull();</code>
<code> </code><code>protectedoverridestringComputeDescription();</code>
<code> </code><code>protectedoverrideboolComputeIsReadOnly();</code>
讓我們先來看一下CachedModelMetadata<TPrototypeCache>類型
代碼1-8
<code>publicabstractclassCachedModelMetadata<TPrototypeCache> : ModelMetadata</code>
<code> </code><code>protectedCachedModelMetadata(CachedModelMetadata<TPrototypeCache>prototype, Func<</code><code>object</code><code>>modelAccessor);</code>
<code> </code><code>protectedCachedModelMetadata(DataAnnotationsModelMetadataProviderprovider, TypecontainerType, TypemodelType, stringpropertyName, TPrototypeCache prototypeCache);</code>
<code> </code><code>publicoverridesealedboolConvertEmptyStringToNull { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>publicoverridesealedstringDescription { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>publicoverridesealedboolIsComplexType { </code><code>get</code><code>; }</code>
<code> </code><code>publicoverridesealedboolIsReadOnly { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>protected</code> <code>TPrototypeCache PrototypeCache { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>protectedvirtualboolComputeConvertEmptyStringToNull();</code>
<code> </code><code>protectedvirtualstringComputeDescription();</code>
<code> </code><code>protectedvirtualboolComputeIsComplexType();</code>
<code> </code><code>protectedvirtualboolComputeIsReadOnly();</code>
看到這裡怎麼感覺這麼亂的呢亂七八糟的,比較煩躁啊!!!!大家莫慌。
前面說到了Model中繼資料在初始化的時候便會初始了很多的值,可是大家有沒有想過Model中繼資料的真正的作用?在初始化的時候可以說我們的Model中繼資料已經包含了所表示對象的對象類型和值一些基本的資訊,但是我們要給Model上附加額外的動作,比如說控制這個某某屬性是隻讀的或者是讓某某屬性換一個顯示的方式,這種事情是在初始化的時候Model中繼資料做不了的,那咋整?
看代碼1-8中最下面的Compute開頭的四個方法,這四個方法就是用來擷取我們設定的動作然後設定到Model中繼資料上的,當然看了一下實作并不是我們想要的結果,而是通過代碼1-7中定義的CachedDataAnnotationsModelMetadata類型來實作的,在代碼1-7中CachedDataAnnotationsModelMetadata類型繼承的是CachedModelMetadata<CachedDataAnnotationsMetadataAttributes>類型,現在我們就來看看CachedDataAnnotationsMetadataAttributes類型的定義。
CachedDataAnnotationsMetadataAttributes類型
示例代碼1-9
<code>publicclassCachedDataAnnotationsMetadataAttributes</code>
<code> </code><code>publicCachedDataAnnotationsMetadataAttributes(IEnumerable<Attribute>attributes);</code>
<code> </code><code>publicSystem.ComponentModel.DataAnnotations.DisplayAttributeDisplay { </code><code>get</code><code>; protectedset; }</code>
<code> </code><code>publicSystem.ComponentModel.DataAnnotations.DisplayFormatAttributeDisplayFormat { </code><code>get</code><code>; protectedset; }</code>
<code> </code><code>publicSystem.ComponentModel.DataAnnotations.EditableAttributeEditable { </code><code>get</code><code>; protectedset; }</code>
<code> </code><code>publicReadOnlyAttributeReadOnly { </code><code>get</code><code>; protectedset; }</code>
代碼1-9中的CachedDataAnnotationsMetadataAttributes類型就是應用于中繼資料特性多個類型的封裝,這裡我們暫且不管,知道CachedDataAnnotationsMetadataAttributes類型是個啥就行了。現在我們回到代碼1-7中看看那幾個方法的具體實作方式。
代碼1-10
<code> </code><code>[SecuritySafeCritical]</code>
<code> </code><code>protectedoverridestringComputeDescription()</code>
<code> </code><code>if</code> <code>(</code><code>base</code><code>.PrototypeCache.Display==</code><code>null</code><code>)</code>
<code> </code><code>returnbase.ComputeDescription();</code>
<code> </code><code>returnbase.PrototypeCache.Display.GetDescription();</code>
我挑了其中一個,這裡可以清楚的看到是要調用基類當中的PrototypeCache屬性,那麼這裡的PrototypeCache屬性對應的是什麼類型?
大家可以看一下代碼1-8中PrototypeCache屬性對應的是什麼類型,是一個泛型類型,而在CachedDataAnnotationsModelMetadata類型繼承的時候泛型類型我們可以在代碼1-7中看到,也就是代碼1-9定義的類型。
最後我們看下示意圖。
圖4
<a href="http://s3.51cto.com/wyfs02/M00/49/9E/wKiom1QW4CeAxB4VAAChgnhCyd8480.jpg" target="_blank"></a>
最後總結一句在Model中繼資料初始化的時候便會完成一些初始值的初始化,而對于Model上的行為設定,則需要通過CachedDataAnnotationsMetadataAttributes類型來執行設定了,而CachedDataAnnotationsMetadataAttributes類型中對應的幾個屬性的類型大家一試便知。
本文轉自jinyuan0829 51CTO部落格,原文連結http://blog.51cto.com/jinyuan/1553098:,如需轉載請自行聯系原作者