必看

从 ASP.NET MVC 6 开始,Tag Helper 就逐步取代了 HTML Helper。相较于后者,Tag Helper 的可控性更强,更适应现代网页设计开发。

注:虽然 HTML Helper 用得少了,但这种组件封装的思想还是值得一看的。

概述

本节将详细介绍 HTML Helper(HTML 帮助程序)。

本节内容包括:

  • HTML Helper 的概念

  • 自定义 HTML Helper

  • 内置的 HTML Helper

  • HTML Helper 和模型

HTML Helper 的概念

我们可以将 Razor 表达式看作 HTML 文本模板上的“窟窿”,这些“窟窿”最终将由 Razor 表达式的值填充。
例如,下面的代码中有四个 Razor 表达式(窟窿):

  1. <td>@p.ID</td><td>@p.Name</td><td>@p.Price</td><td>@(p.Price * rate)</td>,

最终这些 Razor 表达式的“窟窿”将由它们的值填充,大致如下:

  1. <td>101</td><td>Bike</td><td>100</td><td>75</td>.

得到上面 HTML 的原因是这些 Razor 表达式的值是纯文本。但是如果 Razor 表达式的值本身就是 HTML 标签将会怎样?

  1. <h1>Welcome to Seattle</h1>

这将实现将新的 HTML 标签(此处的 h1)嵌入到现有的 HTML 标签中。这便是 HTML Helper 的基本思想。

自定义 HTML Helper

通过自定义 HTML Helper,提前向你揭示它的运行机制。

HTML Helper 通过扩展方法实现。下面这段代码就是我们的自定义 HTML Helper,理论上这段代码放在项目中任何地方都会生效,但推荐你将它放在项目根目录下的 Extensions 文件夹内。

  1. namespace Microsoft.AspNetCore.Mvc.Rendering
  2. {
  3. public static class MyHtmlHelperExtensions
  4. {
  5. public static IHtmlContent ColorfulHeading(this IHtmlHelper htmlHelper, int level, string color, string content)
  6. {
  7. level = level < 1 ? 1 : level;
  8. level = level > 6 ? 6 : level;
  9. var tagName = $"h{level}";
  10. var tagBuilder = new TagBuilder(tagName);
  11. tagBuilder.Attributes.Add("style", $"color:{color ?? "green"}");
  12. tagBuilder.InnerHtml.Append(content ?? string.Empty);
  13. return tagBuilder;
  14. }
  15. }
  16. }

仔细分析这段代码:

  • 我们将该类放在 Microsoft.AspNetCore.Mvc.Rendering 命名空间中。因为 Razor 引擎总是从这个命名空间加载扩展

    • 你也可以将该类放在其他命名空间中,但你必须在视图的顶部使用 @use 指令导入命名空间
  • 扩展方法类必须声明为 static

  • 扩展方法也必须是 static 的。返回类型是由 TagBuilder 类实现的 IHtmlContent。this IHtmlHelper htmlHelper 参数表明这是一个扩展方法

  • 方法名称 ColorfulHeading 反映了它的逻辑 —— 为给定的文本内容创建 header 标签,并允许开发人员设置标题级别和前景色

测试代码:

  1. @Html.ColorfulHeading(1, "green", "Welcome to Seattle")
  2. @Html.ColorfulHeading(2, "orange", "Especially in Summer")

效果:
4.3 使用 Razor Helper - 图1

生成的 HTML 源码:

  1. <h1 style="color:green">Welcome to Seattle</h1>
  2. <h2 style="color:orange">Especially in Summer</h2>

用于模型绑定的 HTML Helper

我们知道模型绑定时,一旦从 Web 端发送过来的键值对与模型名或模型的属性名不匹配,模型绑定就会失败。

幸运的是,对于设计良好的 Web 程序,我们始终在领域模型、视图模型和数据传输模型(DTO)之间共享相同的名称集。这使我们有机会通过 Lambda 表达式来约束名称。

如何通过变量获取属性名

