當我們調用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文件放在Views\Shared\DisplayTemplates中。如下面的代碼片斷所示,我們將一個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模板的方式進行呈現的。