• ASP.NET MVC 之Model的呈现


    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不是非常不好的情况下,会出现下面的结果:

    http://www.cnblogs.com/DotCpp/

    生成的是一个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旅程:

    http://www.cnblogs.com/DotCpp/

         【注意,这个图中代码是经过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 中国大陆许可协议进行许可。
    如您有任何疑问或者授权方面的协商,请给我留言
  • 相关阅读:
    IOS
    WAN
    在不同网段使用 VLAN 通信
    数据链路层
    GRE 协议
    路由协议 (1)
    隔离广播域的 VLAN 来了
    数据包的通信过程
    Webpack 原理浅析
    蒲公英 · JELLY技术周刊 Vol.16 谷歌首个线上 Web 开发者大会
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3321971.html
Copyright © 2020-2023  润新知