ASP.NET MVC Model元数据及其定制: Model元数据的定制
在《上篇》我们已经提到过了,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