9

【ASP.NET Core】标记帮助器——抽象层 - 东邪独孤

 1 year ago
source link: https://www.cnblogs.com/tcjiaan/p/17132372.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

标记帮助器,即 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>元素。

367389-20230218174541794-574803188.png

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

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

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

367389-20230218175104687-738807569.png

嗯,看来 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 标记上。

367389-20230218185420244-999417413.png

还没完呢,接下来咱们偷窥一下 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 样式。

367389-20230218190524654-35328807.png

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK