标记帮助器,即 Tag Helpers。这个嘛,就直接翻译了,叫“标记帮助器”,虽然不好听,但只能这样了。当然你翻译为“标记增强器”也行。

所谓标记帮助器,就是针对 HTML 标签(不管是标准的还是自己命名的)进行扩展的做法。它是以 Razor 为基础的,服务于开发人员的。在服务器端用 C# 代码来实现一些需求,并生成 HTML 元素。在 Razor 文档中可以方便书写,VS 、VS Code 等工具还有提示功能。

不太恰当的理解就是把某个 HTML 标记封装为了一种组件,或者补充它原有的功能。不过,理解为一种组件也不算错,只不过不像 Razor 组件那样完整化的封装(里面是一大段HTML),Tag Helper 就是针对某个 HTML 元素的。

老周这篇水文不介绍常用的标记帮助器,毕竟这些大伙们都会用,就是在 Razor 文档中用 @addTagHeler 指令导入的那些类型。如内置的 input、form 元素的帮助器。像咱们常用的像 asp-controller 、asp-action 这些HTML属性就是通过帮助器来扩充的。

老周的想法是:咱们扒一下标记帮助器的底层知识,看能不能发现点啥乐子。生活不易,人世悲苦,“长太息以掩涕兮,哀民生之多艰”,所以得找点乐子充实一下人生。

咱们先聊最抽象的接口:ITagHelperComponent。咦?这货还真是以“Component”结尾,看来确实把标记帮助器认定为一种小型 Razor 组件。看看这接口为我们规范了些啥。

Order 属性:愚蠢的机器把它翻译为【订单】。这个错误很离谱,后果很严重。你要真按订单去理解,那就完了。这个是叫【顺序】,说直接点叫优先级。数值越小就越先被执行,比如,0、3、5,那么,Order 为0的先执行,Order为5的后执行。

Init 方法:看名字就知道这是初始化时被调用的。一般没有特别需要,这方法里不用写什么代码。方法有个 TagHelperContext 类型的参数。唯一能让你修改的是 Items 属性,它是个字典结构,用来存一些自定义数据。这些自定义数据可以在不同的 TagHelper 间传递。有点像 HttpContext.Items。

ProcessAsync 方法:这个是核核核心心心,重要的事延长三拍。各种为 HTML 元素添加属性、生成内容等都在此方法中完成。

实现 ITagHelperComponent 接口的类,在 Razor 文档中是不能被 @addTagHelper 指令导入的。咱们来做来试验。

[HtmlTargetElement("p")]
public class PragTagHelper : ITagHelperComponent
{
public int Order => 2; //这个优先级可以随意 public void Init(TagHelperContext context)
{
// 不用写代码
} public Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
// 内容之前
output.PreContent.SetHtmlContent("<strong>");
// 内容之后
output.PostContent.SetHtmlContent("</strong>");
return Task.CompletedTask;
}
}
[HtmlTargetElement("p")] 特性表示:我这个标记帮助器是专为<p>元素准备,它只作用于此元素。上述例子的意思是在<p>元素的内部文本呈现之前插入“<strong>”,在内部文本呈现之后插入“</strong>”。就是实现了让段落中的文本加粗显示的效果。PreContent 表示元素内容之前,PostContent 表示元素内容之后。
现在,咱们在 Razor 文档用 @addTagHelper 指令导入一下。
@page
@addTagHelper TestApp.PragTagHelper, TestApp <p>孔明用枪打死了王司徒</p> <p>孔明用手雷轰死了王朗</p>

运行程序后,发现不起作用。生成的 HTML 文档没有插入<strong>元素。

然后,我把标记帮助器的代码改一改。这次咱们不实现 ITagHelperComponent 接口,而是 ITagHelper 接口。

[HtmlTargetElement("p")]
public class PragTagHelper : ITagHelper
{
……
}

然后再次运行。哟西,这下起作用了。

嗯,看来 ITagHelper 接口里面有文章,从声明可以看到,这个接口是继承 ITagHelperComponent 接口的。但这个接口是空的,没定义新成员。

public interface ITagHelper : ITagHelperComponent
{
}

这样就可以得出结论:ITagHelper 接口是一个标记接口,用来筛选出哪些类型可以用 @addTagHelper 指令引入——即哪些类型被认为是标记帮助器。

为了方便开发者定义自己的标记帮助器,ASP.NET Core 还提供了一个抽象类 TagHelper。

public abstract class TagHelper : ITagHelper, ITagHelperComponent
{
// 构造函数
protected TagHelper(); public virtual int Order { get; } public virtual void Init(TagHelperContext context); public virtual void Process(TagHelperContext context, TagHelperOutput output); public virtual Task ProcessAsync(TagHelperContext context, TagHelperOutput output);
}

这个抽象类将接口的实现成员都声明为虚方法,派生时开发者可以按需重写。于是,咱们前面那个例子可以做以下修改:

[HtmlTargetElement("p")]
public class PragTagHelper : TagHelper
{
public override void Process(TagHelperContext context, TagHelperOutput output)
{
// 内容之前
output.PreContent.SetHtmlContent("<strong>");
// 内容之后
output.PostContent.SetHtmlContent("</strong>");
}
}

咱们前面试过,ITagHelperComponent 的实现类是不能被 @addTagHelper 指令发现的,那么,这个接口还有没有用呢?当然有用,只是直接实现这个接口的类,只针对<head>和<body>元素,通常用于大面积修改 HTML 的情形。比如,你要在 <body> 元素中插入一段 js 脚本,插入一堆HTML元素,插入一段CSS样式等。

来,咱们用例子来说明。

public class InsertStylesTagHelper : ITagHelperComponent
{
public int Order => 105; public void Init(TagHelperContext context)
{
// 空白
} public Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
// 要判断一下是不是<head>元素
if(output.TagName.Equals("head", StringComparison.OrdinalIgnoreCase))
{
// 插入以下CSS
string css = """
<style>
h5 {
color: blue;
} h3 {
color: green;
font-style: italic;
} p[setfont] {
font-family: '楷体';
}
</style>
"""; output.PreContent.AppendHtml(css);
}
return Task.CompletedTask;
}
}

这个帮助器就是在 <head> 元素内容的前面插入一段 supper style,不,是 CSS。因为CSS是一大段文本,这里老周用到了 C# 的原义文本块(就是不转义特殊字符),这个功能和 Python 中的差不多。只是C#要求左"""后要换行,右"""前也要换行。这样规定可能是为了写起代码来好看。

这种不实现 ITagHelper 的类型不能用 @addTagHelper 指令来引入,而是要添加到 ITagHelperComponentManager 接口的 Components 属性中,此属性是个列表对象,可以Add。

ITagHelperComponentManager 类有个内部实现的类叫 TagHelperComponentManager,这个类没有对外公开,但不影响我们使用。当我们在服务容器上开启 MVC、RazorPages 等功能时,会自动向容器注册 ITagHelperComponentManager 。因此,在 Razor 文档中,咱们可以通过依赖注入获取它,然后把自己定义的 TagHelper 放进 Components 列表即可。
@page
@using Microsoft.AspNetCore.Mvc.Razor.TagHelpers
@using TestApp
@inject ITagHelperComponentManager tagHelperManager @{
// 手动添加TagHelper组件
var mytaghelper = new InsertStylesTagHelper();
// 添加到组件列表中
tagHelperManager.Components.Add(mytaghelper);
} <html>
<head>
<title>好看的例子</title>
<meta charset="UTF-8" />
</head>
<body>
<h3>三号标题</h3>
<h5>五号标题</h5>
<h2>二号标题 - 此处不应用样式</h2>
<p>其他内容 - 不应用样式</p>
<p setfont>使用楷书字体</p>
</body>
</html>

这样一弄,在运行程序后,自定义的 InsertStylesTagHelper 类就会自动应用到 head 标记上。

还没完呢,接下来咱们偷窥一下 TagHelperComponentManager 类的源代码。

internal sealed class TagHelperComponentManager : ITagHelperComponentManager
{
/// <summary>
/// Creates a new <see cref="TagHelperComponentManager"/>.
/// </summary>
/// <param name="tagHelperComponents">The collection of <see cref="ITagHelperComponent"/>s.</param>
public TagHelperComponentManager(IEnumerable<ITagHelperComponent> tagHelperComponents)
{
if (tagHelperComponents == null)
{
throw new ArgumentNullException(nameof(tagHelperComponents));
} Components = new List<ITagHelperComponent>(tagHelperComponents);
} /// <inheritdoc />
public ICollection<ITagHelperComponent> Components { get; }
}

其实这代码没啥好看的,只要注意它的构造函数就行了。不知道你看到这个构造函数想到了啥,老周想到了依赖注入。什么意思?就是说:你把实现 ITagHelperComponent 接口的类都注册为服务,那么,它就会自动起作用了,而且是面向整个应用程序的 Razor 代码。刚才咱们用依赖注入获取 ITagHelperComponentManager,并手动添加标记帮助器对象的方法是局部的,只对当前 Razor 文档有效。

所以,下面咱们把自己写的 InsertStylesTagHelper 注册为服务。

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddTransient<ITagHelperComponent, InsertStylesTagHelper>();
var app = builder.Build();

最后回到 Razor 文档,打扫一下,手动添加到 Components 列表的代码现在不需要了。

@page

<html>
<head>
<title>好看的例子</title>
<meta charset="UTF-8" />
</head>
<body>
<h3>三号标题</h3>
<h5>五号标题</h5>
<h2>二号标题 - 此处不应用样式</h2>
<p>其他内容 - 不应用样式</p>
<p setfont>使用楷书字体</p>
</body>
</html>

再次运行一下,你会发现也是可行的,<head>元素内也插入了 CSS 样式。

好了,今天咱们就聊到这儿吧。

【ASP.NET Core】标记帮助器——抽象层的更多相关文章

  1. 【asp.net core 系列】- 11 Service层的实现样板

    0.前言 在<asp.net core 系列>之实战系列中,我们在之前的篇幅中对项目有了一个大概的认知,也搭建了一个基础的项目骨架.那么就让我们继续完善这个骨架,让它更加丰满.这一篇,我将 ...

  2. ASP.NET Core利用拦截器 IActionFilter实现权限控制

    “麦荻网教系统”采用了前后端代码分离的架构,即“Miidy.Cloud.Console”站与“Miidy.Cloud.Manage”站(两个前端站)同时通过web api的方式调用“Miidy.Clo ...

  3. 重学ASP.NET Core 中的标记帮助程序

    标记帮助程序是什么 标记帮助程序使服务器端代码可以在 Razor 文件中参与创建和呈现 HTML 元素. 例如,内置的 ImageTagHelper 可以将版本号追加到图片名称.  每当图片发生变化时 ...

  4. C# -- HttpWebRequest 和 HttpWebResponse 的使用 C#编写扫雷游戏 使用IIS调试ASP.NET网站程序 WCF入门教程 ASP.Net Core开发(踩坑)指南 ASP.Net Core Razor+AdminLTE 小试牛刀 webservice创建、部署和调用 .net接收post请求并把数据转为字典格式

    C# -- HttpWebRequest 和 HttpWebResponse 的使用 C# -- HttpWebRequest 和 HttpWebResponse 的使用 结合使用HttpWebReq ...

  5. 用ASP.NET Core 2.1 建立规范的 REST API -- HATEOAS

    本文所需的一些预备知识可以看这里: http://www.cnblogs.com/cgzl/p/9010978.html 和 http://www.cnblogs.com/cgzl/p/9019314 ...

  6. ASP.Net Core开发(踩坑)指南

    ASP.NET与ASP.NET Core很类似,但它们之间存在一些细微区别以及ASP.NET Core中新增特性的使用方法,在此之前也写过一篇简单的对比文章ASP.NET MVC应用迁移到ASP.NE ...

  7. 在Mac上开发使用yeoman构建Asp.net core项目并且实现分层引用

    1.Yeoman? yeoman是一个自动化脚手架工具.它提供很多generator,generator相当于VisualStudio的模板,用来初始化项目.更多的就不多说了,写一遍都写不完,自己看吧 ...

  8. yoeman构建Asp.net core项目并且实现分层

    在Mac上开发使用yoeman构建Asp.net core项目并且实现分层引用 1.Yoeman? yoeman是一个自动化脚手架工具.它提供很多generator,generator相当于Visua ...

  9. ASP.NET CORE MVC 2.0 项目中引用第三方DLL报错的解决办法 - InvalidOperationException: Cannot find compilation library location for package

    目前在学习ASP.NET CORE MVC中,今天看到微软在ASP.NET CORE MVC 2.0中又恢复了允许开发人员引用第三方DLL程序集的功能,感到甚是高兴!于是我急忙写了个Demo想试试,我 ...

  10. ASP.NET Core gRPC 入门全家桶

    一. 说明 本全家桶现在只包含了入门级别的资料,实战资料更新中. 二.官方文档 gRPC in Asp.Net Core :官方文档 gRPC 官网:点我跳转 三.入门全家桶 正片: ASP.NET ...

随机推荐

  1. uwsgi 启动配置文件

    # uwsig使用配置文件启动 [uwsgi] # 项目目录 chdir=/myfiles/xxx/xxx/my_project # 指定项目的application module=my_projec ...

  2. C++初阶(封装+多态--整理的自认为很详细)

    继承 概念:继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类.继承呈现了面向对象程序设计的层次结构,体现了由简单 ...

  3. Referenced file contains errors (http://mybatis.org/dtd/mybatis-3-config.dtd). For more information, right click on the message in the Problems View and select "Show Details..."

    mybatis配置文件报错Referenced file contains errors mybatis的配置文件报错 The errors below were detected when vali ...

  4. tostring、(string)和 String.valueOf()

    上周遇到一个问题,只怪自己平时没注意这个细节,从数据库取数据在map集合里,取出该值是我用了.tostring的方法,一次在当取出数据为空时代码报java.lang.NullPointerExcept ...

  5. ArcGIS 通过字段计算 设置顺序编码

    地块编号="前缀" & left("0000",4-len( [FID]+1)) & ([FID] +1)

  6. day18 批量查询与模糊查询 & 聚合函数与内置函数 & distinct关键字 & 分页查询limit & 排序order

    day18 两个表student和user student id s_name s_age s_sex user id u_name u_age u_sex 批量插入 insert into stud ...

  7. 100以内能被7整除的前五个数-Java

    import java.util.HashSet; import java.util.Set; public class Demo { //100以内能够被7整除的前五个数 public static ...

  8. linux开机进入grub rescue界面修复

    一.先输入ls回车,查看显示内容 如: (hd0) (hd0,msdos1) (hd0,msdos2)... 二.然后找出哪个盘安装了系统 ls (hd0,msdos1)/boot/grub/ ls( ...

  9. 浏览器DevTools使用技巧

    我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品.我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值. 本文作者:正则 作为一名前端开发人员,平时开发中使用最多的就是 Ch ...

  10. [深度学习] ubuntu18.04配置深度学习环境笔记

    最近装过很多ubuntu18.04系统的nvidia驱动,cuda10.2,cudnn7.6.5,发现每次都会出现一些小问题.总结了具体步骤,做个记录.主要分为三个步骤:驱动安装,cuda安装,cud ...