ASP.NET MVC 之Model的呈现(仅此一文系列三)

本文目的

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

 public class HomeController : Controller
{
public ActionResult Index()
{
var model = new DemoModel {Email = "test@test.com"};
return View(model);
}
} public class DemoModel
{
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
}

创建对应的强类型视图

@model TestMvc.Controllers.DemoModel
<fieldset>
<legend>DemoModel</legend> <div >
@Html.DisplayFor(model => model.Email)
</div>
</fieldset>

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

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

 <div >
<a href="mailto:test@test.com">test@test.com</a>
</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绑定与验证,所以我建议你还是先认识一下这位好兄弟】,让我们来看看这个兄弟长什么样:

    public class ModelMetadata
{
private readonly Type _modelType; //Model的类型
private readonly string _propertyName; //属性名称
private object _model; //Model的值
private Func<object> _modelAccessor; //为了用Lambd给Model传值(这是潮流)
private IEnumerable<ModelMetadata> _properties; //Model属性值的ModelMetadata集合 //通过子类的ComputeXX系列函数将Model的注解特性赋值到下面几个属性
public virtual string DataTypeName { get; set; }
public virtual string Description { get; set; }
public virtual string DisplayFormatString { get; set; }
public virtual string DisplayName { get; set; }
(其它略,属性太多了,影响阅读...) public object Model
{
get
{
if (_modelAccessor != null)
{
_model = _modelAccessor();
_modelAccessor = null;
}
return _model;
}
} public virtual IEnumerable<ModelMetadata> Properties
{
get
{
if (_properties == null)
{
IEnumerable<ModelMetadata> originalProperties = Provider.GetMetadataForProperties(Model, RealModelType);
_propertiesInternal = SortProperties(originalProperties.AsArray());
_properties = new ReadOnlyCollection<ModelMetadata>(_propertiesInternal);
}
return _properties;
}
} protected ModelMetadataProvider Provider { get; set; } //ModelMetadata的创建者(后面会有介绍)
public virtual string TemplateHint { get; set; } //Model显示用的模板名称 }

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

    public class CachedDataAnnotationsModelMetadata : CachedModelMetadata<CachedDataAnnotationsMetadataAttributes>
{
public CachedDataAnnotationsModelMetadata(CachedDataAnnotationsModelMetadataProvider provider, Type containerType, Type modelType, string propertyName, IEnumerable<Attribute> attributes)
: base(provider, containerType, modelType, propertyName, new CachedDataAnnotationsMetadataAttributes(attributes.ToArray()))
{
PrototypeCache = new CachedDataAnnotationMetadataAttributes(attributes.ToArray()); //这个赋值应该在它的父类CachedModelMetadata中执行的,这里简化一下,方便阅读
} protected override string ComputeDataTypeName()
{
if (PrototypeCache.DataType != null)
{
return PrototypeCache.DataType.ToDataTypeName();
}return base.ComputeDataTypeName();
} public override string DataTypeName{ get{ return ComputeDataTypeName();}}
}

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

ModelMetadata的创建者ModelMetadataProvider

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

public abstract class ModelMetadataProvider
{
public abstract IEnumerable<ModelMetadata> GetMetadataForProperties(object container, Type containerType);
public abstract ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName);
public abstract ModelMetadata GetMetadataForType(Func<object> modelAccessor, Type modelType);
}

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

public abstract class AssociatedMetadataProvider : ModelMetadataProvider
{
private static void ApplyMetadataAwareAttributes(IEnumerable<Attribute> attributes, ModelMetadata result)
{
foreach (IMetadataAware awareAttribute in attributes.OfType<IMetadataAware>())
{
awareAttribute.OnMetadataCreated(result);
}
} protected abstract ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName); public override ModelMetadata GetMetadataForType(Func<object> modelAccessor, Type modelType)
{
//得到Model上上面的Attribute
AttributeList attributes = new AttributeList(GetTypeDescriptor(modelType).GetAttributes());
//创建ModelMetadata
ModelMetadata result = CreateMetadata(attributes, null /* containerType */, modelAccessor, modelType, null /* propertyName */);
//创建ModelMetadata后,用所有实现IMetadataAware接口的Attribute对ModelMetadata进行改造
ApplyMetadataAwareAttributes(attributes, result);
return result;
} protected virtual ICustomTypeDescriptor GetTypeDescriptor(Type type)
{
return TypeDescriptorHelper.Get(type);
}
}

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

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

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

public class CachedDataAnnotationsModelMetadataProvider : CachedAssociatedMetadataProvider<CachedDataAnnotationsModelMetadata>
{
protected override CachedDataAnnotationsModelMetadata CreateMetadataPrototype(IEnumerable<Attribute> attributes, Type containerType, Type modelType, string propertyName)
{
return new CachedDataAnnotationsModelMetadata(this, containerType, modelType, propertyName, attributes);
} protected override CachedDataAnnotationsModelMetadata CreateMetadataFromPrototype(CachedDataAnnotationsModelMetadata prototype, Func<object> modelAccessor)
{
return new CachedDataAnnotationsModelMetadata(prototype, modelAccessor);
}
}

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

