DisplayNameFor()方法的工作原理

DisplayNameFor()方法的工作原理
原创Peter Yelnav 最后发布于2018-11-23 11:09:51 阅读数 1308 收藏
展开
最近研究了一下ASP.NET MVC,困惑于视图中DisplayNameFor()方法,于是粗略探究了一下。观点浅显,如有错误之处,还请各位大神多多指正。

完整代码可以到Microsoft Doc / ASP.NET / ASP.NET MVC中查看,链接如下:https://docs.microsoft.com/en-us/aspnet/mvc/overview/getting-started/introduction/。这里只贴出与话题相关的代码:

视图:

@model IEnumerable<MvcMovie.Models.Movie>

<table class="table">
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
/* removed for clarity */
</tr>

@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
/* removed for clarity */
</tr>
}
</table>
模型:

public class Movie
{
public int ID { get; set; }
public string Title { get; set; }

[Display(Name = "Release Date")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}

public class MovieDBContext : DbContext
{
public DbSet<Movie> Movies { get; set; }
}
我的疑问是:视图中DisplayNameFor()方法如何输出属性的名称? 注意,不是属性的值,而是属性的名称。为了解释这个问题,需要分析下面这行代码:

@Html.DisplayNameFor(model => model.Title)
探究一:@Html
首先,需要明确一点:每个视图都是一个类。

这可能有悖常识:在视图的源代码中只有HTML\CSS\Razor代码,却没有class关键字!怎么证明它是一个类呢?

在视图代码中,在Html\Model等属性上右击鼠标,在弹出的对话框中选择“Go to Definition”,Visual Studio会导引到抽象类WebViewPage<TModel>,可以看到,上述属性都定义在这个类中(如果查看ViewBage的定义,Visual Studio会导引到抽象类WebViewPage,而WebViewPage<TModel>继承自WebViewPage)。

// System.Web.Mvc
abstract class WebViewPage<TModel>: WebViewPage
{
public AjaxHelper<TModel> Ajax { get; set; }
public HtmlHelper<TModel> Html { get; set; }
public TModel Model { get; }
public ViewDataDictionary<TModel> ViewData { get; set; }

/* Others removed for clarity */
}
换言之,每个视图都是一个继承自WebViewPage<TModel>的类。Html是这个类的一个属性,类型是HtmlHelper<TModel>。

在上面的视图中,TModel具体表示什么呢?在视图的开头有如下一行代码:

@model IEnumerable<MvcMovie.Models.Movie>
它清晰地表明:TModel表示IEnumerable<MvcMovie.Models.Movie>(注意,不是MvcMovie.Models.Movie)。

那么,HtmlHelper<TModel>在这里就表示HtmlHelper< IEnumerable<MvcMovie.Models.Movie>>。

探究二:DisplayNameFor(model => model.Title)
如果我们查看HtmlHelper<TModel>的定义(包括它的父类),可以发现并没有DisplayNameFor()方法的定义。右击DisplayNameFor()方法,选择“Go to Definition”,Visual Studio会导引到静态类DisplayNameExtensions,可以发现它是一个静态的扩展方法,并且它有两个重载。

// System.Web.Mvc.Html
static class DisplayNameExtensions
{
public static MvcHtmlString DisplayNameFor<TModel, TValue>(
this HtmlHelper<IEnumerable<TModel>> html,
Expression<Func<TModel, TValue>> expression);

public static MvcHtmlString DisplayNameFor<TModel, TValue>(
this HtmlHelper<TModel> html,
Expression<Func<TModel, TValue>> expression);

/* Others removed for clarity */
}
这两个重载的区别在于:是将Html看作HtmlHelper<IEnumerable<TModel>>,还是看作HtmlHelper<TModel>?编译器又是如何作出正确选择的呢?

继续回到下面这行代码:

@Html.DisplayNameFor(model => model.Title)
如上分析,这里的Html表示HtmlHelper< IEnumerable<MvcMovie.Models.Movie>>。问题是,编译器怎么知道TModel是IEnumerable<MvcMovie.Models.Movie>还是MvcMovie.Models.Movie呢?

诀窍在于lamda表达式=>后面的部分。

让我们先做两个实验:

实验一:把上述代码改成

@Html.DisplayNameFor(model => "hello")
修改后,Visual Studio发出了错误提示:“The call is ambiguous between the following properties or methods: …”。后面的内容被省略了,就是DisplayNameFor()方法的两个重载。

这表明,编译器已经无法确定使用哪个重载了,也就是说无法确定TModel到底表示什么了。

实验二:把上述代码改成:

@Html.DisplayNameFor(model => model.Title + "hello")
这次编译器没有错误提示。因为从语法角度看,修改后的lamda表达式依然符合Func<TModel, TValue>的要求(注意,两个重载的lamda表达式格式是一致的),所以编译时不会发生错误。

但是,运行后发生了异常:InvalidOperationException: Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions。

这个异常大致的意思是:运行时,编译器会调用模板进行匹配,这个模板只支持读单个字段、读单个属性、一维数组的某个元素、单参数索引器等格式。

至此,答案基本上清晰了:

1)编译器并不是按照正常思路使用Func<TModel, TValue>。

2)正常用法是获取Func<TModel, TValue>的返回值,而这里通过获取返回值的类型来获取某个属性的类型。

3)编译器期望编程人员在Func<TModel, TValue>中使用TModel的一个属性作为返回值,这样返回值的类型TValue就是这个属性的类型,编译器就可以获取这个属性的名称并输出到HTML中。

注意:因为是运行时的逻辑,所以上面所说的编译器是指JIT。

探究三:真的是输出属性名称吗?
其实,编译器本意并不是输出属性名称,而是通过定制属性制定的字符串。看模型中ReleaseDate属性的定义,上面被贴上了若干个定制属性。这些定制属性告诉编译器,在调用DisplayNameFor()方法时应该输出什么、格式是什么。

[Display(Name = "Release Date")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime ReleaseDate { get; set; }
只有当这些定制属性不存在时,编译器才使用属性名作为HTML输出。这是为了简化程序员编程、针对普遍情况而采用的默认配置。
————————————————
版权声明:本文为CSDN博主「Peter Yelnav」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/huguangdlut/article/details/84374957

原文地址:https://www.cnblogs.com/wfy680/p/12258397.html

时间: 2024-10-10 05:28:57

DisplayNameFor()方法的工作原理的相关文章

HTML5 的离线储存使用方法和工作原理

在用户没有与因特网连接时,可以正常访问站点或应用,在用户与因特网连接时,更新用户机器上的缓存文件.原理:HTML5 的离线存储是基于一个新建的.appcache 文件的缓存机制(不是存储技术),通过这个文件上的解析清单离线存储资源,这些资源就会像 cookie 一样被存储了下来.之后当网络在处于离线状态下时,浏览器会通过被离线存储的数据进行页面展示. 如何使用:1.页面头部像下面一样加入一个 manifest 的属性:2.在 cache.manifest 文件的编写离线存储的资源:CACHE M

Servlet生命周期、工作原理、配置

Servlet生命周期 分为三个阶段: 1,初始化阶段  调用init()方法 2,响应客户请求阶段 调用service()方法 3,终止阶段 调用destroy()方法 Servlet工作原理 每一个自定义的Servlet都必须实现Servlet的接口,Servlet接口中定义了五个方法,其中比较重要的三个方法涉及到Servlet的生命周期,分别是上文提到的init(),service(),destroy()方法.GenericServlet是一个通用的,不特定于任何协议的Servlet,它实

HashMap的工作原理

这是一节让你深入理解hash_map的介绍,如果你只是想囫囵吞枣,不想理解其原理,你倒是可以略过这一节,但我还是建议你看看,多了解一些没有坏处. hash_map基于hash table(哈希表).哈希表最大的优点,就是把数据的存储和查找消耗的时间大大降低,几乎可以看成是常数时间:而代价仅仅是消耗比较多的内存.然而在当前可利用内存越来越多的情况下,用空间换时间的做法是值得的.另外,编码比较容易也是它的特点之一. 其基本原理是:使用一个下标范围比较大的数组来存储元素.可以设计一个函数(哈希函数,也

Java EnumSet工作原理初窥

EnumSet是Java枚举类型的泛型容器,Java既然有了SortedSet.TreeSet.HashSet等容器,为何还要多一个EnumSet<T>呢?答案肯定是EnumSet有一定的特性,举个例子,EnumSet的速度很快.其他特性就不一一列举了,毕竟本文的内容不是介绍EnumSet的特性. 首先以事实说话,存在这样一个EnumSet,它有50个枚举值T0-T49,将50个值插入到容器(HashSet.EnumSet)中,为一个操作,将50个枚举值移出做为第二个操作.把第一个和第二个操作

struts2学习笔记2 -struts2的开发步骤和工作原理

struts2的开发步骤: 1.先定义一个能发送请求的页面,可以是链接,也可以是表单(form) 2.开发action类,struts2对action并没有过多的要求,只要求: a 推荐实现action接口,或继承actionsupport类 b 为每个请求参数都提供feild,并为之提供相应的setter和getter方法 c 该action类应该有无参数构造器 3.配置action类 所有action都需要放在package里配置. <action>元素有如下属性: name 指定该act

虚函数列表: 取出方法 // 虚函数工作原理和(虚)继承类的内存占用大小计算 32位机器上 sizeof(void *) // 4byte

#include <iostream> using namespace std; class A { public: A(){} virtual void geta(){ cout << "A:A" <<endl; } virtual void getb(){ cout << "A:B" <<endl; } }; class B :public A{ public: B(){} virtual void g

从头认识java-15.7 Map(2)-介绍HashMap的工作原理-put方法

这一章节我们来介绍HashMap的工作原理. 1.HashMap的工作原理图 下图引用自:http://www.admin10000.com/document/3322.html 2.HashMap初始化的时候我们可以这样理解:一个数组,每一个位置存储的是一个链表,链表里面的每一个元素才是我们记录的元素 3.下面我们来看put的源码: public V put(K key, V value) { if (key == null) return putForNullKey(value); int

GPRS DTU工作原理和功能 DTU配置方法详解

GPRS DTU 简称GPRS模块,即串口服务器的无线版,其功能与串口服务器类似.利用移动和联通遍布全国的GSM网络,通过短信方式进行数据传输.那么GPRS DTU有什么功能呢?GPRS DTU怎么配置? GPRS DTU工作原理简介: DTU与服务器之间的通信是由GPRS DTU端(客户端)发起的,服务器端通过发回反馈或接受通信来对DTU端做出响应.DTU端与服务器端共同组成了基于GPRS和INTERNET网络通信的应用系统.相比DTU端,服务器端安装有更为复杂的应用程序,能够接受任何DTU端

汽车防雨剂的工作原理和使用方法

身为一名老司机,肯定都有开车遇到雨雾天气的经历,雨水覆盖在后视镜和车窗玻璃上,影响开车视线,安全隐患很大.其实这个问题是可以轻松化解的,只要给汽车玻璃喷上防雨剂,雨天后视镜就不会再沾水了. 一.汽车防雨剂工作原理 玻璃车窗容易残余雨水是因为玻璃具有亲水性,加上行驶途中沾上灰尘,雨滴聚集不容易滑落.汽车防雨剂借鉴荷叶仿生学原理,将氟素纳米分子喷涂在玻璃表面,形成一层光滑防水膜,能快速疏导雨水,避免镜面模糊不清,驾驶更安全. 二.汽车防雨剂使用方法 防雨剂使用方法很简单,注意细节即可. 1.首先将后