Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple tag helpers targetting the same element

I have just noticed that if I have 2 tag helpers targeting the same element, both can be executed. The order in which they are executed depends on the order in which they are registered in _ViewImports.cshtml.

For example, I can create another tag helper for the anchor element:

[HtmlTargetElement("a", Attributes = "foo")]
public class FooTagHelper : TagHelper
{
    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        //Get the url from href attribute generated in the default AnchorTagHelper
        var url = output.Attributes["href"].Value.ToString();

        ...
    }
}

Use it as follows (notice I am also adding attributes of the default anchor helper like asp-controller):

<a class="menu" asp-controller="Home" asp-action="Index" foo>Foo</a>

If this helper is registered in _ViewImports.cshtml after the default ASP ones:

  • Whenever Process is called, the TagHelperOutput already contains the href generated by the default AnchorTagHelper. I could also update the anchor generated by the default tag helper in any way I like.

Is there any degree of control over this behavior?

You might want to decide whether or not to execute further helpers targeting the same element (As if sealing your output). You might also want to allow other helpers, but make sure some attribute wasn't modified.

like image 202
Daniel J.G. Avatar asked Oct 03 '15 12:10

Daniel J.G.


1 Answers

Override the readonly property order, like this:

[HtmlTargetElement("a", Attributes = "foo")]
public class FooTagHelper : TagHelper
{
    // This should be the last tag helper on any tag to run
    public override int Order => int.MaxValue;

    public override async Task ProcessAsync(TagHelperContext context,
        TagHelperOutput output)
    {
        //...
    }
}

Reading the source code of the TagHelperRunner class, I realized that the same TagHelperContext and TagHelperOutput will be shared for all the tag helpers found for the same element, which will be processed orderd by the ITagHelper.Order property.

So you can control the order in which they are executed by assigning appropriated values to the Order property. As a reference, this is the TagHaelperRunner.RunAsync method:

public async Task<TagHelperOutput> RunAsync([NotNull] TagHelperExecutionContext executionContext)
{
    var tagHelperContext = new TagHelperContext(
        executionContext.AllAttributes,
        executionContext.Items,
        executionContext.UniqueId,
        executionContext.GetChildContentAsync);
    var tagHelperOutput = new TagHelperOutput(
        executionContext.TagName,
        executionContext.HTMLAttributes)
    {
        SelfClosing = executionContext.SelfClosing,
    };
    var orderedTagHelpers = executionContext.TagHelpers.OrderBy(tagHelper => tagHelper.Order);

    foreach (var tagHelper in orderedTagHelpers)
    {
        await tagHelper.ProcessAsync(tagHelperContext, tagHelperOutput);
    }

    return tagHelperOutput;
}
  • The default Order if you extend the class TagHelper is 0.

  • The MVC tag helpers like AnchorTagHelper or InputTagHelper seem to have the order defined as -1000.

So far, I have also found that you can query some of the properties in TagHelperOutput to check if a previous tag helper has modified the output. Although you cannot know if a tag helper with higher order (executed after yours) modifies the output:

  • TagHelperOutput.IsContentModified will return true only when the Content is modified (Not when the attributes or the PreElement, PreContent, PostElement, PostContent are modified)

  • TagHelperOutput.PreElement.IsModified and similar for PreContent, PostElement and PostContent will return true when those have been modified.

  • Content set by a previous tag helper could be removed by calling TagHelperOutput.Content.Clear() and similar for Pre/Post Element/Context properties.

  • Content can be completely suppressed by calling TagHelperOutput.SuppressOutput() which calls clear on every of those properties and set TagName as null. If you want the tag helper to render something you will then need to assign them again.

Finally, if you had to share some data between multiple tag helpers for the same element, you can use the TagHelperContext.Items dictionary.

like image 122
Daniel J.G. Avatar answered Nov 12 '22 23:11

Daniel J.G.