.NET/ASP.NETMVC 深入剖析 Model元数据、HtmlHelper、自定义模板、模板的装饰者模式(三)

阅读目录:

7.HtmlHelper、HtmlHelper<T>中的ViewModel的类型推断

8.控制ViewModel中的某个属性的呈现(使用PartialView部分视图细粒度控制ViewModel的呈现)

9.模板的装饰者模式(PartialView与ViewModel的嵌套使用(简))

7.HtmlHelper、HtmlHelper<T>中的ViewModel的类型推断

在View中用来根据当前View中引入的强类型ViewModel生成HTMLDom结构的核心功能都被封装在以HtmlHelper为首的对象模型中,包括HtmlHelper<T>泛型类型,它直接派生自HtmlHelper基类,这两个类型的功能都是围绕着如何生成前端所需要的HTML结构和一些常用的UI元素;

但是这两个类型所能做的事情很有限,它们只是庞大生成功能的核心模型;我们使用的都是围绕着这两个类型的扩展方法,如:

@Html.EditorForModel()

在当前View中引用的Html属性其实是一个HtmlHelper<T>类型的属性,定义代码:

public HtmlHelper<TModel> Html { get; set; }

该类型被定义在public abstract class WebViewPage<TModel> : WebViewPage类中,其实该类是一个模板化代码生成的基类;我们在ASP.NETMVC项目中添加的所有View文件都会直接或间接的继承自该类型,在View中引入的类型定义:

@model  MvcApplication4.Models.Customer

正是这里泛型类型的类型参数,所以围绕着HtmlHelper<T>的扩展方法才变成灵活的泛型的代码生成接口;因为他们彼此通过强大的泛型类型推断,依次的推断下去,最终会到达扩展方法的内部,如:

@Html.EditorFor(model => model.Shopping)

这意思是说在View中输出一个编辑model.Shopping属性的文本框HtmlDom结构,但是我们调用的明明是一个没有任何类型形参的方法,其实它已经通过上面说将的环节进行了类型关联;

画红线的部分是View所使用的强类型HtmlHelper<T>对象,类型参数是我们在View中通过@model的方式定义的;画绿色的部分也是强类型的EditorFor<T>方法,同样该泛型方法已经被类型推断过了,看泛型方法的定义:

public static class EditorExtensions
{
  public static MvcHtmlString EditorFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression);
}

上述代码中加粗的部分正是关键所在,这里扩展的其实是我们在View中引入的强类型参数的HtmlHelper<MvcApplication4.Models.Customer>,这样任何围绕HtmlHelper<T>进行扩展的扩展方法都会最终使用到类型ViewModel;

8.控制ViewModel中的某个属性的呈现(使用PartialView部分视图细粒度控制ViewModel呈现)

对于ViewModel的呈现一直都是被系统控制着,虽然一个简单的字符串类型字段可以用一个文本框的HtmlDom结构方式呈现出来,但是那仅仅是代表着没有任何业务概念的功能性设置,也就是出发点是从CLR类型系统考虑的,而不是特定领域角度;如果这个字符串代表着某种业务概念,那么我们希望通过更人性化的方式让用户使用,而不是一个硬生生的文本框;我们可能会需要提供了一个供自动输入提示的HtmlDom结构,该结构可能还需要其他的UI成员协助,如:自动提示可能需要JS、后台Service接口等一系列成员相互协调完成;

这是一个简单的需求,在大型项目中这样的功能很常见,也是到处会使用到,不单单是一个两个页面,N多页面都会有一点点的差异性,但是整体功能都会差不多,这样我们只需要在设计的时候适当的提供一些接口就可以了;

那么ASP.NETMVC是如何生成前台所需要的HtmlDom结构的呢? 前面一章我们总结了,对于ViewModel的呈现形式只会有两种,一种是Edit一种是Display,不会有其他的呈现形式,所以在围绕着HtmlHelper对象的扩展方法中大多数都是以这种类别区分的,Edit一组,Display一组;

到目前位置我们已经知道ViewModel与View之间的桥梁是Model元数据,可以简单的理解为HtmlHelper<T> 一系列扩展方法都是通过获取Model元数据信息来控制到底需要输出什么形式的HtmlDom结构,而Model元数据都是通过Model元数据控制特性来完成的,这就可以通过控制Model元数据来控制Model的呈现细节;

public class Address
    {
        [UIHint("CustomAddress")]
        [Display(Name = "地址")]
        public string AddressId { get; set; }

}

我们在Address类型中为AddressId属性加上一个UIHint类型的特性,其实意思是想说明我们在程序内部使用的是使用地址ID,而在现实的时候我们希望将原来很单调的地址ID编程一个更人性化的地址显示方式,比如:位于什么省、什么市等等一些其他的地理信息;

在ASP.NETMVC内部有一个internal static class TemplateHelpers 类型的模板辅助类,该类是大部分模板化输出的帮助接口,在该类的内部定义了一套模板化使用的字典:

视图的类型:

static readonly Dictionary<DataBoundControlMode, string> modeViewPaths =
           new Dictionary<DataBoundControlMode, string> {
               { DataBoundControlMode.ReadOnly, "DisplayTemplates" },
               { DataBoundControlMode.Edit,     "EditorTemplates" }
           };

这里定义了两组类型,也就是显示、编辑,这两组类型将作为查找自定义模板的物理文件夹路径,同样ModelMedata中的同一个属性在不同的显示类型中将有不同的判断作用;

编辑、显示:

static readonly Dictionary<string, Func<HtmlHelper, string>> defaultDisplayActions =
    new Dictionary<string, Func<HtmlHelper, string>>(StringComparer.OrdinalIgnoreCase) {
        { "EmailAddress",       DefaultDisplayTemplates.EmailAddressTemplate },
        { "HiddenInput",        DefaultDisplayTemplates.HiddenInputTemplate },
        { "Html",               DefaultDisplayTemplates.HtmlTemplate },
        { "Text",               DefaultDisplayTemplates.StringTemplate },
        { "Url",                DefaultDisplayTemplates.UrlTemplate },
        { "Collection",         DefaultDisplayTemplates.CollectionTemplate },
        { typeof(bool).Name,    DefaultDisplayTemplates.BooleanTemplate },
        { typeof(decimal).Name, DefaultDisplayTemplates.DecimalTemplate },
        { typeof(string).Name,  DefaultDisplayTemplates.StringTemplate },
        { typeof(object).Name,  DefaultDisplayTemplates.ObjectTemplate },
    }; 

static readonly Dictionary<string, Func<HtmlHelper, string>> defaultEditorActions =
    new Dictionary<string, Func<HtmlHelper, string>>(StringComparer.OrdinalIgnoreCase) {
        { "HiddenInput",        DefaultEditorTemplates.HiddenInputTemplate },
        { "MultilineText",      DefaultEditorTemplates.MultilineTextTemplate },
        { "Password",           DefaultEditorTemplates.PasswordTemplate },
        { "Text",               DefaultEditorTemplates.StringTemplate },
        { "Collection",         DefaultEditorTemplates.CollectionTemplate },
        { typeof(bool).Name,    DefaultEditorTemplates.BooleanTemplate },
        { typeof(decimal).Name, DefaultEditorTemplates.DecimalTemplate },
        { typeof(string).Name,  DefaultEditorTemplates.StringTemplate },
        { typeof(object).Name,  DefaultEditorTemplates.ObjectTemplate },
    };

这是两组显示模式的模板化操作方法的字典,可以看出同一个HiddenInput特性将在不同的显示模式先输出不同的HtmlDom结构;

在我们的ASP.NETMVC项目中要同样的有两组文件夹DisplayTemplates、EditorTemplates,这两个文件夹将会是系统查找的路径;

我们在DisplayTemplates目录下创建了一个用来显示客户地址信息的自定义模板,其实也就是PartialView部分视图,用来重用UI;在该部分视图中,我们写点测试数据:

@model string 

<div>
    <h2>@Model</h2>
    <h2>地址:上海市、长宁区</h2>
    <h2>气温:-1~10</h2>
    <h2>交通:方便出行</h2>
</div>

然后我们刷新一下界面,看如何个性化了地址显示;

这样我们就可以控制细粒度的ViewModel显示;

9.模板的装饰者模式(PartialView与ViewModel的嵌套使用(简))

其实我们应该能够领悟到通过PartialView与HtmlHelper彼此互相嵌套能让原本单一的部分视图变成一个强大的具有设计模式功能的模板装饰者模式;想想看,如果我们将这里的AddressId类型再设计成复杂的类型,然后在该复杂的类型内部我们嵌套了一个原本在其他地方使用的地址类型ViewModel,而且刚好该类型也具有相应的部分是视图,这样我们就可以将ViewModel的嵌套使用与PartialView嵌套使用相结合,这样就可以使用类似设计模式中的装饰者模式来完成很多UI上的展现重用功能;

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏小灰灰

基于ForkJoin构建一个简单易用的并发组件

基于ForkJoin构建一个简单易用的并发组件 在实际的业务开发中,需要用到并发编程的知识,实际使用线程池来异步执行任务的场景并不是特别多,而且一般真的遇到了需...

3778
来自专栏Golang语言社区

十条有用的 Golang语言 技术

十条有用的 Go 技术 这里是我过去几年中编写的大量 Go 代码的经验总结而来的自己的最佳实践。我相信它们具有弹性的。这里的弹性是指: 某个应用需要适配一个灵...

4159
来自专栏用户2442861的专栏

Python标准库11 时间与日期 (time, datetime包)

作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明。谢谢!

752
来自专栏企鹅号快讯

Bruce.Wang-记一次对JS木马分析

0×00 前言 随着 javascript 这类脚本语言的快速发展,它能展现的内容越来越多样,所运用的功能变得越来越强大。当运用在 web 开发上时,增加了更多...

2366
来自专栏企鹅FM

深入浅出Kotlin协程

协程(Coroutines)已经随着Kotlin1.3版本一起发布了1.0正式版,android平台可以使用如下方式引入:

2.9K8
来自专栏拂晓风起

【超精简JS模版库/前端模板库】原理简析 和 XSS防范

1423
来自专栏对角另一面

lodash源码分析之chunk的尺与刀

以不正义开始的事情,必须用罪恶使它巩固。 ——莎士比亚《麦克白》 最近很多事似乎印证了这句话,一句谎言最后要用一百句谎言来圆谎。 本文为读 lodash...

2260
来自专栏安恒网络空间安全讲武堂

记一次JS木马分析

记一次JS木马分析 0x00 前言 随着 javascript 这类脚本语言的快速发展,它能展现的内容越来越多样,所运用的功能变得越来越强大。当运用在 web ...

4068
来自专栏程序员与猫

Go Code Review Comments 译文(截止2018年7月27日)

持续更新中… 原文最新链接 https://github.com/golang/go/wiki/CodeReviewComments/5a40ba36d38...

1303
来自专栏老司机的简书

老司机读书笔记——Effective Objective-C 2.0阅读笔记

比方说,在循环中不断地创建的临时对象。即便这些对象在调用完方法之后就就不在使用了,他们也依然处于存活状态,因为目前还在自动释放池里,等待系统稍后将其释放并回收。...

822

扫码关注云+社区