Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Add <head> and <body> Tag Helpers #5728

Closed
DamianEdwards opened this issue Jan 27, 2017 · 7 comments
Closed

Add <head> and <body> Tag Helpers #5728

DamianEdwards opened this issue Jan 27, 2017 · 7 comments

Comments

@DamianEdwards
Copy link
Member

DamianEdwards commented Jan 27, 2017

They need to allow components in DI to add stuff to be rendered in the <head> and/or <body> elements. Maybe via TagHelperInitializers? Need to consider support for ordering too.

@Eilon
Copy link
Member

Eilon commented Mar 1, 2017

@jbagga please set up a mtg with me, @NTaylorMullen @DamianEdwards (required), and @davidfowl (optional) to figure this out.

@DamianEdwards
Copy link
Member Author

Here's a straw-man for us to start from that I just hacked together that works. The scenario at the end is the Application Insights SDK injecting the client-side analytics JavaScript without anything needing to be in _Layout.cshtml like it is today.

[HtmlTargetElement("head")]
[EditorBrowsable(EditorBrowsableState.Never)]
public class HeadTagHelper : TagHelper
{
    private readonly IList<IHeadTagHelper> _processors;

    public HeadTagHelper(IEnumerable<IHeadTagHelper> processors)
    {
        _processors = processors.OrderBy(p => p.Order).ToList();
    }

    public override void Init(TagHelperContext context)
    {
        foreach (var processor in _processors)
        {
            processor.Init(context);
        }
    }

    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        foreach (var processor in _processors)
        {
            await processor.ProcessAsync(context, output);
        }
    }
}

[HtmlTargetElement("body")]
[EditorBrowsable(EditorBrowsableState.Never)]
public class BodyTagHelper : TagHelper
{
    private readonly IList<IBodyTagHelper> _processors;

    public BodyTagHelper(IEnumerable<IBodyTagHelper> processors)
    {
        _processors = processors.OrderBy(p => p.Order).ToList();
    }

    public override void Init(TagHelperContext context)
    {
        foreach (var processor in _processors)
        {
            processor.Init(context);
        }
    }

    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        foreach (var processor in _processors)
        {
            await processor.ProcessAsync(context, output);
        }
    }
}

public interface IHeadTagHelper
{
    int Order { get; }

    void Init(TagHelperContext context);

    Task ProcessAsync(TagHelperContext context, TagHelperOutput output);
}

public interface IBodyTagHelper
{
    int Order { get; }

    void Init(TagHelperContext context);

    Task ProcessAsync(TagHelperContext context, TagHelperOutput output);
}

public class AppInsightsHeadTagHelper : IHeadTagHelper
{
    private readonly string _js;

    public AppInsightsHeadTagHelper(JavaScriptSnippet appInsightsJs)
    {
        _js = appInsightsJs.FullScript;
    }

    public int Order => 1;

    public void Init(TagHelperContext context)
    {
            
    }

    public Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        output.PostContent.AppendHtml("<script>console.log('This was injected!!');</script>");
        output.PostContent.AppendHtml(_js);

        return Task.CompletedTask;
    }
}

public static class WebHostExtensions
{
    public static IWebHostBuilder UseApplicationInsights2(this IWebHostBuilder builder) => builder
        .UseApplicationInsights()
        .ConfigureServices(services => services
            .AddSingleton<IHeadTagHelper, AppInsightsHeadTagHelper>());
}

@Eilon
Copy link
Member

Eilon commented Mar 1, 2017

Very nice. So given this code, which seems to be fine, what design questions do we actually have?

@DamianEdwards
Copy link
Member Author

We should discuss naming and enumerate some more scenarios to ensure this covers what we want. Also, what config, options, logging, etc. we might want.

@jbagga
Copy link
Contributor

jbagga commented Mar 1, 2017

cc @sebastienros

@DamianEdwards
Copy link
Member Author

Idea to make this more generic:

public interface ITagHelperComponent
{
    bool AppliesTo(TagHelperContext context);
    int Order { get; }
    void Init(TagHelperContext context);
    Task ProcessAsync(TagHelperContext context, TagHelperOutput output);
}

[HtmlTargetElement("body")]
[HtmlTargetElement("head")]
public class BodyHeadTagHelper : TagHelperComponentTagHelper
{

}

public abstract class TagHelperComponentTagHelper : TagHelper
{
    private readonly IList<ITagHelperComponent> _components;

    public TagHelperComponentTagHelper(IEnumerable<ITagHelperComponent> components)
    {
        _components = components.OrderBy(p => p.Order).ToList();
    }

    public override void Init(TagHelperContext context)
    {
        foreach (var component in _components)
        {
            if (component.AppliesTo(context))
            {
                component.Init(context);
            }
        }
    }

    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        foreach (var component in _components)
        {
            if (component.AppliesTo(context))
            {
                await component.ProcessAsync(context, output);
            }
        }
    }
}

public class AppInsightsHeadTagHelperComponent : ITagHelperComponent
{
    private readonly string _js;

    public AppInsightsHeadTagHelperComponent(JavaScriptSnippet appInsightsJs)
    {
        _js = appInsightsJs.FullScript;
    }

    public bool AppliesTo(TagHelperContext context) => string.Equals("head", context.TagName, StringComparison.OrdinalIgnoreCase);

    public int Order => 1;

    public void Init(TagHelperContext context)
    {
            
    }

    public Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        output.PostContent.AppendHtml("<script>console.log('This was injected!!');</script>");
        output.PostContent.AppendHtml(_js);

        return Task.CompletedTask;
    }
}

@davidfowl
Copy link
Member

👏

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants