在《上篇》我們已經提到過了,Model中繼資料的定製是通過在作為Model的資料類型極其屬性成員上應用相應的特性來實現,這些用於聲明式中繼資料定義的特性大都定義在System.ComponentModel.DataAnnotations.dll程式集中,程式集的名稱同時也是對應的命名空間名稱,所以我們可以它們為資料註解特性(Data Annotation Attribute),接下來我們來介紹一些常用的資料註解特性,以及它們對於中繼資料具有怎樣的影響。
一、UIHintAttribute
HtmlHelper和HtmlHelper<TModel>定義了一系列的基於Model的模板方法,比如Display/DisplayFor、Editor/EditorFor、DisplayForModel/EditForModel、Lable/LabelFor和DisplayText/DisplayTextFor。所謂模板方法,就是說我們在通過調用這些方法將代表Model的資料呈現在View中的時候,並不對最終呈現的UI元素進行顯失地控制,而採用預設或者指定的模板來決定最終呈現在瀏覽器中的HTML。每個具體的模板均具有相應的名稱,這些模板方法在進行Model呈現的時候根據對應的Model中繼資料得到對應的模板名稱。具體來說,模板的名稱通過ModelMetadata的TemplateHint屬性工作表示,如下面的代碼片斷所示,這是一個字串類型的可讀寫屬性。
1: public class ModelMetadata
2: {
3: //其他成員
4: public virtual string TemplateHint{get;set;}
5: }
ModelMetadata的TemplateHint屬性可以通過UIHintAttribute特性來定製。如下面的代碼片斷所示,UIHintAttribute具有PresentationLayer和UIHint兩個唯讀屬性,分別用於限制展現層的類型(比如“HTML”、“Silverlight”、“WPF”、“WinForms”等和模板名稱,這兩個屬性均在建構函式中初始化。
1: [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple=true)]
2: public class UIHintAttribute : Attribute
3: {
4: //其他成員
5: public UIHintAttribute(string uiHint);
6: public UIHintAttribute(string uiHint, string presentationLayer);
7:
8: public string PresentationLayer { get; }
9: public string UIHint { get; }
10: }
通過應用在UIHintAttribute上的AttributeUsageAttribute定義我們不難看出,由於其AllowMultiple屬性被設定為True,意味著我們可以在相同的目標元素上應用多個UIHintAttribute特性,那麼哪一個會被選擇用於定製Model中繼資料呢?
如果多個UIHintAttribute應用到了相應的元素(類型或者屬性),會先選擇一個PresentationLayer屬性為“MVC”(不區分大小寫)的UIHintAttribute。如果這樣的UIHintAttribute不存在,則選擇一個PresentationLayer屬性值為空白的UIHintAttribute。值得一提的是,如果具有多個匹配的UIHintAttribute可控選擇,系統會選擇第一個,但是通過反射擷取到的Attribute的順序和Attribute被標註的屬性沒有直接的關係。
接下來我們通過一個簡單的執行個體來示範UIHintAttribute特性對Model中繼資料的影響,以及對應用在相同目標元素上的多個UIHintAttribute的選擇策略。考慮到重用性,我們編寫了如下一個靜態輔助方法GetModelMetadata<TModel>用於擷取Model類型為TModel針對指定屬性的Model中繼資料。
1: public static ModelMetadata GetModelMetadata<TModel>(string propertyName)
2: {
3: ModelMetadataProvider provider = ModelMetadataProviders.Current;
4: ModelMetadata containerMetadata = new ModelMetadata(provider, null, () => null, typeof(TModel), null);
5: return containerMetadata.Properties.FirstOrDefault(m => m.PropertyName == propertyName);
6: }
我們通過如下的代碼定義了一個類型為Model的資料類型,三個屬性Foo、Bar和Baz定義其中。對於屬性Bar來說,我們同時應用了兩個模板名稱分別為“Template A”和“Template B”的UIHintAttribute特性,後者將字元“Mvc”作為presentationLayer參數的值。屬性Baz跨平台 app了基於模板名稱“Template A”的UIHintAttribute特性。
1: public class Model
2: {
3: public string Foo { get; set; }
4:
5: [UIHint("Template A")]
6: [UIHint("Template B", "Mvc")]
7: public string Bar { get; set; }
8:
9: [UIHint("Template A")]
10: public string Baz { get; set; }
11: }
現在我們在一個控制台程式中編寫如下的測試程式。我們通過上面定義的輔助方法GetModelMetadata<TModel>建立針對定義在資料類型Model中的Foo、Bar和Baz三個屬性的ModelMetadata,並分別列印出對應的TemplateHint屬性。
1: ModelMetadata foo = GetModelMetadata<Model>("Foo");
2: ModelMetadata bar = GetModelMetadata<Model>("Bar");
3: ModelMetadata baz = GetModelMetadata<Model>("Baz");
4:
5: Console.WriteLine("Foo: {0}", foo.TemplateHint??"N/A");
6: Console.WriteLine("Bar: {0}", bar.TemplateHint ?? "N/A");
7: Console.WriteLine("Baz: {0}", baz.TemplateHint ?? "N/A");
上面的測試程式執行之後會在控制台上產生如下的輸出結果,這和我們上面介紹的關於UIHintAttribute特性針對Model中繼資料的定製,以及針對應用在相同目標元素上的多個UIHintAttribute特性的選擇策略是相符的。
1: Foo: N/A
2: Bar: Template B
3: Baz: Template A