前面的幾個篇幅主要圍繞控制器的執行過程,奈何執行過程中包含的知識點太龐大了,只能一部分一部分的去講解,在上兩篇中我們看到在控制器方法選擇器根據請求選定了控制器方法后會生成對應的描述對象之后進入過濾器執行過程中,之后也是我們所講的在授權過濾器執行之后會執行對Model的系列操作,中間包括Model元數據解析、Model綁定、Model驗證,最后會通過Web API框架的獨有的方式也就是ParameterBinding參數綁定來執行,在這些操作完畢之后會開始執行行為過濾器,可在控制器方法執行前后進行攔截,從技術方向來說這是AOP思想的一種實現,從業務邏輯上來講也是一種依賴注入,扯遠了,本篇就來談談在ASP.NET Web API框架中的Model元數據,也就是ModelMetadata.
在講解Model綁定之前我還是先來講解一下在Web API中的Model元數據,在我前面的ASP.NET MVC隨筆系列匯總中對MVC框架中的Model的元數據有過講解,對MVC有了解的朋友可以去看看,在ASP.NET Web API框架中的Model元數據的設計思想和MVC框架中的是一樣的,都是對Model進行類型解析,然后已樹形結構呈現或者是給需要它的地方使用。為什么說是樹形結構呢?我們來看一下示例,后面再結合對象類型這么一講解大家就差不多該明白了。
我們先不管在框架中是怎么表示的,先來看一下定義的Model:
示例代碼1-1
public class EmployeesInfo { [Display(Descr在代碼1-1中我定義了一個Model類型為EmployeesInfo,表示員工的信息,在這其中有幾個屬性,其中AddressInfo屬性是下面的Address類型,這里是為了說明Model元數據所表示的對象存在兩種類型,一種是簡單類型,另一種是復雜類型。至于這個類型怎么判斷下面會說,我們還是先來看示例代碼,看下Model經過框架生成為Model元數據過后包含的信息。
示例代碼1-2
public class MetadataController : ApiController { public string Get() { EmployeesInfo employeesInfo = new EmployeesInfo() { Name = "JinYuan", Age = 24, Sex = "男", AddressInfo = new Address() { AddressInfo = "南京市", ZipCode = "210000" } }; ModelMetadata modelMetadata = this.Configuration.Services.GetModelMetadataPRovider().GetMetadataForType(()=>employeesInfo, employeesInfo.GetType()); StringBuilder strBuilder = new StringBuilder(); ModelMetadataAnalysis(strBuilder, modelMetadata); return strBuilder.ToString(); } private void ModelMetadataAnalysis(StringBuilder stringBuilder, ModelMetadata modelMetadata) { if (modelMetadata.IsComplexType == true) { foreach (var metadata in modelMetadata.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
public class ModelMetadata { public ModelMetadata(ModelMetadataProvider provider, Type containerType, Func<object> modelaccessor, Type modelType, string propertyName); public virtual Dictionary<string, object> AdditionalValues { get; } public Type ContainerType { get; } public virtual bool ConvertEmptyStringToNull { get; set; } public virtual string Description { get; set; } public virtual bool IsComplexType { get; } public bool IsNullableValueType { get; } public virtual bool IsReadOnly { get; set; } public object Model { get; set; } public Type ModelType { get; } public virtual IEnumerable<ModelMetadata> Properties { get; } public string PropertyName { get; } protected ModelMetadataProvider Provider { get; set; } public string GetDisplayName(); public virtual IEnumerable<System.Web.Http.Validation.ModelValidator> GetValidators(IEnumerable<System.Web.Http.Validation.ModelValidatorProvider> validatorProviders); }代碼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元數據對象所表示的類型是否是復雜類型,這個怎么判斷的呢?
public virtual bool IsComplexType { get { return !TypeHelper.HasStringConverter(this.ModelType); } }就是看類型是否可以轉換為String類型。
IsReadOnly屬性下面再講,因為在初始化一個Model元數據的時候得到的信息只有這么多,而IsReadOnly屬性則是通過ModelAttribute來控制的。
我們再來看一下ModelMetadataProvider
示例代碼1-4
public abstract class ModelMetadataProvider { protected 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); }在代碼1-4中我們看到Model元數據提供程序ModelMetadataProvider類型中有三個抽象方法,本身也是抽象類,這三個方法的含義來給大家解釋一下。
GetMetadataForProperties()方法是根據容器實例、容器的類型來獲取容器中所有屬性的元數據類型。
GetMetadataForProperty()方法則是根據一個獲取容器實例的委托、容器類型,和要返回的屬性元數據的屬性名稱。
GetMetadataForType()方法就是根據一個類型來獲取這個類型所所表示的元數據,不過委托參數是要能獲取到這個類型的實例。
下面我們回到代碼1-2中,在我們獲取Model元數據提供程序的地方,上面也說過了我們是從哪里獲取到的,現在我們就來看看具體的類型,
示例代碼1-5
this.SetSingle<ModelMetadataProvider>(new DataAnnotationsModelMetadataProvider());
那我們就來看看DataAnnotationsModelMetadataProvider類型中的定義。
示例代碼1-6
public class DataAnnotationsModelMetadataProvider : AssociatedMetadataProvider<CachedDataAnnotationsModelMetadata> { public DataAnnotationsModelMetadataProvider(); protected override CachedDataAnnotationsModelMetadata CreateMetadataFromPrototype(CachedDataAnnotationsModelMetadata prototype, Func<object> modelAccessor); protected override CachedDataAnnotationsModelMetadata CreateMetadataPrototype(IEnumerable<Attribute> attributes, Type containerType, Type modelType, string propertyName); }我們先不管DataAnnotationsModelMetadataProvider類型,而是看它中定義的函數的返回類型CachedDataAnnotationsModelMetadata類型。
CachedDataAnnotationsModelMetadata類型
示例代碼1-7
public class CachedDataAnnotationsModelMetadata : CachedModelMetadata<CachedDataAnnotationsMetadataAttributes> {
新聞熱點
疑難解答