public class ModelMetadataProviders
{
private static ModelMetadataProviders _instance = new ModelMetadataProviders();
private ModelMetadataProvider _currentProvider; internal ModelMetadataProviders()
{
_currentProvider=new CachedDataAnnotationsModelMetadataProvider();
} public static ModelMetadataProvider Current
{
get { return _instance._currentProvider; }
set { _instance._currentProvider = value; }
}
}

终极解密

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

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绑定与验证,敬请期待!

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


作者:.ccp(.cpp的代码人生)
出处:http://www.cnblogs.com/dotcpp/
本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
如您有任何疑问或者授权方面的协商,请给我留言

ASP.NET MVC 之Model的呈现的更多相关文章

  1. 仅此一文让你明白ASP.NET MVC 之Model的呈现(仅此一文系列三)

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

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

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

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

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

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

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

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

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

  6. ASP.NET MVC 3 Model【通过一简单实例一步一步的介绍】

    今天主要讲Model的两个方面: 1. ASP.Net MVC 3 Model 简介 通过一简单的事例一步一步的介绍 2. ASP.Net MVC 3 Model 的一些验证 MVC 中 Model ...

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

    Asp.net MVC的Model Binder工作流程以及扩展方法(1)2014-03-19 08:02 by JustRun, 523 阅读, 4 评论, 收藏, 编辑 在Asp.net MVC中 ...

  8. 区别:ASP.NET MVC的Model、DTO、Command

    最近在用CQRS架构模式做项目,有些感悟,记录下来. 问题的描述(大家是否也存在过类似的情况呢?): 从刚开始时项目中没有区分这3种对象,所以导致了很多职责公用,然后就乱了,比如Command一部分职 ...

  9. ASP.NET MVC传递Model到视图的多种方式总结

    ASP.NET MVC传递Model到视图的多种方式总结 有多种方式可以将数据传递到视图,如下所示: ViewData ViewBag PartialView TempData ViewModel T ...

随机推荐

  1. C#将Word,Excel与Html,PDF互转

    public class OfficeHelper { /// <summary> /// word转成html /// </summary> /// <param na ...

  2. Shuttle ESB

    Shuttle ESB(六)——在项目中的应用 如果说你认真看了前面几篇关于ESB的介绍,我相信,在这一篇文章中,你将会找到很多共鸣. 尽管,市面上开源的ESB确实非常之多,像Java中的Mule E ...

  3. 从用python做zoj1011发生Non-zero Exit Code错误说起

    近期做了浙大oj的第1011道题,遇见一件奇怪的事.这道题我用c++和php做,提交后都正确.可是用全然同样的逻辑改写成python代码提交后却产生了Non-zero Exit Code的判题结果.p ...

  4. 转载:Eclipse+Spket插件+ExtJs4修改版提供代码提示功能[图]

    转载:Eclipse+Spket插件+ExtJs4修改版提供代码提示功能[图] ExtJs是一种主要用于创建前端用户界面,是一个基本与后台技术无关的前端ajax框架.功能丰富,无人能出其右.无论是界面 ...

  5. C语言库函数大全及应用实例一

    原文:C语言库函数大全及应用实例一                                 [编程资料]C语言库函数大全及应用实例一 函数名: abort 功 能: 异常终止一个进程 用 法: ...

  6. Mac下一个/usr/include失踪

    Mac升级到Yosemite后,突然发现vim的YouCompleteMe代码提示所以空头支票成员,排查了一下,原本/usr/include目录中缺少.所有的C/C++头文件不见了. .. 第一次发现 ...

  7. javascript3

    计算阶乘函数:<script> function factorial(n){ var product=1; while (n>1){ product*=n;//product=pro ...

  8. 还在等待漫长的iOS构建过程?来试试通过命令行的方式进行iOS应用快速构建和运行吧

    不必多言,Xcode慢得很是众所周知的了.更甚者是,我有时发觉自己太依赖于Cocoa Touch的自动完成功能了,这可是个天使和魔鬼的结合体! 故此我开始去寻觅一个替代的流程来通过命令行来实现我需要的 ...

  9. ASP.NET 5+EntityFramework 7

    爱与恨的抉择:ASP.NET 5+EntityFramework 7   EF7 的纠缠 ASP.NET 5 的无助 忘不了你的好 一开始列出的这个博文大纲,让我想到了很久之前的一篇博文:恋爱虽易,相 ...

  10. 工欲善其事:编辑器之神Vim(一)

    本篇文章收集了Vim最常用和实用的一些命令,掌握这些命令就可以使用Vim了 本文仅列举个人认为常用的命令,本系列文章更像是自己的学习笔记,而不是VIM使用教程. 如果你想了解vim的更多,可以看看Co ...