【问题标题】:Tag helper order of execution标签助手执行顺序
【发布时间】:2019-06-17 06:40:41
【问题描述】:

我正在编写一组针对<form><input> 标记(以及其他标记)的 ASP.Net Core 标记助手。我的<form> 标签助手定义了一个自定义属性,它想要传递给子元素的值。

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

这意味着子标签助手在父标签助手之后执行。但是,我发现在<form><input> 标签助手的情况下,FormTagHelper InputTagHelper之后执行。

例如,考虑以下 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 字典是空的。

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

【问题讨论】:

    标签: asp.net-core asp.net-core-tag-helpers


    【解决方案1】:

    解决方案

    除了Process() 方法,基本标签助手还提供Init() 方法。总结:

    用给定的上下文初始化Microsoft.AspNetCore.Razor.TagHelpers.ITagHelperMicrosoft.AspNetCore.Razor.TagHelpers.TagHelperContext.Items 的添加应在此方法中完成,以确保在执行子代之前添加它们。

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

    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");
        }
    }
    

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

    我认为执行顺序是不言自明的。但是红色的No access 部分呢?让我们从确定Items 字典到底是什么以及它是如何工作的开始。它的数字为IDictionary&lt;object, object&gt;,但它不是普通字典。这是CopyOnWriteDictionary,非常特别。它有两个底层字典 ReadDictionaryWriteDictionary,它会根据当前执行的操作类型(读/写)调用它们中的任何一个。

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

    这是因为 InputTagHelper 的值被添加到 _innerDictionary 而不是 _sourceDictionary,然后在 FormTagHelper 中使用。这种行为创建了对Items 字典的单向访问。子标签助手可以访问父母添加的值,但不能相反。

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

    【讨论】:

    • 在很多层面上,ms-docs 中缺失的部分 - 感谢@Prolog
    【解决方案2】:

    我现在刚刚运行以下标签助手(父母和孩子)

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

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

    1. 父级初始化(TagHelperContext 上下文)
    2. 父母的 ProcessAsync(TagHelperContext 上下文,TagHelperOutput 输出)
    3. 父进程(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 输出)

    【讨论】:

    • 我明白为什么会这样了!
    • 只有当我调用 await output.GetChildContentAsync();在父 processAsync 方法中,然后等待子 Init 在父进程方法之前运行
    猜你喜欢
    • 1970-01-01
    • 2017-05-10
    • 1970-01-01
    • 2019-11-07
    • 1970-01-01
    • 2013-09-05
    • 1970-01-01
    • 2011-07-21
    • 1970-01-01
    相关资源
    最近更新 更多