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

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

  1. HashMap的工作原理

    HashMap的工作原理   HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道HashTable和HashMap之间 ...

  2. HashMap的工作原理深入再深入

    前言 首先再次强调hashcode (==)和equals的真正含义(我记得以前有人会说,equals是判断对象内容,hashcode是判断是否相等之类): equals:是否同一个对象实例.注意,是 ...

  3. HashMap工作原理(转载)

    转载自:http://www.importnew.com/7099.html  HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都知道哪里要用Hash ...

  4. [转] HashMap的工作原理

    HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道Hashtable和HashMap之间的区别,那么为何这道面试题如此 ...

  5. 【转】HashMap的工作原理

    很好的文章,推荐Java的一个好网站:ImportNew HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道Hasht ...

  6. 转:HashMap的工作原理,及笔记

    HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道Hashtable和HashMap之间的区别,那么为何这道面试题如此 ...

  7. HashMap的工作原理(转)

    HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道Hashtable和HashMap之间的区别,那么为何这道面试题如此 ...

  8. 【伯乐在线】HashMap的工作原理

    本文由 ImportNew - 唐小娟 翻译自 Javarevisited.欢迎加入翻译小组.转载请见文末要求. HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道Ha ...

  9. 【转】Java学习---HashMap的工作原理

    [原文]https://www.toutiao.com/i6592560649652404744/ HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都 ...

随机推荐

  1. ng-指令

    在 Angular 中最常用的指令分为两种,它们分别是 属性型指令 和 结构型指令. NgClass 作用:添加或移除一组 CSS 类 NgStyle 作用:添加或移除一组 CSS 样式 NgMode ...

  2. SQL Server误设置max server memory处理小结

    昨天网上一网友说,由于他同事误将"max server memory"设置为10M后,SQL Server数据库登录不了,当时我简单测试了一下,今天有空就顺手将整个过程整理一下,记 ...

  3. [[FJOI2016]神秘数][主席树]

    明白之后 5min 就写好了-自闭- 这题的题意是问你 \([L,R]\) 区间的数字不能构成的数字的最小值- 首先考虑 如果 \([1,x]\) 可以被表示 那么加入一个 \(a_i\) 显然 \( ...

  4. Docker+Nginx+Tomcat实现负载均衡

    环境检测: 1.Docker没有安装的小伙伴请查看https://www.cnblogs.com/niuniu0108/p/12372531.html 2.没有创建Nginx容器的小伙伴请查看http ...

  5. codechef Counting D-sets

    难度 \(hard\) 题意 官方中文题意 做法 为避免数重,强制每维至少有一个\(0\),这个可以简单容斥 直径恰好为\(d\),不好求,也容斥一下

  6. Java工程师的必备知识点

    最近参加了一次公司内部的调岗计划,打算加入一个更核心的部门.调岗计划有面试环节,为了不让自己搞砸,悉心准备了将近一个月,请教了百度和腾讯的有过面试官经验的大学同学,系统性的总结了Java工程师的核心知 ...

  7. 通过ssh-copy-id免密码连接Linux主机

    Login Raspberry Pi without passcode via ssh-copy-id Generate public key $ ssh-keygen -t rsa Upload p ...

  8. 海康 - 终端服务器 - TS-5012-F

    简介 型号描述 主要特点 典型应用 技术参数        型号 参数 TS-5012-F (1T) TS-5012-F (2T) TS-5012-F (4T) TS-5012-F (8T) 系统参数 ...

  9. 自定义输入函数 - C语言编程

    有返回功能的输入函数: #include <stdio.h> #include <conio.h> void input(char content[]); int main() ...

  10. poj1321棋盘问题(dfs+摆放问题)

    在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别.要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C. ...