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元數據的層次化結構。
表示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)。
二、基本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