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>
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:
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: