ASP.NET MVC Model元數據及其定制: 初識Model元數據


Contronoller激活之后,ASP.NET MVC會根據當前請求上下文得到目標Action的名稱,然后解析出對應的方法並執行之。在整個Action方法的執行過程中,Model元數據的解析是一個非常重要的環節。ASP.NET MVC中的Model實際上View Model,表示最終綁定到View上的數據,而Model元數據描述了Model的數據結構,以及Model的每個數據成員的一些特性。正是有了Model元數據的存在,才使模板化HTML的呈現機制成為可能。此外,Model元數據支撐了ASP.NET MVC的Model驗證體系,因為針對Model的驗證規則正是定義在Model元數據中。ASP.NET MVC的Model元數據通過類型ModelMetadata表示。ModelMetadata通過一系列的屬性描述了Model及其成員相關的元數據信息,在正式介紹這些元數據選項之前,我們很有必要先來了解一下Model元數據層次化結構。[本文已經同步到《How ASP.NET MVC Works?》中]

目錄
一、Model元數據層次化結構
二、基本Model元數據信息
三、Model元數據的定制
    UIHintAttribute
    HiddenInputAttribute與ScaffoldColumnAttribute
    DataTypeAttribute與DisplayFormatAttribute
    EditableAttribute與ReadOnlyAttribute
    DisplayAttribute與DisplayNameAttribute
    RequiredAttribute
四、IMetadataAware接口
    AllowHtmlAttribute
    實例演示:創建實現IMetadataAware接口的特性定制Model元數據

一、Model元數據層次化結構

作為Model的數據類型可以一個和簡單的字符串或者是一個值類型的對象,也可能是一個復雜的數據類型。對於一個復雜的數據類型,基於類型本身和數據成員的元數據都通過一個ModelMetadata來表示,而某個數據成員又可能是一個復雜類型,所以通過ModelMetadata對象表示的Model元數據實際上具有一個樹形層次化結構。

舉個例子,我們具有一個具有如下定義的表示聯系人的數據類型Contact。屬性Name、PhoneNo、EmailAddress和Address分別代表姓名、電話號碼、郵箱地址和聯系地址。聯系地址通過另一個數據類型Address表示,屬性Province、City、District和Street分別表示所在省份、城市、城區和街道。

   1: public class Contact
   2: {
   3:     public string Name { get; set; }
   4:     public string PhoneNo { get; set; }
   5:     public string EmailAddress { get; set; }
   6:     public Address Address { get; set; }
   7: }
   8: public class Address
   9: {
  10:     public string Province { get; set; }
  11:     public string City { get; set; }
  12:     public string District { get; set; }
  13:     public string Street { get; set; }
  14: }

如果將Contact類型作為Model,作為其元數據的ModelMetadata不僅僅具有Contact類型本身和其屬性成員的描述,由於其Address屬性是一個復雜類型,元數據還需要描述定義在該類型中的4個屬性成員。下圖反映基於Contact類型的Model元數據的層次化結構。

image

表示Model元數據的ModelMetadata類型不僅用於描述某個作為Model的數據類型,還用於遞歸地描述其所有屬性成員(不包含字段成員),所以ModelMetadata具有一個樹型層次化結構,這也可以從ModelMetadata的定義可以看出來。

   1: public class ModelMetadata
   2: {
   3:     //其他成員
   4:     public virtual IEnumerable<ModelMetadata> Properties { get; }
   5: }

如上面的代碼片斷所示,ModelMetadata具有一個類型為IEnumerable<ModelMetadata>的只讀屬性Properties,表示用於描述屬性/字段成員的ModelMetadata集合。ModelMetadata的層次化結構可以通過如下圖所示的UML來體現。由於基於類型的ModelMetadata和基於數據成員的ModelMetadata是一種包含關系,我們可以將前者稱為后者的容器(Container)。

image

二、基本Model元數據信息

基於作為Model類型創建的元數據主要是為View實現模板化HTML呈現和數據驗證服務的,我們可以通過在類型和數據成員上應用相應的特性控制Model在View中的呈現方式或者定義相應的驗證規則。在介紹聲明式Model元數據編程方式之前,我們先來介紹表示Model元數據的ModelMetadata類型中與UI呈現和數據驗證無關的基本屬性。

   1: public class ModelMetadata
   2: {
   3:    //其他成員
   4:     public Type ModelType { get; }
   5:     public virtual bool IsComplexType { get; }
   6:     public bool IsNullableValueType { get; }
   7:     public Type ContainerType { get; }
   8:  
   9:     public object Model { get; set; }
  10:     public string PropertyName { get; }
  11:  
  12:     public virtual Dictionary<string, object> AdditionalValues { get; }
  13:     protected ModelMetadataProvider Provider { get; set; }
  14: }

如上面的代碼片斷所示,ModelMetadata具有四個類型相關的只讀屬性。ModelType表示Model本身的類型,比如說針對上面定義的Contact類型的ModelMetadata對象,其ModelType屬性值就是Contact類型;而針對其屬性的ModelMetadata對象,則具體的屬性類型作為它的ModelType屬性。屬性IsComplexType和IsNullableValueType分別表示以ModelType屬性表示的Model類型是一個復雜類型和可空值類型。

