ASP.NET MVC的Model元数据与Model模板:模板的获取与执行策略2012-11-24 cnblogs 蒋金楠当我们调用HtmlHelper或者HtmlHelper<TModel>的模板方法对整个Model或者Model的某个数据成员以某种模式(显示模式或者编辑模式)进行呈现的时候,通过预先创建的代表Model元数据的ModelMetadata对象都可以找到相应的模板。如果模板对应着某个自定义的分部View,那么只需要执行该View即可;对于默认模板,则直接可以得到相应的HTML。本篇文章着重讨论模板的获取和执行机制,不过在这之前,顺便来讨论一下DataTypeAttribute和模板的关系。一、 DataTypeAttribute和模板有何关系?通过《初识Model元数据》针对Model元数据定义的介绍,我们知道通过DataTypeAttribute特性对目标元素设置的数据类型最终会反映在表示Model元数据的ModelMetadata对象的DataTypeName属性上。此外,对于某些设置的数据类型,比如Date、Time、Duration和Currency等,还会随之创建一个DisplayFormatAttribute应用到ModelMetadata上。那么ModelMetadata的DataTypeName属性对目标元素的最终呈现具有怎样的影响呢?实际上在模板匹配的过程中会将ModelMetadata的DataTypeName属性当作模板名称来看待,所以下面两种形式的Model类型定义可以看成是等效的。通过UIHintAttribute特性设置的模板名称和通过DataTypeAttribute特性设置的数据类型的唯一不同之处在于前者具有更高的优先级。换句话说,如果将UIHintAttribute和DataTypeAttribute同时应用到同一个数据成员分别将模板名称和数据类型设置为ABC和123,自定义模板123只有在模板ABC不存在的情况下才会被使用。
 1: public class Model
 2: {
 3: [DataType(DataType.Html)]
 4: public string Foo { get; set; }
 5:
 6: [DataType(DataType.MultilineText)]
 7: public string Bar { get; set; }
 8:
 9: [DataType(DataType.Url)]
10: public string Baz { get; set; }
11: }
12:
13: public class Model
14: {
15: [UIHint("Html")]
16: public string Foo { get; set; }
17:
18: [UIHint("MultilineText")]
19: public string Bar { get; set; }
20:
21: [UIHint("Url")]
22: public string Baz { get; set; }
23: }
实例演示:证明DataTypeName与模板名称的等效性为了证明通过DataTypeAttribute特性设置数据类型在针对目标元素进行可视化呈现过程中被视为模板名称,我们来做一个简单的实例演示。在这个实例中我们定义了如下一个表示三角形的数据类型Triangle,其属性A、B和C是一个Point对象,表示三个角所在的坐标。
 1: public class Triangle
 2: {
 3: [DataType("PointInfo")]
 4:public Point A { get; set; }
 5:
 6: [DataType("PointInfo")]
 7:public Point B { get; set; }
 8:
 9: [DataType("PointInfo")]
10: public Point C { get; set; }
11: }
12:
13: [TypeConverter(typeof(PointTypeConverter))]
14: public class Point
15: {
16: public double X { get; set; }
17: public double Y { get; set; }
18: public Point(double x, double y)
19: {
20: this.X = x;
21: this.Y = y;
22: }
23:
24: public static Point Parse(string point)
25: {
26: string[] split = point.Split(",");
27: if (split.Length != 2)
28: {
29: throw new FormatException("Invalid point expression.");
30: }
31: double x;
32: double y;
33: if (!double.TryParse(split[0], out x) ||!double.TryParse(split[1], out y))
34: {
35: throw new FormatException("Invalid point expression.");
36: }
37: return new Point(x, y);
38: }
39: }
40:
41: public class PointTypeConverter : TypeConverter
42: {
43:public override bool CanConvertFrom(ITypeDescriptorContext context,Type sourceType)
44: {
45: return sourceType == typeof(string);
46: }
47:
48:public override object ConvertFrom(ITypeDescriptorContext context,CultureInfo culture, object value)
49: {
50: if (value is string)
51: {
52: return Point.Parse(value as string);
53: }
54: return base.ConvertFrom(context, culture, value);
55: }
56: }
对于类型Triangle和Point的定义,有两点值得注意:其一,Triangle的三个A、B和C属性上应用了DataTypeAttribute特性并将自定义数据类型设置为PointInfo(不是Point);其二,Point类型上应用了TypeConverterAttribute特性并将TypeConverter类型设置为PointTypeConverter,后者支持源自字符串的类型转换。通过前面对复杂类型(Complex Type)的介绍,这样会将Triangle的三个属性从复杂类型成员转换成简单类型成员。根据前提介绍的关于Object模板对数据成员的便利规则,Triangle的这三个属性才能被最终呈现出来。现在我们创建一个Model类型为Point的强类型分部View作为模板,并将其命名为PointInfo(和前面通过DataTypeAttribute特性指定的自定义数据类型一致)。我们只为Point定义关于显示模式的模板,所以我们将该分部View文件放在ViewsSharedDisplayTemplates中。如下面的代码片断所示,我们将一个Point对象显示为(X,Y)的形式。
 1: @model MvcApp.Models.Point
 2: (@Model.X, @Model.Y)
现在我们创建一个默认的HomeCtroller。如下面的代码片断所示,在默认的Index操作方法中我们创建了一个Triangle对象将其呈现在默认的View中。
 1: public class HomeController : Controller
 2: {
 3: public ActionResult Index()
 4: {
 5: Triangle triangle = new Triangle
 6: {
 7: A = new Point(1,2),
 8: B = new Point(2,3),
 9: C = new Point(3,4)
10: };
11: return View(triangle);
12: }
13: }
下面是对应的View的定义,这是一个Model类型为Triangle的强类型View,我们仅仅调用了HtmlHelper<TModel>的DisplayModel方法将作为Model的Triangle对象以显示模式呈现出来。
 1: @model MvcApp.Models.Triangle
 2: @Html.DisplayForModel()
运行该Web应用会在浏览器中得到如下图所示的呈现效果,我们可以看到作为我们创建的Triangle对象的A、B和C属性表示的三个角的坐标是完全按照我们定义的PointInfo模板的方式进行呈现的。