ASP.NET MVC的Model元數據提供機製的實現
在前麵的介紹中我們已經提到過表示Model元數據的ModelMetadata對象最終是通過一個名為ModelMetadataProvider的組件提供的,接下來我們著重討論基於ModelMetadataProvider的Model元數據提供機製及其擴展。[本文已經同步到《How ASP.NET MVC Works?》中]
在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: }
通過前麵的介紹我們知道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元數據提供係統的擴展主要體現在對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。如果項目裏麵確實需要使用到類似的用法,可以考慮自己實現緩存。
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-26 11:34:42