在這里判斷某個類型是否是復雜類型的條件只有一個,即是否允許字符串類型向該類型的轉換。具體來說,將通過ModelType屬性表示的Model類型作為傳輸傳入TypeDescriptor的靜態方法GetConverter得到一個TypeConverter對象,如果TypeConverter不支持從字符串類型的轉換則認為是復雜類型。所以所有的基元類型(Primative Type)均不是復雜類型,所有可空值類型(Nuallable Type)均不是是復雜類型。對於一個默認為復雜類型的自定義的數據類型,我們可以通過TypeConverterAttribute特性標注一個支持從字符串類型轉換的TypeConverter使之轉變成非復雜類型。

如下面的代碼片斷所示,我們定義了一個表示二維坐標的Point類型,由於我們在該類型上應用了一個TypeConverterAttribute特性指定了類型為PointTypeConverter的TypeConverter。由於PointTypeConverter支持從字符串到Point類型之間的轉換,所以Point並不是一個復雜類型。

   1: [TypeConverter(typeof(PointTypeConverter))]
   2: public class Point
   3: {
   4:     public double X { get; set; }
   5:     public double Y { get; set; }
   6:     public Point(double x, double y)
   7:     {
   8:         this.X = x;
   9:         this.Y = y;
  10:     }
  11:     public static Point Parse(string point)
  12:     { 
  13:         string[] split = point.Split(',');
  14:         if(split.Length != 2)
  15:         {
  16:             throw new FormatException("Invalid point expression.");
  17:         }
  18:         double x;
  19:         double y;
  20:         if (!double.TryParse(split[0], out x) || 
  21:             !double.TryParse(split[1], out y))
  22:         { 
  23:             throw new FormatException("Invalid point expression.");
  24:         }
  25:         return new Point(x, y);
  26:     }
  27: }
  28: public class PointTypeConverter : TypeConverter
  29: {
  30: public override bool CanConvertFrom(ITypeDescriptorContext context, 
  31:    Type sourceType)
  32:     {
  33:         return sourceType == typeof(string);
  34:     }
  35:  
  36: public override object ConvertFrom(ITypeDescriptorContext context, 
  37:     CultureInfo culture, object value)
  38:     {
  39:         if (value is string)
  40:         {
  41:             return Point.Parse(value as string);
  42:         }
  43:         return base.ConvertFrom(context, culture, value);
  44:     }
  45: }

通過上面的介紹我們知道表示Model元數據的ModelMetadata具有一個樹形的層次結構,某個節點可以看成了其子節點的容器,而ContainerType這是表述容器的類型。同樣以前面定義的Contact類型為例,基於該類型本身的ModelMetadata是整個層次樹的根節點,所以ContainerType返回Null;基於屬性Address的ModelMetadata的ContainerType屬性返回Contact類型;而基於Address的屬性Province的ModelMetadata的ContainerType屬性值則是Address類型。

ModelMetadata的Model屬性代表的是作為Model的對象。如果將上面定義的Contact對象作為View的Model,那么表示該Model本身元數據的ModelMetadata對象來說,其Model屬性就是該Contact對象;對於基於Contact某個屬性的ModelMetadata對象則將對應的屬性值作為自己的Model。值得一提的是,該屬性是可讀可寫的,意味着我們可以隨時根據需要改變它。另一個屬性PropertyName表示對應的屬性值,對於根節點ModelMetadata來說,該屬性總是返回Null。

ModelMetadata的AdditionalValues屬性返回一個字典對象,用於存儲一些自定義的屬性,字典元素的Key和Value分別代表自定義屬性的名稱和值。對於自定義屬性的添加,我們可以在數據類型或者其數據成員上應用AdditionalMetadataAttribute特性來實現。如下面的代碼片斷所示,AdditionalMetadataAttribute具有Name和Value兩個只讀屬性分別表示自定義屬性的名稱和值,它們直接通過構造函數進行初始化。AdditionalMetadataAttribute實現了IMetadataAware接口,對於Model元數據的定制來說,這是一個非常重要並且實用的接口,我們將在下篇對其進行單獨介紹。

   1: [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Property | 
   2:     AttributeTargets.Class, AllowMultiple=true)]
   3: public sealed class AdditionalMetadataAttribute : Attribute, IMetadataAware
   4: {
   5:     public AdditionalMetadataAttribute(string name, object value);
   6:     public void OnMetadataCreated(ModelMetadata metadata);
   7:     public string Name { get; }
   8:     public object Value { get;}
   9: }

ModelMetadata的屬性Provider是一個ModelMetadataProvider對象,顧名思義,ModelMetadataProvider是ModelProvider的提供者。ModelProvider是ASP.NET MVC整個Model元數據系統的核心,我們將在后續的博文中對其進行單獨講述。

ASP.NET MVC Model元數據及其定制: 初識Model元數據
ASP.NET MVC Model元數據及其定制: Model元數據的定制
ASP.NET MVC Model元數據及其定制:一個重要的接口IMetadataAware


免責聲明!

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



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