阅读目录:程序员
ModelMetadata是ASP.NETMVC中用来表示Model的元数据对象,它包含了一个Model的全部的相关元数据信息,固然这取决Model的使用方向,不一样的使用方向会有不一样类型的元数据,咱们这里的ModelMetadata是针对View显示相关的元数据;ModelMetadata中绝大部分元数据是用来做为最终在View生成环节当中须要使用到的,好比:如何肯定一个领域相关的属性(Address)该如何展示,这里的Address可能不是一个简单的String类型表示,而是由一组复杂的类型表示,这样的状况下咱们就须要经过自定义元数据来控制最终使用的呈现模板(PartialView);数据库
在MVC的定义中,Model准确意思是ViewModel(显示Model,只是用来做为界面呈现使用的数据实体),它是直接提供给View做为呈现使用的数据实体,一般状况下还将做为DTO类型的数据实体,负责数据的往返传输;ASP.NETMVC提供一种自定义Model呈现方式的接口,它容许咱们经过自定义某个ViewModel中的属性显示视图(PartialView部分视图),从而能够对ViewModel进行很是细粒度的呈现控制,可是这一扩展机制的背后正是ModelMetadata的功劳;编程
ModelMetadata起到中间桥梁的做用,在桥梁的一端是ViewModel,另外一端是View,然而咱们能够在ViewModel上经过定义Attribute的方式进行元数据的自定义,能够经过改变某个ViewModel的ModelMetadata来操纵最终的呈现;数据结构
图1:Customer ViewModelapp
图2:Customer ModelMetadata框架
元数据的层次结构与所要表示的ViewModel的结构是一致的,好比上图中的Customer实体中有一个Shopping属性,该属性表示实体中的配送信息,而后Shopping中还包含一个Address属性表示配送地址,对应的ModelMetadata也是这种包含的层次结构,在每一个ModelMetadata内部都有一个类型为IEnumerable<ModelMetadata>的Properties属性来引用它的下级ModelMetadata,这就造成了一个无限嵌套的元数据表示结构,在ModelMetadata经过下面两行代码来保存属性的这种嵌套依赖关系; ide
1 public class ModelMetadata { 2 3 public virtual IEnumerable<ModelMetadata> Properties {} /*类型的子对象元数据*/ 4 5 public string PropertyName {} /*所表示的属性名称*/ 6 7 }
当咱们有了一个ViewModel以后就能够在任何一个View中显示它,View的呈现是强类型的,也就是说必须具备一个实体的类型做为数据呈现容器的基础在View中引入,由于一系列的HtmlHelper扩展方法都是基于这个强类型,咱们经过一个简单的示例,来大概的了解一下ASP.NETMVC使用方式;工具
Customer ViewModel 代码:开发工具
1 namespace MvcApplication4.Models 2 { 3 public class Customer 4 { 5 public string CustomerId { get; set; } 6 public Shopping Shopping { get; set; } 7 } 8 public class Shopping 9 { 10 public string ShoppingId { get; set; } 11 public Address Address { get; set; } 12 } 13 public class Address 14 { 15 public string AddressId { get; set; } 16 public string CountryCode { get; set; } 17 public string City { get; set; } 18 public string Street { get; set; } 19 } 20 }
这是一个简单的以Customer为主的ViewModel,在Customer中定义了一个Shopping类型的属性,而后在Shopping类型中又定义了一个String类型的Address属性,这是一个很经常使用的嵌套对象结构;测试
HomePage Controller 代码:
1 namespace MvcApplication4.Controllers 2 { 3 using Models; 4 5 public class HomePageController : Controller 6 { 7 public ActionResult Index() 8 { 9 Customer customer = new Customer() 10 { 11 CustomerId = "Customer123456", 12 Shopping = new Shopping() 13 { 14 ShoppingId = "Shopping123456", 15 Address = new Address() 16 { 17 AddressId = "Address123456", 18 CountryCode = "CN", 19 City = "Shanghai", 20 Street = "Jiangsu Road" 21 } 22 } 23 }; 24 return View(customer); 25 } 26 27 public ActionResult Edit(Customer customer) 28 { 29 if (customer != null) 30 return new ContentResult() { Content = "Is Ok" }; 31 return new ContentResult() { Content = "Is Error" }; 32 } 33 } 34 }
控制器什么事情也没作,直接实例化了一个嵌套层次结构的Customer对象并初始化了一些测试数据,该Action使用ViewResult类型做为返回结果;
Index View 代码:
1 @model MvcApplication4.Models.Customer 2 3 <table> 4 <tr> 5 <td> 6 <h2>Model Details Display.</h2> 7 @Html.DisplayForModel() 8 @Html.DisplayFor(model => model.Shopping) 9 @Html.DisplayFor(model => model.Shopping.Address) 10 11 </td> 12 <td></td> 13 <td> 14 <h2>Model details Editor.</h2> 15 @using (Html.BeginForm("Edit", "HomePage", FormMethod.Post)) 16 { 17 @Html.EditorForModel() 18 @Html.EditorFor(model => model.Shopping) 19 @Html.EditorFor(model => model.Shopping.Address) 20 <input type="submit" value="Submit" /> 21 }</td> 22 </tr> 23 </table>
视图分别对Customer类型的嵌套属性进行了编辑、显示定义,这里须要说明的是EditorForModel()、DisplayForModel()不会作到对嵌套类型的编辑、显示,由于这不符合平常使用,咱们须要明确的编码须要编辑、显示的属性,经过EditorFor()、DisplayFor()方法进行选择;
这是一个最基本的MVC使用方式,Customer是须要View进行显示的ViewModel,在View中经过HtmlHelper扩展方法对Customer实体生成编辑、显示时的全部HTML,这确实方便了不少,咱们不须要去管到底如何生成这些HTML了;
图3:
背后为咱们自动生成了编辑、显示所须要的HTML;
图4(如下两幅):
自动化生成是好事,可是有些时候咱们并不但愿它帮咱们生成一些不须要的HTML或者说咱们但愿能对生成的过程进行一些控制,好比:这里的Customer对象,在对象内部的一些属性(如:CustomerId)咱们根本不但愿暴露出来被编辑或被显示,咱们但愿能经过简单的方式控制这种现实方式;固然MVC为咱们提供了一整套自动化机制,一样也为咱们提供了控制这些自动化机制的接口;
ViewModel在界面上呈现的方式只有两种,要么显示(Display)要么编辑(Editor),上图中已经给出MVC默认生成的HTML格式;这是做为默认的方式输出,咱们并无参与到输出过程的任何环节中,要想控制ViewModel的某个属性的展示方式咱们必须对ModelMetadata进行控制,由于最终生成的这些HTML是根据Model元数据来定的,准确点讲HtmlHelper对象和一系列围绕HtmlHelper的扩展方法都是基于某个ViewModel的ModelMetadata进行最终的生成,全部跟生成相关的选项都是在ModelMetadata中设定的,若是咱们没有对ViewModel的ModelMetadata进行设置那么它将有一些默认的数据选项做为最终生成的基础;
ASP.NETMVC提供一个叫作 “数据注释 DataAnnotations” 的方式对某个ViewModel的Model的元数据进行设置,经过在ViewModel中运用一些预约义好的特性来设置本属性所要展示的方式;好比:上面的Customer实体咱们想控制他的CustomerId只能显示在界面上,不能对其进行编辑,也就是说咱们只能看不能改;
Customer 代码:
1 namespace MvcApplication4.Models 2 { 3 public class Customer 4 { 5 [HiddenInput] /*设置CustomerId不出现Input输入框*/ 6 public string CustomerId { get; set; } 7 public Shopping Shopping { get; set; } 8 } 9 public class Shopping 10 { 11 public string ShoppingId { get; set; } 12 public Address Address { get; set; } 13 } 14 public class Address 15 { 16 public string AddressId { get; set; } 17 public string CountryCode { get; set; } 18 public string City { get; set; } 19 public string Street { get; set; } 20 } 21 }
图5:
咱们经过使用 HiddenInput特性把CustomerId的输入框Input隐藏起来了,经过上图中的CustomerId部分的HTML代码,咱们能清晰的看见CustomerId的Input的Type被设置成了Hidden,也符合HiddenInput的定义,只将其隐藏起来而不是不输出HTMLDom;HiddenInput特性中有一个惟一的属性参数DisplayValue,该属性参数意思是说隐藏Input元素可是是否要显示该属性的值,它是一个Bool类型参数(true:显示该属性值,false:不显示,而且在Display模式下也不显示);
这里我就有一个疑问了,在 Display模式下也不显示,可是通常不少场景下都是须要显示的,并且这样的一个特性会致使两种模式下的显示冲突;这里的CustomerId假设我须要在Display下显示出来,可是在编辑模式下我就是要不显示出CustomerId属性值;其实这个时候就须要咱们本身扩展这些设置显示方式的特性了,前提是咱们得很清楚它是如何控制HTMLDOM输出的,究竟是如何与HtmlHelper对象协调的,又如何参与到元数据设置当中的;
在ASP.NETMVC中有一组预先定义好的Attribute,这些Attribute是专门用来控制某个ViewModel中的属性元数据选项;在大多数状况下,咱们可使用这些预先定义好的Attribute来解决通常的业务场景,可是实践经验告诉咱们通常的业务场景很少见,一般都是须要咱们对元数据进行自定义控制,这样咱们才能作到对当前业务逻辑最大粒度的抽象,从而达到在某个层面上能作到面向特定领域的范围;
Customer 代码:
1 namespace MvcApplication4.Models 2 { 3 public class Customer 4 { 5 [Display(Name = "客户ID")] 6 public string CustomerId { get; set; } 7 public Shopping Shopping { get; set; } 8 } 9 public class Shopping 10 { 11 [Display(Name = "配送ID")] 12 public string ShoppingId { get; set; } 13 public Address Address { get; set; } 14 } 15 public class Address 16 { 17 [Display(Name = "地址")] 18 public string AddressId { get; set; } 19 [Display(Name = "国家编码")] 20 public string CountryCode { get; set; } 21 [Display(Name = "城市编码")] 22 public string City { get; set; } 23 [Display(Name = "街道")] 24 public string Street { get; set; } 25 } 26 }
这里经过Diaplay预约义特性来控制元数据显示选项,在Display特性中有不少可选属性用来进一步设置显示选项,这里咱们只使用了Name属性来设置该属性在界面上显示的文本信息,用来替换本来显示代码属性名称的默认选项;
图6:
能够作到将界面上本来显示字段名称的地方换成使用领域语言显示,也就是咱们经过Diaplay特性设置的显示文本;
ViewModel中的属性有两种类型的含义,好比:在Address数据实体中CountryCode默认是字符串类型,可是它的领域类型是一个表示国家代码的编号;虽然不少时候咱们可使用字符串、数字等这些CLR类型来表达任何一种领域概念,这仅仅是代码层面的表示而已,而一旦咱们将该实体做为领域对象在界面呈现时就须要还原出领域相关的特性;很常见的状况就是咱们常常将字符串类型的Email用特定的格式在界面上表示,这就是说明该字段是一个领域相关的特性;代码是给咱们程序员看的,而领域语言是给相关的领域参与者看的,因此在ViewModel中设置的这些预约义元数据控制特性大致能够归来为这两类;
在ASP.NETMVC中大部分使用的预约义特性都是位于System.ComponentModel.DataAnnotations命名空间中,惟独HiddenInput特性是孤身一人在System.Web.Mvc命名空间中,这可能对你形成了一些理解上的困扰;明明是ASP.NETMVC框架使用的对象为何会跑到System.ComponentModel.DataAnnotations命名空间中去,又为何恰恰HiddenInput就在System.Web.Mvc命名空间中,按道理说也应该是在System.Web.Mvc开头的命名空间中才对;其实这要想说清楚就牵扯到一些.NET组件程序设计相关的理论知识,因此会在下一个章节详细的分析它为何会在System.ComponentModel.DataAnnotations命名空间中,这些设计究竟是为了什么;
在ASP.NETMVC中大部分预先定义好的元数据控制特性都是密封类型的,只有不多一部分是公开类型的,因此若是咱们须要扩展的对象能从这部分对象上继承那将会很方便,能够省掉不少工做;有些特性不是一个简单的数据声明标识,其中会有一些预约义行为会被走到,因此若是咱们重写这部分的行为就能够作到简单的扩展这部分对象来轻松的达到扩展目的;
可是很大程度上咱们须要本身能从根本上定制一个元数据控制特性对象,咱们不但愿经过继承原有的预约义的元数据控制特性对象来进行简单的扩展,咱们须要最大粒度的设计,我想这个要求一点都不过度,谁愿意在碍手碍脚的地方Happy呢;
ASP.NETMVC提供IMetadataAware接口让咱们能够随心所欲的控制元数据,控制元数据就能够控制最终根据元数据生成的逻辑;
CustomDisplayName 代码:
1 [AttributeUsage(AttributeTargets.Property)] 2 public class CustomDisplayName : Attribute, IMetadataAware 3 { 4 public string Name { get; set; } //默认显示名称 5 public void OnMetadataCreated(ModelMetadata metadata) 6 { 7 metadata.DisplayName = string.Format("{0}/{1}", 8 string.IsNullOrEmpty(this.Name) ? metadata.DisplayName : this.Name, metadata.PropertyName); 9 } 10 }
这是一个很简单的自定义元数据对象,当咱们将CustomDisplayName 特性对象设置在指定的ViewModel中的任何一个属性上时,将能够在运行时获取到系统自动生成的元数据对象模型ModelMetadata,这个时候咱们就能够对当前的元数据进行随意的控制,甚至能够一直追述元数据的全部关联元数据;
上面的示例代码将复写经过预约义特性Display特性设置的元数据信息DisplayName:
1 public class Customer 2 { 3 [CustomDisplayName(Name = "自定义")] 4 [Display(Name = "客户ID")] 5 public string CustomerId { get; set; } 6 public Shopping Shopping { get; set; } 7 }
在CustomerId属性上咱们设置了两个特性,一个是系统预约义的Display特性,该特性将会对元数据对象ModelMetadata的DisplayName属性进行设置,还有一个正是咱们自定义的CustomDisplayName特性,在咱们自定义特性的内部逻辑中,若是咱们设置了CustomDisplayName对象的Name属性,那么咱们将使用该值复写经过预约义特性Display特性所设置的默认元数据信息,从而达到控制最终元数据的目的;
图7:
当前这个值是咱们经过Display预约义特性设置的;
图8:
在CustomDisplayName中的Name属性是咱们设置的默认要显示的文本,若是咱们设置了默认值将使用该值复写预约义特性Display设置的值;
图9:
使用IMetadataAware接口咱们能够设计自定义的元数据设置对象,这也是ASP.NETMVC目前公开的惟一一个元数据定义接口;固然若是碰见很是复杂的业务场景时就须要咱们对元数据提供程序进行控制,能够将元数据的定义方式从声明式迁移到配置文件中,固然这须要有业务须要才行,纯粹的技术实现没有太多的意义;
在ASP.NETMVC中,大部分的元数据控制特性都是定义在System.ComponentModel.DataAnnotations命名空间中,固然也有一小部分是ASP.NETMVC直接固定的,这些都是跟ASP.NETMVCWEB编程直接相关的(如:HiddenInput元数据库控制特性,用来隐藏HTML中的Input Dom元素),可是大部分都是位于组件对象模型命名空间中;这就会给咱们带来一些疑问,为何跟ASP.NETMVC框架相关的对象模型会被定义在System.ComponentModel.DataAnnotations命名空间中,而该命名空间中的对象模型倒是跟系统组件设计相关的领域,若是你没有系统组件开发经验或者没有Winform程序开发经验的对你来讲可能真的很困惑,由于System.ComponentModel.DataAnnotations命名空间基本上是用来支撑全部.NET平台上的基础框架,若是你想扩展VS插件、编写设计时组件,这些跟.NET平台相关的领域都会须要该命名空间的支持;
能够简单定义System.ComponentModel.DataAnnotations命名空间的做用,该命名空间主要是用来支撑跟.NET平台组件开发相关的领域,在该命名空间中的对象模型都是用来支持VisualStudio设计时及基础框架的通用组成部分;
组件模型一般具备三个基本的生命周期,设计时、编译时、运行时,这里的组件与咱们一般理解的运行时组件不是一个概念,这里的组件的参照物是.NET基础框架,做为以VS为开发工具的.NET程序,在设计时咱们都须要可视化编程,将一个简单的对象以图形界面的方式呈现出来而且提供设计时支持,这些才这是咱们这里所说的组件,若是你的组件并无提供设计时、编译时、运行时这三个基本的生命周期事件,那么只能说你的组件是不完整的;
设计时:当咱们在使用传统ASP.NET开发程序的时候最经常使用的就是拖拽一个控件放入界面上,此时会出现一个GUI的设计界面,让咱们点击相应的位置设置一些选项,这就是设计时支持,被拖拽的能够视为一个能够重用的组件,这是它在设计时的一个生命周期;
编译时:当咱们启动VS进行编译时,组件有一个自我属性检查的过程,一般是用来检查咱们的预设置项是否正确,好比一些WindowsService,是否填写了正确的启动项属性,这就是组件的编译时支持;
运行时:这个比较好理解,运行时就是在程序运行过程当中提供的功能,固然你的组件能够不提供运行时支持,而仅仅提供设计时、编译时的支持;
组件设计时元数据和ASP.NETMVC Model元数据很类似,为何说类似,是由于都须要通过一个对元数据获取的过程;在ASP.NETMVC中Model元数据的设置过程须要经过提取做用于Model上的元数据控制特性而且逐一顺序执行后才能完成,而这里的组件设计时元数据提取过程能够当作是和ASP.NETMVC Model元数据设置过程当中的提取元数据控制特性过程彻底一致的复用功能;
图10:
上图中被圈出的部分是对设计时元数据的控制特性,经过对须要绑定到VS属性窗口中的模型运用相似ASP.NETMVC中定义Model控制元数据特性的同样的方式来达到控制被使用的模型,惟一不一样的是背后的元数据处理程序不一样而已,可是能够进行相似的理解;
通过上面两个小结的讲解,咱们知道什么是系统组件及组件的一个基本的特征,如:生命周期,更为重要的是咱们知道了一些跟ASP.NETMVC元数据类似的功能出如今系统组件开发的功能集中,这为咱们理解为何ASP.NETMVC元数据注解特性对象会定义在系统组件命名空间中作了不少充足的准备;
System.ComponentModel.DataAnnotationns命名空间是位于System.ComponentModel命名空间下,表示它是一个系统组件开发相关的数据注解组件;帮助咱们在开发系统组件时进行很好的数据注解声明,最有意义的是能够很轻松的实现元数据驱动设计、契约式设计等相似须要借助数据注解功能的设计方法;
既然定义在System.ComponentModel下也就意味着能够供.NET平台上的全部跟组件设计相关的框架使用,在.NET平台中有不少须要借助数据注解特性功能的场景(好比:在WPF中须要借助数据注解功能来达到MVVM模式的使用);
图11:
System.ComponentModel.DataAnnotations中的数据注解特性是提供给全部.NET平台上应用框架使用的,这些框架都或多或少在一些设计上须要数据注解功能,这样就不须要重复定义这些相似功能了;在ASP.NETMVC中,咱们使用这些数据注解特性来声明元数据控制选项,在其余的应用框架中如:WPF中,可能须要用来指定UI上的双向绑定事件,这些都是须要创建在这些数据注解特性上的;
在System.ComponentModel.DataAnnotations中有一个扩展自System.ComponentModel.TypeDescriptionProvider的类型:
// 摘要: // 经过添加在关联类中定义的特性和属性信息,从而扩展某个类的元数据信息。 public class AssociatedMetadataTypeTypeDescriptionProvider : TypeDescriptionProvider { }
该类型扩展了本来很单纯的组件类型描述提供程序,添加了关联类的数据描述获取功能;意思是说咱们可使用该类来获取全部预约义的关联元数据控制特性;
1 [AttributeUsage(AttributeTargets.Property)] 2 public class ValidatorAttribute : Attribute /*自定义的关联类特性*/ 3 { 4 public string ValidatorFormatString { get; set; } 5 } 6 public class Customer 7 { 8 9 [Validator(ValidatorFormatString = "XXX")]/*设置关联特性*/ 10 [CustomDisplayName(Name = "自定义")] 11 [Display(Name = "客户ID")] 12 public string CustomerId { get; set; } 13 public Shopping Shopping { get; set; } 14 }
AssociatedMetadataTypeTypeDescriptionProvider provider = new AssociatedMetadataTypeTypeDescriptionProvider(typeof(ValidatorAttribute)); var result = provider.GetTypeDescriptor(customer).GetProperties()[0].Attributes;
经过使用AssociatedMetadataTypeTypeDescriptionProvider 公共关联类类型描述提供程序获取全部关联类的元数据控制声明;
图12:
咱们可使用System.ComponentModel.DataAnnotations命名空间提供的公共组件设计框架中提供的关于数据注解方面的功能来方便的开发有关元数据注解方面的程序特性;