本文目的

我们来看一个小例子,在一个ASP.NET MVC项目中创建一个控制器Home,只有一个Index:

  1. public class HomeController : Controller
  2. {
  3. public ActionResult Index()
  4. {
  5. var model = new DemoModel {Email = "test@test.com"};
  6. return View(model);
  7. }
  8. }
  9.  
  10. public class DemoModel
  11. {
  12. [DataType(DataType.EmailAddress)]
  13. public string Email { get; set; }
  14. }

创建对应的强类型视图

  1. @model TestMvc.Controllers.DemoModel
  2. <fieldset>
  3. <legend>DemoModel</legend>
  4.  
  5. <div >
  6. @Html.DisplayFor(model => model.Email)
  7. </div>
  8. </fieldset>

运行一下,如果你的RP不是非常不好的情况下,会出现下面的结果:

生成的是一个Email的链接。看一下email部分对应的html源文件:

  1. <div >
  2. <a href="mailto:test@test.com">test@test.com</a>
  3. </div>

对于不求甚解的人来说,这很正常啊,正常的就像1+1=2一样。但对于勤学好问的同学来说,问题来了。

为什么生成的是一个email链接,不是一个纯文本?

因为我用DataType指明了它是email!

那DataType是如何指导Model生成html的?

如果你对这些问题感兴趣,请你看下去。

Model的兄弟--ModelMetadata

在介绍Model如何能在View中正常显示之前,不得不提一下他的兄弟ModelMetadata这个类。ModelMetadata实现对Model的一些特征进行描述(如Model的类型、Model的值,Model要显示的模板 ),并且实现了对Model的所有属性值进行描述(递归结构)。有了这个兄弟,Model才能知道如何在View中正常显示【注意:这里特指的是用HtmlHelper的系列扩展方法(如Display/DiaplayFor/EditorFor等)方法在View中显示。如果你是在View中直接取Model的值用html显示,你可以暂不用关心ModelMetadata。但!!不要觉得这样你就不用了解ModelMetadata了,因为MVC中还有一个核心与它息息相关---Model绑定与验证,所以我建议你还是先认识一下这位好兄弟】,让我们来看看这个兄弟长什么样:

  1. public class ModelMetadata
  2. {
  3. private readonly Type _modelType; //Model的类型
  4. private readonly string _propertyName; //属性名称
  5. private object _model; //Model的值
  6. private Func<object> _modelAccessor; //为了用Lambd给Model传值(这是潮流)
  7. private IEnumerable<ModelMetadata> _properties; //Model属性值的ModelMetadata集合
  8.  
  9. //通过子类的ComputeXX系列函数将Model的注解特性赋值到下面几个属性
  10. public virtual string DataTypeName { get; set; }
  11. public virtual string Description { get; set; }
  12. public virtual string DisplayFormatString { get; set; }
  13. public virtual string DisplayName { get; set; }
  14. (其它略,属性太多了,影响阅读...)
  15.  
  16. public object Model
  17. {
  18. get
  19. {
  20. if (_modelAccessor != null)
  21. {
  22. _model = _modelAccessor();
  23. _modelAccessor = null;
  24. }
  25. return _model;
  26. }
  27. }
  28.  
  29. public virtual IEnumerable<ModelMetadata> Properties
  30. {
  31. get
  32. {
  33. if (_properties == null)
  34. {
  35. IEnumerable<ModelMetadata> originalProperties = Provider.GetMetadataForProperties(Model, RealModelType);
  36. _propertiesInternal = SortProperties(originalProperties.AsArray());
  37. _properties = new ReadOnlyCollection<ModelMetadata>(_propertiesInternal);
  38. }
  39. return _properties;
  40. }
  41. }
  42.  
  43. protected ModelMetadataProvider Provider { get; set; } //ModelMetadata的创建者(后面会有介绍)
  44. public virtual string TemplateHint { get; set; } //Model显示用的模板名称
  45.  
  46. }

然而在MVC中默认使用的却不是这个类,而是它的子类CachedDataAnnotationsModelMetadata(其实中间还隔着一个CachedModelMetadata类,因为该类只是做了简单的封装,为了方便读者理解,在此省略)。CachedDataAnnotationsModelMetadata做的最主要的一件事情就是通过一系列的Compute函数得到Model上注解特性的值,并把这些值赋值到对应的属性。如下:

  1. public class CachedDataAnnotationsModelMetadata : CachedModelMetadata<CachedDataAnnotationsMetadataAttributes>
  2. {
  3. public CachedDataAnnotationsModelMetadata(CachedDataAnnotationsModelMetadataProvider provider, Type containerType, Type modelType, string propertyName, IEnumerable<Attribute> attributes)
  4. : base(provider, containerType, modelType, propertyName, new CachedDataAnnotationsMetadataAttributes(attributes.ToArray()))
  5. {
    PrototypeCache = new CachedDataAnnotationMetadataAttributes(attributes.ToArray()); //这个赋值应该在它的父类CachedModelMetadata中执行的,这里简化一下,方便阅读
  6. }
  7.  
  8. protected override string ComputeDataTypeName()
  9. {
  10. if (PrototypeCache.DataType != null)
  11. {
  12. return PrototypeCache.DataType.ToDataTypeName();
  13. }return base.ComputeDataTypeName();
  14. }
  15.  
  16. public override string DataTypeName{ get{ return ComputeDataTypeName();}}
  17. }

注意代码中两个红色的部分,把Model的注解特性存在PrototypeCache里面,这样就可以通过反射得到注解特性的值了。比如文章开始处的DemoModel的属性Email对应的CachedDataAnnotationsModelMetadata对象中 DataTypeName最终被赋值为“EmailAddress”,等介绍完下一位重量级人物后,会详解具体调用过程。

ModelMetadata的创建者ModelMetadataProvider

我们还要来认识一位重量级的人物:ModelMetadata的创建者:

  1. public abstract class ModelMetadataProvider
  2. {
  3. public abstract IEnumerable<ModelMetadata> GetMetadataForProperties(object container, Type containerType);
  4. public abstract ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName);
  5. public abstract ModelMetadata GetMetadataForType(Func<object> modelAccessor, Type modelType);
  6. }

它是一个纯抽象类。该类的第一级子类非常重要,看一下代码:

  1. public abstract class AssociatedMetadataProvider : ModelMetadataProvider
  2. {
  3. private static void ApplyMetadataAwareAttributes(IEnumerable<Attribute> attributes, ModelMetadata result)
  4. {
  5. foreach (IMetadataAware awareAttribute in attributes.OfType<IMetadataAware>())
  6. {
  7. awareAttribute.OnMetadataCreated(result);
  8. }
  9. }
  10.  
  11. protected abstract ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName);
  12.  
  13. public override ModelMetadata GetMetadataForType(Func<object> modelAccessor, Type modelType)
  14. {
    //得到Model上上面的Attribute
  15. AttributeList attributes = new AttributeList(GetTypeDescriptor(modelType).GetAttributes());
    //创建ModelMetadata
  16. ModelMetadata result = CreateMetadata(attributes, null /* containerType */, modelAccessor, modelType, null /* propertyName */);
  17. //创建ModelMetadata后,用所有实现IMetadataAware接口的Attribute对ModelMetadata进行改造
  18. ApplyMetadataAwareAttributes(attributes, result);
  19. return result;
  20. }
  21.  
  22. protected virtual ICustomTypeDescriptor GetTypeDescriptor(Type type)
  23. {
  24. return TypeDescriptorHelper.Get(type);
  25. }
  26. }

注意红色的部分,正是因为有这样一个处理,用户就可以通过自已自定义扩展实现IMetadataAware接口,对ModelMetadata进行再加工处理。

根据上面ModelMetadata讲解,我们知道,MVC中默认使用的是CachedDataAnnotationsModelMetadata这个类,于是对应ModelMetadataProvider的最终子类CachedDataAnnotationsModelMetadataProvider。

当外部想通过GetMetadataForType来得到ModelMetadata时,内部会调用CreateMetadata,再根据原型模式调用CreateMetadataPrototype或CraeteMetadataFromPrototype实现最终创建过程:

  1. public class CachedDataAnnotationsModelMetadataProvider : CachedAssociatedMetadataProvider<CachedDataAnnotationsModelMetadata>
  2. {
  3. protected override CachedDataAnnotationsModelMetadata CreateMetadataPrototype(IEnumerable<Attribute> attributes, Type containerType, Type modelType, string propertyName)
  4. {
  5. return new CachedDataAnnotationsModelMetadata(this, containerType, modelType, propertyName, attributes);
  6. }
  7.  
  8. protected override CachedDataAnnotationsModelMetadata CreateMetadataFromPrototype(CachedDataAnnotationsModelMetadata prototype, Func<object> modelAccessor)
  9. {
  10. return new CachedDataAnnotationsModelMetadata(prototype, modelAccessor);
  11. }
  12. }

同MVC的路由表RouteTable一样,ModelMetadataProvider也有一个静态的ModelMetadataProviders变量Current来提供默认的ModelMetadataProvider。简化代码如下:

  1. public class ModelMetadataProviders
  2. {
  3. private static ModelMetadataProviders _instance = new ModelMetadataProviders();
  4. private ModelMetadataProvider _currentProvider;
  5.  
  6. internal ModelMetadataProviders()
  7. {
  8. _currentProvider=new CachedDataAnnotationsModelMetadataProvider();
  9. }
  10.  
  11. public static ModelMetadataProvider Current
  12. {
  13. get { return _instance._currentProvider; }
  14. set { _instance._currentProvider = value; }
  15. }
  16. }

终极解密

知道了这两位重量级的大员后,现在我们回到文章开始的代码:

  1. Html.DisplayFor(model => model.Email)

当View中执行这句时,执行HtmlHelper的扩展函数,正式进入漫长的生成MvcString旅程:

【注意,这个图中代码是经过N重简化而来,实际调用流程复杂无比(全是在TemplateHelpers中跳转),函数参数多如牛毛,实在不忍让大家看的痛苦】

红色线是程序执行的主流程,后来的执行主场景都是在TemplateHelpers里面完成。

从黄色线可以看出ModelMetadata是通过调用ModelMetadata本身的静态函数FromLambdExpression--->GetMetadataFromProvider,最终由ModelMetadataProviders完成创建。

再来看看桔黄色部分。MVC中有一个DefaultDisplayTemplates的类,存储了诸如Boolean/String/Html/Email等等数据类型的模板,MVC最终根据ViewData中的ModelMetadata.DataType的值找到对应的模板,最终完成HTML字符串的输出,作为WebViewPage.ExecutePageHierarchy的一部分(WebViewPage请参看:仅此一文让你明白ASP.NET MVC 之View的显示(仅此一文系列二))。

结语

本文只是用了ModelMetadata的一个DataType属性做为例子,让你了解在Model中添加注解特性是如何被ModelMetadata使用的。还有其它很多Model注解特性(如ReadOnlyAttribute/RequiredAttribute/UIHintAttribute等等)都和该原理类似。

  其实ModelMetadata在view显示中的作用是有限的,因为很多开发人员都不喜欢用这种前台显示方式,尤其随着json的大量使用,与JQUERY等前台的完美结合。但它有属于它的舞台----Model绑定与验证,敬请期待!

希望新手看的明白,老手多提意见。如果你觉得对你有帮助,请让更多人知道它:)

仅此一文让你明白ASP.NET MVC 之Model的呈现(仅此一文系列三)的更多相关文章

  1. 仅此一文让你明白ASP.NET MVC 之Model的呈现

    本文目的 我们来看一个小例子,在一个ASP.NET MVC项目中创建一个控制器Home,只有一个Index: public class HomeController : Controller { pu ...

  2. 仅此一文让你明白ASP.NET MVC 之View的显示(仅此一文系列二)

    题外话 一周之前写的<仅此一文让你明白ASP.NET MVC原理>受到了广大学习ASP.NET MVC同学的欢迎,于是下定决心准备把它写成一个系列,以满足更多求知若渴的同学们.蒋金楠老师已 ...

  3. ASP.NET MVC 之Model的呈现

    ASP.NET MVC 之Model的呈现(仅此一文系列三) 本文目的 我们来看一个小例子,在一个ASP.NET MVC项目中创建一个控制器Home,只有一个Index: public class H ...

  4. 仅此一文让你明白ASP.NET MVC原理

    ASP.NET MVC由以下两个核心组成部分构成: 一个名为UrlRoutingModule的自定义HttpModule,用来解析Controller与Action名称: 一个名为MvcHandler ...

  5. 【转】仅此一文让你明白ASP.NET MVC原理

    原文地址:http://www.cnblogs.com/DotCpp/p/3269043.html ASP.NET MVC由以下两个核心组成部分构成: 一个名为UrlRoutingModule的自定义 ...

  6. 仅此一文让你明白ASP.NET MVC 之View的显示

    有些人要问题,为什么我要学框架?这里我简单说一下,深入理解一个框架,给你带来最直接的好处: 使用框架时,遇到问题可以快速定位,并知道如何解决: 当框架中有些功能用着不爽时,你可以自由扩展,实现你想要的 ...

  7. Asp.net MVC的Model Binder工作流程以及扩展方法(2) - Binder Attribute

    上篇文章中分析了Custom Binder的弊端: 由于Custom Binder是和具体的类型相关,比如指定类型A由我们的Custom Binder解析,那么导致系统运行中的所有Action的访问参 ...

  8. Asp.net MVC的Model Binder工作流程以及扩展方法(1) - Custom Model Binder

    在Asp.net MVC中, Model Binder是生命周期中的一个非常重要的部分.搞清楚Model Binder的流程,能够帮助理解Model Binder的背后发生了什么.同时该系列文章会列举 ...

  9. [转]ASP.NET MVC 2: Model Validation

    本文转自:http://weblogs.asp.net/scottgu/archive/2010/01/15/asp-net-mvc-2-model-validation.aspx?CommentPo ...

随机推荐

  1. XMLFeedSpider例子

    from scrapy import log from scrapy.contrib.spiders import XMLFeedSpider from myproject.items import ...

  2. ubuntu_nfs搭建

    搭建步骤: 1.sudo apt-get install nfs-kernel-server 2.执行命令:mkdir /home/wmx/Desktop/nfs 搭建一个nfs服务专有的文件夹,这里 ...

  3. fatal error LNK1169: 找到一个或多个多重定义的符号

    申明全局变量,全局函数一定要在cpp中申明,其他类引用该全局变量就include该cpp的h文件,然后extern一下就好了.否则容易出现该重复定义错误. 这个"容易"是如何解释的 ...

  4. Arduino uno 教程~持续更新~

    http://arduino.osall.com/index.html http://study.163.com/search.htm?t=2&p=Arduino http://www.ard ...

  5. linux 命令 ---- 同步当前服务器时间

    原因:昨天临走前,虚拟机没有关机,是挂起状态,然后今天来的时候,发现数据库表中存(更新)的时间,不是系统时间, 解决:先运行起我们的虚拟机, (对于asterisk) 1.先查看当前服务器(linux ...

  6. RadioGroup 和 ViewPager 绑定 实现切换

    package com.jereh.helloworld.activity.day12; import android.app.Activity; import android.os.Bundle; ...

  7. React,React Native中的es5和es6写法对照

    es6用在React中的写法总结: 在es6还没有完全支持到浏览器的阶段里,已经有很多技术人员开始用es6的写法来超前编程了,因为有转义es6语法的工具帮助下,大家才可大量使用.解析看看es6写法用在 ...

  8. python 编码问题

    参考原文:http://www.crifan.com/eclipse_pydev_console_messy_char_for_console_is_utf8/ 通用 rq = urllib.urlo ...

  9. Object-C与Swift混编

    1.在Object-C项目中调用Swift 1.1.创建一个Object-C project(项目名例如:YYDemo) 如下图 1.2.创建一个Swift Class,如下图 这里会显示是否需要创建 ...

  10. 第一天---HTML基础学习

    HTML(hyper text markup language) HTML不是一种编程语言,而是一种标记语言(markup language),标记语言是一套markup tag(标记标签),HTML ...