ASP.NET Core: Detect whether a tag helper is rendered with a parent element

186 Views Asked by At

In ASP.NET Core is it possible to detect whether a tag helper is rendered with a parent element?

The reason I ask is that I have a script tag helper which caches the HTML it would normally render and instead I use middleware to render the HTML before the closing body tag. However if the tag helper is called within a partial view then I'd like to simply render it in place.

My first thoughts was to add a body tag helper and then add a variable to the request cache, which I could detect within my script tag helper. However I found that since the body tag helper was within my layout page, it was rendered after my view (which executes the script tag helper).

Another solution I thought was to store a variable within the action, but I don't like this as I have to remember to do this every time I return a partial view.

I'd appreciate any help.

Edit: Here's some code for reference.

ScriptTagHelper:

public class ScriptTagHelper : TagHelper {
    private readonly IHtmlManager _htmlManager;

    public ScriptTagHelper(IHtmlManager htmlManager) {
        _htmlManager = htmlManager;
    }

    public string? At { get; set; }

    [HtmlAttributeNotBound, ViewContext]
    public ViewContext ViewContext { get; set; } = default!;

    public override void Process(TagHelperContext context, TagHelperOutput output) {
        if (!string.IsNullOrEmpty(At)) {
            output.SuppressOutput();

            var builder = new TagBuilder("script") {
                TagRenderMode = TagRenderMode.Normal
            };

            foreach (var attribute in output.Attributes) {
                builder.Attributes.Add(attribute.Name, attribute.Value.ToString());
            }

            _htmlManager.RegisterHtml(At, builder);
        }
    }
}

Here's my middleware:

public class RenderHtmlMiddleware {
    private readonly RequestDelegate _next;

    public RenderHtmlMiddleware(RequestDelegate next) {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext httpContext, IHtmlManager htmlManager) {
        var originBody = httpContext.Response.Body;

        using var body = new MemoryStream();
        httpContext.Response.Body = body;

        try {
            await _next(httpContext);
        } finally {
            httpContext.Response.Body = originBody;
        }

        if (body.Length > 0) {
            var content = Encoding.UTF8.GetString(body.ToArray());

            foreach (var position in Enum.GetValues<HtmlPosition>()) {
                var html = await htmlManager.GetHtmlAsync(position);
                var innerHtml = string.Join("", html.Select(h => h.ToHtmlString()));

                ... Code removed for brevity which handles different positions
                
                content = content.Replace("</body>", string.Concat(innerHtml, Environment.NewLine, "</body>"));
            }

            httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(content);

            await httpContext.Response.WriteAsync(content);
        }
    }
}

Now within my view/partial view I can simply say:

<script at="@HtmlPosition.BodyPostContent" src="foo"></script>
1

There are 1 best solutions below

6
Ruikai Feng On BEST ANSWER

However if the tag helper is called within a partial view then I'd like to simply render it in place.

If you just want to check if it was rendered in a partialview:

you may add a property:

[HtmlAttributeNotBound]
[ViewContext]
public ViewContext ViewContext { get; set; }

You could access HttpContext/current view name with ViewContext

a minimal example:

[HtmlTargetElement("MyTag")]
    public class MyTag : TagHelper
    {

        [HtmlAttributeNotBound]
        [ViewContext]
        public ViewContext ViewContext { get; set; }
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            output.TagName = "label";
            //check if is partial
            var path = ViewContext.View.Path;
            var ispartial = path.Contains("Partial");
           
            if (!ispartial) 
            {
                output.SuppressOutput();
            }
            
            
        }
    }

In Home View:

<partial name="MyPartial"/>
<MyTag>Home</MyTag>

In Partial View:

<MyTag>Partial</MyTag>

Result:

enter image description here

only <label>partial</label> was rendered

Update: A possible solution for template:

public class MyPartial : PartialTagHelper
    {
        public MyPartial(ICompositeViewEngine viewEngine, IViewBufferScope viewBufferScope) : base(viewEngine, viewBufferScope)
        {
        }
        
        
        public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) 
        {
            this.ViewContext.HttpContext.Items.Add("IsPartial", true);
            await base.ProcessAsync(context, output);
            this.ViewContext.HttpContext.Items.Remove("IsPartial");
        }
    }


[HtmlTargetElement("MyTag")]
    public class MyTag : TagHelper
    {

        [HtmlAttributeNotBound]
        [ViewContext]
        public ViewContext ViewContext { get; set; }
        

        public ViewLocationExpanderContext ViewLocationExpanderContext { get; set; }
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            output.TagName = "label";

            var ispartial = ViewContext.HttpContext.Items.TryGetValue("IsPartial",out var partial);          

            //access httpcontext            
           
            if (!ispartial) 
            {
                output.SuppressOutput();
            }
            
            
            
        }
    }

In View:

@model MyEntity
<my-partial name="MyPartial"model="@Model"/>

@Html.DisplayFor(x=>x.SomeEntity)

In partial:

@model MyEntity


@Html.DisplayFor(x=>x.SomeEntity)

Template:

@model SomeEntity

<dl>
    <dd>Id:@Model.Id</dd>
    <dd>Amount:@Model.Amount</dd>
    <dd>Rate:@Model.Rate</dd>    
</dl>
<MyTag>InPartial1</MyTag>
<MyTag>InPartial2</MyTag>

Even if taghelper was call in template,Only that in partial view was rendered: enter image description here