【ASP.NET Core】标记帮助器——抽象层

asp,net,core,标记,帮助,抽象 · 浏览次数 : 277

小编点评

**接口说明:** * `ITagHelperComponent`:用于标记 HTML 元素的抽象接口。 * `TagHelper`:一个抽象类,实现 `ITagHelperComponent` 接口。它负责处理标记元素的类型、顺序和属性。 **`InsertStylesTagHelper` 的用途:** `InsertStylesTagHelper` 是一个实现 `ITagHelperComponent` 接口的类,用于插入 HTML 样式到 HTML 文档中。由于 `ITagHelperComponent` 接口是一个抽象接口,`InsertStylesTagHelper` 的实现类必须通过依赖注入注入 `ITagHelperComponent` 对象。 **`TagHelperComponentManager` 的构造函数:** `TagHelperComponentManager` 是一个内部类,用于管理 `ITagHelperComponent` 对象。它创建一个 `List` 并向 Razor 中注册 `ITagHelperComponent` 对象。当 Razor 动态解析 HTML 文档时,`TagHelperComponentManager` 会自动添加标记帮助器的实例。 **注册 `InsertStylesTagHelper`:** `InsertStylesTagHelper` 是一个可依赖注入的类型。在 Razor 文档中,可以使用 `AddRazorPage` 或 `AddRazorPages` 方法注册它。这会将 `InsertStylesTagHelper` 注册为 Razor 中的全局标记帮助器。

正文

标记帮助器,即 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】标记帮助器——抽象层相似的内容:

【ASP.NET Core】标记帮助器——抽象层

标记帮助器,即 Tag Helpers。这个嘛,就直接翻译了,叫“标记帮助器”,虽然不好听,但只能这样了。当然你翻译为“标记增强器”也行。 所谓标记帮助器,就是针对 HTML 标签(不管是标准的还是自己命名的)进行扩展的做法。它是以 Razor 为基础的,服务于开发人员的。在服务器端用 C# 代码来

【ASP.NET Core】标记帮助器——元素筛选

前一篇中老周从标记帮助的底层介绍关键性的接口,如 ITagHelper ,它是一个标志,用于识别哪些类属于 Tag Helper。 标记帮助器毕竟是针对 HTML 标记的,所以得筛选。说白了就是我写的这个帮助器在哪些 HTML 标记上起作用。这就需要拿出一个特性类。 [AttributeUsage(

【ASP.NET Core】标记帮助器——替换元素名称

标记帮助器不仅可以给目标元素(标记)插入(或修改)属性,插入自定义的HTML内容,在某些需求中还可以替换原来标记的名称。 比如我们在使用 Blazor 时很熟悉的 Component 标记帮助器。在 Razor 文档中你将使用 元素来设置要呈现的组件。而在实际处理时,会去掉

ASP.NET Core 8 预览版 4的重大更新

最新版本的 .NET 8 预览版 4 对 ASP.NET Core 进行了重大改进。值得注意的增强功能包括 Blazor 的流式呈现和表单处理、在最小 API 中扩展对表单绑定的支持、用于提高性能的NativeAOT 编译、使用标识 API 终结点增强的身份验证和授权,以及添加用于应用程序监视的指标

[翻译].NET 8 的原生AOT及高性能Web开发中的应用[附性能测试结果]

随着 .NET 8 的发布,微软迈出了重要一步,为 ASP.NET Core 引入了原生的 Ahead-of-Time (AOT) 编译。这一进步不仅提高了应用程序的性能,还简化了开发过程,标志着 .NET 生态系统进入了新的时代。

NET9 AspnetCore将整合OpenAPI的文档生成功能而无需三方库

OpenAPI 规范是用于描述 HTTP API 的标准。该标准允许开发人员定义 API 的形状,这些 API 可以插入到客户端生成器、服务器生成器、测试工具、文档等中。尽管该标准具有普遍性和普遍性,但 ASP.NET Core 在框架内默认不提供对 OpenAPI 的支持。 当前 ASP.NET

ASP.NET Core中创建中间件的几种方式

前言 今天我们一起来盘点一下在ASP.NET Core应用程序中添加和创建中间件常见的四种方式。 中间件介绍 ASP.NET Core中间件(Middleware)是用于处理HTTP请求和响应的组件,它们被安排在请求处理管道中,并按顺序执行。中间件的设计是为了使其在请求处理管道中能够以灵活和可扩展的

推荐十个优秀的ASP.NET Core第三方中间件,你用过几个?

ASP.NET Core 作为一个强大的、跨平台的、高性能的开源框架,为开发者提供了丰富的功能和灵活的扩展性。其中,中间件(Middleware)是 ASP.NET Core 架构中的核心组件之一,它负责处理 HTTP 请求和响应的管道,允许开发者在请求和响应之间插入自定义逻辑。随着 ASP.NET

Asp-Net-Core开发笔记:使用原生的接口限流功能

前言 之前介绍过使用 AspNetCoreRateLimit 组件来实现接口限流 从 .Net7 开始,AspNetCore 开始内置限流组件,当时我们的项目还在 .Net6 所以只能用第三方的 现在都升级到 .Net8 了,当然是得来试试这个原生组件 体验后:配置使用都比较简单,不过功能也没有 A

ASP.NET Core如何禁用模型验证(或者从模型状态中移除某些属性)?

这是一篇4年前的文章:【经验分享】在ASP.NET Core中,如果禁用某个请求的模型验证? 事隔多年,又有网友问到这个问题。我就来重新整理一下,顺便扩展一下之前的解决办法。 这是一个来自网友【David】的提问。在 AppBoxCore 项目的新增用户页面,新增一个上传按钮: