ASP.NET MVC Model元数据及其定制: Model元数据的定制2012-11-18 博客园 Artech在《上篇》我们已经提到过了,Model元数据的定制是通过在作为Model的数据类型极其属性成员上应用相应的特性来实现,这些用于声明式元数据定义的特性大都定义在System.ComponentModel.DataAnnotations.dll程序集中,程序集的名称同时也是对应的命名空间名称,所以我们可以它们为数据注解特性(Data Annotation Attribute),接下来我们来介绍一些常用的数据注解特性,以及它们对于元数据具有怎样的影响。一、UIHintAttributeHtmlHelper和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