ASP.NET MVC的Model元數據與Model模板:模板的獲取與執行策略


當我們調用HtmlHelper或者HtmlHelper<TModel>的模板方法對整個Model或者Model的某個數據成員以某種模式(顯示模式或者編輯模式)進行呈現的時候,通過預先創建的代表Model元數據的ModelMetadata對象都可以找到相應的模板。如果模板對應着某個自定義的分部View,那么只需要執行該View即可;對於默認模板,則直接可以得到相應的HTML。本篇文章着重討論模板的獲取和執行機制,不過在這之前,順便來討論一下DataTypeAttribute和模板的關系。[本文已經同步到《How ASP.NET MVC Works?》中]

一、 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模板的方式進行呈現的。

image

二、模板的獲取與執行

當我們調用HtmlHelper或者HtmlHelper<TModel>的模板方法對整個Model或者Model的某個數據成員以某種模式(顯示模式或者編輯模式)進行呈現的時候,通過預先創建的代表Model元數據的ModelMetadata對象都可以找到相應的模板。如果模板對應着某個自定義的分部View,那么只需要執行該View即可;對於默認模板,則直接可以得到相應的HTML。

根據Model元數據對目標模板的解析是整個模板方法執行流程中最核心的部分,也是本篇討論的重點。我們以針對HtmlHelper<TModel>的擴展方法DisplayFor為例,看看針對通過表達式expression獲取的Model對象是如何以顯示模式呈現出來的。

   1: public static class DisplayExtensions
   2: {
   3:     public static MvcHtmlString DisplayFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string templateName);
   4: }

在DisplayFor被調用的時候,如果通過參數expression表示的Model獲取表達式是針對某個屬性的,那么屬性名會被獲取出來。然后執行表達式得到一個作為Model的對象,該對象連同屬性名(如果有)一起被用於表示Model元數據的Metadatadata對象。接下來會根據該Metadatadata對象得到一系列表示分部模板View名稱的列表,這些View名稱按照優先級排列如下:

  • 作為參數templateName傳入的模板名稱(如果不為空)。
  • Metadatadata的TemplateHint屬性值(如果不為空)。
  • Metadatadata的DataTypeName屬性值(如果不為空)。
  • 如果Model對象的真實類型為非空值類型,該類型名作為模板View名;否則底層(Underlying)類型名作為模板View名(比如說,對於int?類型則將Int32作為模板View名)。
  • 如果Model對象的真實類型為非復雜類型,則使用String模板(由於非復雜類型能夠實現與String類型之間的轉換,所以可以轉換成String進行呈現)。
  • 在Model的聲明類型為接口情況下,如果該接口繼承自IEnuerable則采用Collection模板。
  • 在Model的聲明類型為接口情況下,使用Object模板。
  • 如果Model聲明類型不是接口類型,按照其類型繼承關系向上追溯知道Object類型,逐個將類型名稱作為模板View名稱。如果聲明類型實現了IEnuerable接口,則將最后的Object替換成Collection。

對於得到的這個表示列表,會按照先后順序便利所有的元素。針對具體的表示模板View名稱的某個字符串,會根據呈現的模式在指定的路徑(顯示模式和編輯模式分別為“/DisplayTemplates/{TemplateName}”和“/EditorTemplates/{TemplateName}”)去尋找定義模板的View。如果這樣的View存在,則直接執行該View並返回。如果不能找到自定義模板分部View,則根據該模板名稱在默認的模板列表中查找,如果存在名稱匹配的默認模板,則直接返回默認模板對應的HTML。如果默認的模板列表中的名稱均與指定的名稱不匹配,在進入下一次迭代。

ASP.NET MVC的Model元數據與Model模板:預定義模板
ASP.NET MVC的Model元數據與Model模板:模板的獲取與執行策略
ASP.NET MVC的Model元數據與Model模板:將ListControl引入ASP.NET MVC


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM