标签助手的执行顺序

Tag helper order of execution

我正在编写一组 ASP.Net 核心标签助手,目标是(在其他标签中)<form><input> 标签。我的 <form> 标签助手定义了一个自定义属性,它希望将其值传递给子元素。

我读过的所有文章都让这听起来很简单:父标签助手将值存储在 context.Items 词典中,子级从同一个词典中读取它。

这意味着 子标签助手在父标签助手之后执行。但是,我发现,在 <form> 和 [=14 的情况下=] 标签助手,FormTagHelperInputTagHelper.

之后执行

例如,考虑这个 HTML:

<form my-attr='Hello'>
  <input asp-for='SomeProperty' />
</form>

我的表单标签助手:

public class FormTagHelper : TagHelper
{
    public string MyAttr { get; set; }
    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        Debug.WriteLine("<form>");
        context.Items.Add("my-attr", MyAttr ?? "");
    }
}

输入标签助手:

public class InputTagHelper : TagHelper
{
    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        Debug.WriteLine("<input>");
        var valueFromParentForm = context.Items["my-attr"].ToString();
    }
}

我希望 valueFromParentForm"Hello",但实际上它抛出异常,因为 context.Items 字典是空的。

这是怎么回事,我可以做些什么来解决这个奇怪的、由内而外的执行顺序?

解决方案

留给 Process() method the base tag helper provides also Init() 方法。摘要:

Initializes the Microsoft.AspNetCore.Razor.TagHelpers.ITagHelper with the given context. Additions to Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext.Items should be done within this method to ensure they're added prior to executing the children.

只需覆盖此方法并添加您需要的任何内容:

public override void Init(TagHelperContext context)
{
    context.Items.Add(1, "Init FormTagHelper");
}

说明

对于您的 html 代码:

<form my-attr='Hello'>
  <input asp-for='SomeProperty' />
</form>

让我们有两个标签助手:

FormTagHelper

[HtmlTargetElement("form")]
public class FormTagHelper : TagHelper
{
    public override void Init(TagHelperContext context)
    {
        context.Items.Add(1, "Init FormTagHelper");
    }

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        context.Items.Add(4, "Process FormTagHelper");
    }
}

InputTagHelper

[HtmlTargetElement("input")]
public class InputTagHelper : TagHelper
{
    public override void Init(TagHelperContext context)
    {
        context.Items.Add(2, "Init InputTagHelper");
    }

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        context.Items.Add(3, "Process InputTagHelper");
    }
}

为了更好地理解方法的调用顺序,让我们看一下这张图:

我觉得执行顺序很好self-explanatory。但是红色 No access 部分呢?让我们从确定 Items 字典到底是什么以及它如何工作开始。它显示为 IDictionary<object, object> 但它不是常规词典。这是一个CopyOnWriteDictionary,而且很特别。它有两个基础词典 ReadDictionaryWriteDictionary,它会根据当前执行的操作类型 (read/write) 调用其中一个。

虽然您可以从 FormTagHelper.Init() 添加 1,但您将无法从 FormTagHelper.Process() 访问密钥 23,尽管事实上根据图表,他们应该已经在那里了:

那是因为 InputTagHelper 的值被添加到 _innerDictionary,而不是 _sourceDictionary,然后在 FormTagHelper 中使用。这种行为创建 one-way 访问 Items 字典。子标记助手能够访问父添加的值,但不能以相反的方式访问。

执行 InputTagHelper()Init() 方法后 Items 字典的状态:

我现在 运行 以下标签助手(parent 和 children)

<sp-row>
  <sp-col>Child 1</sp-col>
  <sp-col>Child 2</sp-col>
</sp-row>

并且它是 运行 以下顺序(而不是预览答案的顺序):

  1. Parent 的 Init(TagHelperContext 上下文)
  2. Parent 的 ProcessAsync(TagHelperContext 上下文,TagHelperOutput 输出)
  3. Parent的进程(TagHelperContext上下文,TagHelperOutput输出)
  4. Child1 的初始化(TagHelperContext 上下文)
  5. Child1 的 ProcessAsync(TagHelperContext 上下文,TagHelperOutput 输出)
  6. Child1 的进程(TagHelperContext 上下文,TagHelperOutput 输出)
  7. Child2 的初始化(TagHelperContext 上下文)
  8. Child2 的 ProcessAsync(TagHelperContext 上下文,TagHelperOutput 输出)
  9. Child2 的进程(TagHelperContext 上下文,TagHelperOutput 输出)