zoukankan      html  css  js  c++  java
  • 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
    首先,需要明确一点:每个视图都是一个类。

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

    在视图代码中,在HtmlModel等属性上右击鼠标,在弹出的对话框中选择“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

  • 相关阅读:
    JAVA实现的异步redisclient
    权限表设计
    操作系统之存储管理(续)
    leetcode第一刷_Jump Game
    linux之stat函数解析
    重温微积分 —— 偏微分与链式法则
    重温微积分 —— 偏微分与链式法则
    所谓敏感(数字的敏感)
    所谓敏感(数字的敏感)
    推理集 —— 特殊的工具
  • 原文地址:https://www.cnblogs.com/wfy680/p/12258397.html
Copyright © 2011-2022 走看看