ASP.NET MVC Model元数据及其定制

作者:互联网   出处:控件中国网   2014-11-05 19:05:56   阅读:2

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元数据层次化结构。
    目录
    一、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元数据系统的核心,我们将在后续的博文中对其进行单独讲述。

Copyright© 2006-2015 ComponentCN.com all rights reserved.重庆磐岩科技有限公司(控件中国网) 版权所有 渝ICP备12000264号 法律顾问:元炳律师事务所
客服软件
live chat