在创建受模型名或模型属性名约束的 HTML Helper 程序前,让我们先看看“如何通过 Lambda 表达式获取模型属性名”。下面的示例运行在一个控制台程序中。

首先,准备一个模型类:

  1. public class Person
  2. {
  3. public int ID { get; set; }
  4. public string Name { get; set; }
  5. public int Age { get; set; }
  6. }

然后,准备一组泛型类。这组泛型类大致反映了实际视图类的结构。FakeViewPage 就表示从视图文件(.cshtml 文件)转换而来的视图页面类。

  1. public class FakeHtmlHelper<TModel>
  2. {
  3. }
  4. public class FakeViewPage<TModel>
  5. {
  6. public FakeHtmlHelper<TModel> HtmlHelper { get; }
  7. public FakeViewPage()
  8. {
  9. HtmlHelper = new FakeHtmlHelper<TModel>();
  10. }
  11. }

接着,给 FakeHtmlHelper 添加一个扩展方法:

  1. public static class FakeHtmlHelperExtension
  2. {
  3. public static string GetName<TModel, TResult>(this FakeHtmlHelper<TModel> target, Expression<Func<TModel, TResult>> expression)
  4. {
  5. var body = expression.Body as MemberExpression;
  6. return body.Member.Name;
  7. }
  8. }

最后,我们完善 Main 方法:

  1. static void Main(string[] args)
  2. {
  3. var view = new FakeViewPage<Person>();
  4. var propertyName = view.HtmlHelper.GetName(p => p.ID);
  5. Console.WriteLine(propertyName);
  6. propertyName = view.HtmlHelper.GetName(p => p.Name);
  7. Console.WriteLine(propertyName);
  8. propertyName = view.HtmlHelper.GetName(p => p.Age);
  9. Console.WriteLine(propertyName);
  10. }

运行程序,输出:

  1. ID
  2. Name
  3. Age

约束 HTML 标签的属性

现在就将名称约束应用到 HTML Helper 上。

在 MyHtmlHelperExtensions 类里面添加一个扩展方法:

  1. namespace Microsoft.AspNetCore.Mvc.Rendering
  2. {
  3. public static class MyHtmlHelperExtensions
  4. {
  5. public static IHtmlContent MyTextBoxFor<TModel, TResult>(this IHtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TResult>> expression)
  6. {
  7. var body = expression.Body as MemberExpression;
  8. var propertyName = body.Member.Name.ToLower(); // keep lower case
  9. var tagBuilder = new TagBuilder("input");
  10. tagBuilder.Attributes.Add("type", "text"); // can be omitted
  11. tagBuilder.Attributes.Add("name", propertyName); // key purpose 1: for name-value pair
  12. tagBuilder.Attributes.Add("id", propertyName); // key purpose 2: for client JavaScript and CSS
  13. return tagBuilder;
  14. }
  15. }
  16. }

添加 View 测试该方法:

  1. @model HelloMVC.Models.Person
  2. <html>
  3. <head>
  4. <title></title>
  5. </head>
  6. <body>
  7. @Html.MyTextBoxFor(p => p.ID)<br />
  8. @Html.MyTextBoxFor(p => p.Name)<br />
  9. @Html.MyTextBoxFor(p => p.Age)<br />
  10. </body>
  11. </html>

页面效果:
4.3 使用 Razor Helper - 图2

源码:

  1. <input id="id" name="id" type="text"></input><br />
  2. <input id="name" name="name" type="text"></input><br />
  3. <input id="age" name="age" type="text"></input><br />

显然,我们成功的通过 Lambda 表达式实现了 —— 使用模型的属性名约束生成的 HTML 标签的属性。

这样做得好处是模型绑定的键值对永远不会出错,特别是模型的属性名被更改时。

PS:如果我们在 HTML Helper 扩展方法中创建更复杂的 TagBuilder 结构,我们可以获得如日历、数据表、图表和仪表板等更复杂的 UI 组件。这也是 Telerik UI for ASP.NET MVC 等第三方 HTML Helper 的工作模式。

ASP.NET Core 内置的 HTML