Tag helper order of execution

夙愿已清 提交于 2019-12-10 22:16:34

问题


I am writing a set of ASP.Net Core tag helpers that target (among other tags) <form> and <input> tags. My <form> tag helper defines a custom attribute, the value of which it wants to pass to the child elements.

All of the articles I've read make this sound simple: The parent tag helper stores the value in the context.Items dictionary, and the children read it from that same dictionary.

This implies that the child tag helpers execute after the parent tag helper. However, I've discovered that, in the case of <form> and <input> tag helpers, the FormTagHelper executes after the InputTagHelper.

By way of example, consider this HTML:

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

My form tag helper:

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

Input tag helper:

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

I would expect valueFromParentForm to be "Hello", but in fact it throws an exception because the context.Items dictionary is empty.

What is this is all about, and what might I do to work around this weird, inside-out order of execution?


回答1:


Solution

Aside for Process() method the base tag helper provides also Init() method. It's summary:

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.

Simply override this method and add whatever you need:

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

Explanation

For your html code:

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

let's have two tag helpers:

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

To understand better in what order methods are called let's take a look at this diagram:

I think the execution order is pretty self-explanatory. But what about the red No access part? Let's starts with establishing what exactly Items dictionary is and how it works. It figures as IDictionary<object, object> but it is not a regular dictionary. It's a CopyOnWriteDictionary and it is quite special. It has two underlying dictionaries ReadDictionary and WriteDictionary and it calls either of them depending on what type of action (read/write) is currently performed.

While you can add 1 from FormTagHelper.Init(), you won't be able to access keys 2 and 3 from FormTagHelper.Process() despite the fact that according to the diagram they should already be there:

That's because values for InputTagHelper are being add to _innerDictionary not _sourceDictionary which is then used in FormTagHelper. Such behaviour creates one-way access to Items dictionary. Children tag helpers are able to access values added by parents, but not the opposite way.

State of Items dictionary after execution of Init() method of InputTagHelper():




回答2:


I just run now the following tag helpers (parent and children)

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

and It's run in the following order (And not in the previews answer's order):

  1. Parent's Init(TagHelperContext context)
  2. Parent's ProcessAsync(TagHelperContext context, TagHelperOutput output)
  3. Parent's Process(TagHelperContext context, TagHelperOutput output)
  4. Child1's Init(TagHelperContext context)
  5. Child1's ProcessAsync(TagHelperContext context, TagHelperOutput output)
  6. Child1's Process(TagHelperContext context, TagHelperOutput output)
  7. Child2's Init(TagHelperContext context)
  8. Child2's ProcessAsync(TagHelperContext context, TagHelperOutput output)
  9. Child2's Process(TagHelperContext context, TagHelperOutput output)


来源:https://stackoverflow.com/questions/56625896/tag-helper-order-of-execution

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!