在《上篇》我們已經提到過了,Model元數據的定制是通過在作為Model的數據類型極其屬性成員上應用相應的特性來實現,這些用於聲明式元數據定義的特性大都定義在System.ComponentModel.DataAnnotations.dll程序集中,程序集的名稱同時也是對應的命名空間名稱,所以我們可以它們為數據注解特性(Data Annotation Attribute),接下來我們來介紹一些常用的數據注解特性,以及它們對於元數據具有怎樣的影響。[本文已經同步到《How ASP.NET MVC Works?》中] 目錄 一、UIHintAttribute 二、HiddenInputAttribute與ScaffoldColumnAttribute 三、DataTypeAttribute與DisplayFormatAttribute 四、EditableAttribute與ReadOnlyAttribute 五、DisplayAttribute與DisplayNameAttribute 六、RequiredAttribute 一、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通用應用了基於模板名稱“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 二、HiddenInputAttribute與ScaffoldColumnAttribute 一個作為Model的數據類型往往具有一個唯一標識,當我們以編輯模式將Model對象在View中呈現的時候,往往不允許用於對作為唯一標識的屬性進行修改。如果ID不具有可讀性(比如是一個隨機數或者GUID),有時候甚至不希望讓它顯示在界面上。這個時候我們就會使用到特性HiddenInputAttribute。 HiddenInputAttribute並沒有定義在System.ComponentModel.DataAnnotations命名空間下,它的命名空間為System.Web.Mvc,所以該特使是專門為ASP.NET MVC設計的。顧名思義,HiddenInputAttribute會將目標對象以類型為hidden的<input/>元素呈現出來。在默認的情況下,應用了HiddenInputAttribute特性的目標對象依然會以只讀的形式顯示出來。如果不希望顯示,可以將如下所示的布爾類型的DisplayValue設置為False(默認值為False)。 1: [AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, AllowMultiple=false, Inherited=true)] 2: public sealed class HiddenInputAttribute : Attribute 3: { 4: public HiddenInputAttribute(); 5: public bool DisplayValue { get;set; } 6: } 同樣以前面定義的Model類型為例,現在我們將HiddenInputAttribute特性應用在屬性Foo和Bar上,后者將DisplayValue設置為False。 1: public class Model 2: { 3: [HiddenInput] 4: public string Foo { get; set; } 5: 6: [HiddenInput(DisplayValue = false)] 7: public string Bar { get; set; } 8: 9: public string Baz { get; set; } 10: } 現在我們通過調用HtmlHelper<TModel>的擴展方法EditForModel方法將一個具體的Model對象(new Model { Foo = "foo", Bar = "bar", Baz = "baz" })顯示在某個基於Model類型的強類型View中。最終呈現出來的效果如下圖所示,我們可以看到針對應用了HiddenInputAttribute的兩個屬性Foo和Bar,前者以只讀的形式顯示出來;后者卻在界面上看不到。 如下所示的用於呈現Foo、Bar和Baz三個屬性對應的HTML,從中我們可以清楚地看到兩個應用了HiddenInputAttribute的屬性,不論其DisplayValue屬性具有怎樣的值,均對應着一個的類型為hidden的<input>元素。 1: <div class="editor-label"><label for="Foo">Foo</label></div> 2: <div class="editor-field">foo<input id="Foo" name="Foo" type="hidden" value="foo" /> ... 3: </div> 4: 5: <input id="Bar" name="Bar" type="hidden" value="bar" /> 6: 7: <div class="editor-label"><label for="Baz">Baz</label></div> 8: <div class="editor-field"> 9: <input class="text-box single-line" id="Baz" name="Baz" type="text" value="baz" />... 10: </span> 11: </div> HiddenInputAttribute針對Model元數據的定制體現ModelMetadata的如下兩個屬性的上,其中一個就是上面介紹的TemplateHint,另一個則是布爾類型的屬性HideSurroundingHtml,表示目標元數是否需要通過相應的HTML呈現在UI界面上。具體來說,針對應用了HiddenInputAttribute的目標元素對應的ModelMetadata對象,其被設置為“HiddenInput”,並將其DisplayValue屬性為HideSurroundingHtml屬性賦值。“HiddenInput”為ASP.NET MVC自身定義的一個默認模板名稱,也就是說當目標元素應用了HiddenInputAttribute特性,這個默認模板別用來實現對其的UI呈現。 1: public class ModelMetadata 2: { 3: //其他成員 4: public virtual string TemplateHint{get;set;} 5: public virtual bool HideSurroundingHtml { get; set; } 6: } 我們同樣通過一個測試程序來驗證HiddenInputAttribute特性對Model元素據的定制。針對上面定義的Model類型(Foo和Bar屬性應用了HiddenInputAttribute特性),我們通過如下的測試程序將基於Foo、Bar和Baz屬性的三個ModelMetadata對象獲取出來,然后分別打印出它們的TemplateHint和HideSurroundingHtml屬性。 1: ModelMetadata foo = GetModelMetadata<Model>("Foo"); 2: ModelMetadata bar = GetModelMetadata<Model>("Bar"); 3: ModelMetadata baz = GetModelMetadata<Model>("Baz"); 4: 5: Console.WriteLine("{0,-5}{1, -14}{2, -20}", "", "TemplateHint", "HideSurroundingHtml"); 6: Console.WriteLine(new string('-', 40)); 7: 8: Console.WriteLine("{0,-5}{1, -14}{2, -20}", "Foo", foo.TemplateHint ?? "N/A", foo.HideSurroundingHtml); 9: Console.WriteLine("{0,-5}{1, -14}{2, -20}", "Bar", bar.TemplateHint ?? "N/A", bar.HideSurroundingHtml); 10: Console.WriteLine("{0,-5}{1, -14}{2, -20}", "Baz", baz.TemplateHint ?? "N/A", baz.HideSurroundingHtml); 上面的程序運行之后會在控制台上產生如下的輸出結果,這和我們前面的介紹是相匹配的。 1: TemplateHint HideSurroundingHtml 2: ---------------------------------------- 3: Foo HiddenInput False 4: Bar HiddenInput True 5: Baz N/A False 有的讀者可能會問這樣一個問題,UIHintAttribute和HiddenInputAttribute都會設置表示Model元數據的ModelMetadata對象的TemplateHint屬性,如果兩個特性均應用到相同的目標元素上,最終生成的ModelMetadata對象具有怎樣的TemplateHint屬性值呢?答案是:UIHintAttribute具有更高的優先級。 對於應用了HiddenInputAttribute特性目標元素,不論其DisplayValue具有怎樣的值,都會出現在通過模板方法生成的HTML中,如果我們希望將它從HTML中移除,我們可以應用另一個叫作ScaffoldColumnAttribute的特性。我們將通過預定義模板自動生成HTML的方式成為“基架(Scaffolding)”,ScaffoldColumnAttribute中的ScaffoldColumn代表存在於“基架”中並最終呈現在HTML中的字典,而該特性本身則用於控制目標元素是否應該存在於基架之中。如下面的代碼片斷所示,ScaffoldColumnAttribute具有一個布爾類型的只讀屬性Scaffold表示目標元素是否應該存在於呈現在最終生成的HTML的基架中,該屬性在構造函數中初始化。 1: [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple=false)] 2: public class ScaffoldColumnAttribute : Attribute 3: { 4: public ScaffoldColumnAttribute(bool scaffold); 5: public bool Scaffold { get; } 6: } ScaffoldColumnAttribute最終用於控制用於表示針對目標對象的ModelMetadata對象的ShowForDisplay和ShowForEdit屬性。如下面的代碼所示,這是兩個布爾類型的屬性,分別表示目標元素是否應該出現在顯示和編輯模式的基架中。如果ShowForDisplay的屬性為False,在調用模板方法EditorFor/EditorForModel方法時目標元素將不會出現在最終生成的HTML中;同理,在通過DisplayFor/DisplayForModel方法生成的HTML將不會包含ShowForDisplay為False的元素。這兩個屬性值在默認情況下均為True。 1: public class ModelMetadata 2: { 3: //其他成員 4: public virtual bool ShowForDisplay { get; set; } 5: public virtual bool ShowForEdit { get; set; } 6: } 三、DataTypeAttribute與DisplayFormatAttribute 用於指定數據類型的DataTypeAttribute特性是我們經常使用的數據標注特性。這里所說的數據類型不是我們理解的CLR類型,而是通過DataType枚舉表示的具有某種顯示格式的數據類型。如下面的代碼片斷所示,DataType枚舉定義了一系列包括時間、日期、電話號碼、貨幣、Html、電子郵箱地址在內的數據類型。 1: public enum DataType 2: { 3: Custom, 4: DateTime, 5: Date, 6: Time, 7: Duration, 8: PhoneNumber, 9: Currency, 10: Text, 11: Html, 12: MultilineText, 13: EmailAddress, 14: Password, 15: Url, 16: ImageUrl, 17: CreditCard, 18: PostalCode, 19: Upload 20: } 為Model元數據設置數據類型的DataTypeAttribute實際上是一個驗證特性。如下面的代碼片斷所示,DataTypeAttribute直接繼承自ValidationAttribute,關於驗證和驗證特性,我們會在后續的博文進行單獨討論。除了具有一個DataType枚舉類型的DataType只讀屬性之外,DataTypeAttribute還具有一個字符串類型的表示自定義數據類型的CustomDataType屬性,它們均在相應的構造函數中初始化。 1: [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method, AllowMultiple=false)] 2: public class DataTypeAttribute : ValidationAttribute 3: { 4: public DataTypeAttribute(DataType dataType); 5: public DataTypeAttribute(string customDataType); 6: 7: public virtual string GetDataTypeName(); 8: public override bool IsValid(object value); 9: 10: public string CustomDataType { get; } 11: public DataType DataType { get; } 12: public DisplayFormatAttribute DisplayFormat { get; } 13: } DataTypeAttribute的只讀屬性DisplayFormat涉及到另一個用於進行格式化的DisplayFormatAttribute特性,它的主要目的在於指定一個格式化字符串以控制數據在UI界面上的顯示格式。如下面的代碼片斷所示,格式化字符串通過屬性DataFormatString表示,布爾類型的屬性ApplyFormatInEditMode和HtmlEncode表示格式化規則是否需要應用到編輯模式,以及是否需要對目標內容實施HTML編碼, 默認情況下這兩個屬性值分別為False和True。DisplayFormatAttribute的屬性NullDisplayText和ConvertEmptyStringToNull與空值/空字符串的處理有關,前者表示針對空值(Null)對象的顯示文本,后者表示是否將傳入的空字符串轉換成Null。 1: [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple=false)] 2: public class DisplayFormatAttribute : Attribute 3: { 4: public DisplayFormatAttribute(); 5: 6: public string DataFormatString { get; set; } 7: 8: public bool ApplyFormatInEditMode { get; set; } 9: public bool HtmlEncode { get; set; } 10: 11: public string NullDisplayText { get; set; } 12: public bool ConvertEmptyStringToNull { get; set; } 13: } 定義在DataType枚舉中的部分數據類型(比如Date、Time、Duration和Currency等)都具有各自的格式。當DataTypeAttribute通過指定的DataType枚舉值被創建的時候,會根據對應的格式創建一個DisplayFormatAttribute對象作為其DisplayFormat屬性值。 DataTypeAttribute和DisplayFormatAttribute對Model元數據的定制涉及到ModelMetadata的如下屬性。其中DataTypeAttribute中設置的數據類型對應於ModelMetadata的DataTypeName屬性,而DisplayFormatAttribute的ConvertEmptyStringToNull和NullDisplayText屬性對應着ModelMetadata的同名屬性。通過DisplayFormatAttribute的DataFormatString屬性設置的格式化字符串會賦值給ModelMetadata的DisplayFormatString屬性,表示顯示模式下的格式化字符串。如果ApplyFormatInEditMode屬性為True,該屬性會賦值給ModelMetadata的EditFormatString屬性,表示編輯模式下的格式化字符串。 1: public class ModelMetadata 2: { 3: //其他成員 4: public virtual string DataTypeName { get; set; } 5: 6: public virtual string DisplayFormatString { get; set; } 7: public virtual string EditFormatString { get; set; } 8: 9: public virtual bool ConvertEmptyStringToNull { get; set; } 10: public virtual string NullDisplayText { get; set; } 11: } ModelMetadata表示數據類型名稱DataTypeName屬性類型為字符串,針對定義在DataType枚舉中的每個枚舉項均對應着一個預定義的字符串表示對應的數據類型。如果通過DataTypeAttribute特性已字符串的方式指定一個自定義數據類型,該字符串直接作為ModelMetadata的DataTypeName屬性值。如果沒有顯示地對數據類型進行設置,並且DisplayFormatAttribute的HtmlEncode屬性為False(不需要隊目標內容進行HTML編碼),生成的ModelMetadata對象的DataTypeName屬性值為Html(相當於DataType.Html). 由於一個DataTypeAttribute對應着一個DisplayFormatAttribute,如果兩個這兩個特性同時應用在了相同的目標元素上,在設置沖突的情況下后者(DisplayFormatAttribute)具有更高的優先級。 四、EditableAttribute與ReadOnlyAttribute EditableAttribute和ReadonlyAttribute用於控制目標元素的可讀寫性。如下面的代碼片斷所示,EditableAttribute和ReadonlyAttribute分別具有一個布爾類型的屬性AllowEdit和IsReadOnly分別表示是否運行編輯和是否只讀。 1: [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple=false, Inherited=true)] 2: public sealed class EditableAttribute : Attribute 3: { 4: //其他成員 5: public EditableAttribute(bool allowEdit); 6: public bool AllowEdit { get; private set; } 7: } 8: 9: [AttributeUsage(AttributeTargets.All)] 10: public sealed class ReadOnlyAttribute : Attribute 11: { 12: //其他成員 13: public ReadOnlyAttribute(bool isReadOnly); 14: public bool IsReadOnly { get; } 15: } 不允許編輯即為只讀,所以這兩個標注特性具有相同的作用。它們共同控制着ModelMetadata如下所示的IsReadOnly屬性。如果同時將EditableAttribute和ReadonlyAttribute應用到相同的目標元素上並且作出相反的設置(讓EditableAttribute的AllowEdit屬性和ReadonlyAttribute的IsReadOnly屬性具有相同的布爾值),EditableAttribute特性具有更高的優先級。 1: public class ModelMetadata 2: { 3: //其他成員 4: public virtual bool IsReadOnly{get; set;} 5: } 我們通過如下的方式將特性EditableAttribute和ReadonlyAttribute同時應用到數據類型Model的Bar和Baz屬性上,並在讀寫性上作出相反的設置。而在屬性Foo應用ReadonlyAttribute特性將其設為只讀。 1: public class Model 2: { 3: [ReadOnly(true)] 4: public string Foo { get; set; } 5: 6: [Editable(true)] 7: [ReadOnly(true)] 8: public string Bar { get; set; } 9: 10: [Editable(false)] 11: [ReadOnly(false)] 12: public string Baz { get; set; } 13: } 然后我們采用如下的代碼調用之前定義的輔助方法GetModelMetadata<TModel>得到針對定義在Model類型中三個屬性的ModelMetadata對象,最終將它們的IsReadOnly打印出來。 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.IsReadOnly); 6: Console.WriteLine("Bar: {0}", bar.IsReadOnly); 7: Console.WriteLine("Baz: {0}", baz.IsReadOnly); 上面的測試程序執行之后會在控制台上產生如下的輸出結果。由於Foo屬性上僅僅應用了ReadonlyAttribute特性,所以它控制了ModelMetadata的IsReadOnly屬性;而Bar和Baz屬性則同時應用EditableAttribute和ReadonlyAttribute兩個特性,ModelMetadata的IsReadOnly屬性最終通過EditableAttribute特性來控制。 1: Foo: True 2: Bar: False 3: Baz: True 五、DisplayAttribute與DisplayNameAttribute DisplayAttribute特性為目標元素定義一些說明性文字。如下面的代碼片斷所示,DisplayAttribute具有5個基本屬性,其中Name和ShortName為目標元素設置一個顯示名稱和簡短的顯示名稱。屬性Description和Order為目標元素設置描述性文字和用於排序的權重。字符串類型的Prompt屬性為目標元素設置一個字符串,它在UI界面上以水印的方式呈現。 1: [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method, AllowMultiple=false)] 2: public sealed class DisplayAttribute : Attribute 3: { 4: //其他成員 5: public DisplayAttribute(); 6: 7: public string GetName(); 8: public string GetShortName(); 9: public string GetDescription(); 10: public int? GetOrder(); 11: public string GetPrompt(); 12: 13: public string Name { get; set; } 14: public string ShortName { get; set; } 15: public string Description { get; set; } 16: public int Order { get; set; } 17: public string Prompt { get; set; } 18: 19: public Type ResourceType { get; set; } 20: } 由於DisplayAttribute特性設置的文字最終都是面向最終用戶的,所以有必要對其進行本地化(Localization),為此該特性允許我們通過資源文件的方式來定義它們。DisplayAttribute特性的ResourceType代表采用的資源文件生成的類型,如果我們對該屬性進行了顯式設置,上述5個屬性值將會被認為是對應的資源條目的名稱。正因為如此,如果我們需要得到最終用於顯示的文字,不能通過相應的屬性,而需要通過相應的GetXxx方法。 另一個定義在命名空間System.ComponentModel下的DisplayNameAttribute特性則專門用於設置目標元素的顯示名稱,如下面的代碼片斷所示,目標元素的顯示名稱通過只讀屬性DisplayName表示,該屬性在構造函數中被初始化。如果調用默認的構造函數,該屬性會被設置為空字符串。 1: [AttributeUsage(AttributeTargets.Event | AttributeTargets.Property | AttributeTargets.Method | AttributeTargets.Class)] 2: public class DisplayNameAttribute : Attribute 3: { 4: public DisplayNameAttribute(); 5: public DisplayNameAttribute(string displayName); 6: 7: public virtual string DisplayName { get; } 8: } DisplayAttribute和DisplayNameAttribute特性對Model元數據的定制涉及到如下五個屬性。DisplayAttribute的GetName方法的返回值和DisplayNameAttribute的屬性DisplayName對應於ModelMetadata的DisplayName屬性。DisplayAttribute的GetShortName方法對應着ModelMetadata的ShortDisplayName屬性,而GetDescription和GetOrder對應着ModelMetadata的Description和Order屬性。ModelMetadata的Watermark屬性通過DisplayAttribute的GetPromp方法的返回值初始化。 1: public class ModelMetadata 2: { 3: //其他成員 4: public virtual string DisplayName { get; set; } 5: public virtual string ShortDisplayName { get; set; } 6: public virtual string Description { get; set; } 7: public virtual int Order { get; set; } 8: public virtual string Watermark { get; set; } 9: } 由於DisplayAttribute的GetName方法的返回值和DisplayNameAttribute的DisplayName屬性最終都用於設置ModelMetadata的DisplayName屬性,如果這兩個屬性同時應用到相同的目標元素上並且對顯示名稱作出了不同的設置,那么DisplayAttribute特性具有更高的優先級。 如下面的代碼片斷所示,我們將DisplayAttribute和DisplayNameAttribute特性應用到了數據類型Model的相應的屬性上。其中屬性Bar上應用了DisplayNameAttribute並將顯示名稱設置為“Bar”,而屬性Baz上同時應用了DisplayAttribute和DisplayNameAttribute特性並分別講顯示名稱設置為“BAZ”和“Baz”。 1: public class Model 2: { 3: public string Foo { get; set; } 4: 5: [DisplayName("Bar")] 6: public string Bar { get; set; } 7: 8: [Display(Name = "BAZ")] 9: [DisplayName("baz")] 10: public string Baz { get; set; } 11: } 現在我們通過如下代碼通過調用輔助方法GetModelMetadata<TModel>或者定義在Model類上的三個屬性對應的ModelMetadata對象,並將其DisplayName屬性值打印出來。 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.DisplayName ?? "N/A"); 6: Console.WriteLine("Bar: {0}", bar.DisplayName ?? "N/A"); 7: Console.WriteLine("Baz: {0}", baz.DisplayName ?? "N/A"); 上面的測試程序執行之后會在控制台上產生如下的輸出結果,從這我們可以看到對於同時應用了DisplayAttribute和DisplayNameAttribute特性的Baz屬性,對應ModelMetadata的DisplayName屬性與DisplayAttribute是一致的。 1: Foo: N/A 2: Bar: Bar 3: Baz: BAZ 六、RequiredAttribute 我們來介紹最終一個標注特性RequiredAttribute。顧名思義,RequiredAttribute特性將目標元素設置為必需的數據成員。如下面的代碼片斷所示,和DataTypeAttribute特性一樣,RequiredAttribute也是一個驗證特性。其AllowEmptyStrings屬性表示作為必需數據成員的目標元素是否接受一個空字符串,默認情況下是不允許的。 1: [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple=false)] 2: public class RequiredAttribute : ValidationAttribute 3: { 4: public RequiredAttribute(); 5: public bool AllowEmptyStrings { get; set; } 6: } 對於應用了RequiredAttribute特性的數據成員,對應ModelMetadata的IsRequired屬性將會被設置為True。如下面的代碼片斷所示,該屬性是一個可讀寫的屬性。 1: public class ModelMetadata 2: { 3: //其他成員 4: public virtual bool IsRequired { get; set; } 5: } ASP.NET MVC Model元數據及其定制: 初識Model元數據 ASP.NET MVC Model元數據及其定制: Model元數據的定制 ASP.NET MVC Model元數據及其定制:一個重要的接口IMetadataAware