Tag Helper Components in ASP.NET Core
By Scott Addie and Fiyaz Bin Hasan
A Tag Helper Component is a Tag Helper that allows you to conditionally modify or add HTML elements from server-side code. This feature is available in ASP.NET Core 2.0 or later.
ASP.NET Core includes two built-in Tag Helper Components: head
and body
. They're located in the Microsoft.AspNetCore.Mvc.Razor.TagHelpers namespace and can be used in both MVC and Razor Pages. Tag Helper Components don't require registration with the app in _ViewImports.cshtml
.
View or download sample code (how to download)
Use cases
Two common use cases of Tag Helper Components include:
The following sections describe these use cases.
Inject into HTML head element
Inside the HTML <head>
element, CSS files are commonly imported with the HTML <link>
element. The following code injects a <link>
element into the <head>
element using the head
Tag Helper Component:
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace RazorPagesSample.TagHelpers
{
public class AddressStyleTagHelperComponent : TagHelperComponent
{
private readonly string _style =
@"<link rel=""stylesheet"" href=""/css/address.css"" />";
public override int Order => 1;
public override Task ProcessAsync(TagHelperContext context,
TagHelperOutput output)
{
if (string.Equals(context.TagName, "head",
StringComparison.OrdinalIgnoreCase))
{
output.PostContent.AppendHtml(_style);
}
return Task.CompletedTask;
}
}
}
In the preceding code:
AddressStyleTagHelperComponent
implements TagHelperComponent. The abstraction:- Allows initialization of the class with a TagHelperContext.
- Enables the use of Tag Helper Components to add or modify HTML elements.
- The Order property defines the order in which the Components are rendered.
Order
is necessary when there are multiple usages of Tag Helper Components in an app. - ProcessAsync compares the execution context's TagName property value to
head
. If the comparison evaluates to true, the content of the_style
field is injected into the HTML<head>
element.
Inject into HTML body element
The body
Tag Helper Component can inject a <script>
element into the <body>
element. The following code demonstrates this technique:
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace RazorPagesSample.TagHelpers
{
public class AddressScriptTagHelperComponent : TagHelperComponent
{
public override int Order => 2;
public override async Task ProcessAsync(TagHelperContext context,
TagHelperOutput output)
{
if (string.Equals(context.TagName, "body",
StringComparison.OrdinalIgnoreCase))
{
var script = await File.ReadAllTextAsync(
"TagHelpers/Templates/AddressToolTipScript.html");
output.PostContent.AppendHtml(script);
}
}
}
}
A separate HTML file is used to store the <script>
element. The HTML file makes the code cleaner and more maintainable. The preceding code reads the contents of TagHelpers/Templates/AddressToolTipScript.html
and appends it with the Tag Helper output. The AddressToolTipScript.html
file includes the following markup:
<script>
$("address[printable]").hover(function() {
$(this).attr({
"data-toggle": "tooltip",
"data-placement": "right",
"title": "Home of Microsoft!"
});
});
</script>
The preceding code binds a Bootstrap tooltip widget to any <address>
element that includes a printable
attribute. The effect is visible when a mouse pointer hovers over the element.
Register a Component
A Tag Helper Component must be added to the app's Tag Helper Components collection. There are three ways to add to the collection:
- Registration via services container
- Registration via Razor file
- Registration via Page Model or controller
Registration via services container
If the Tag Helper Component class isn't managed with ITagHelperComponentManager, it must be registered with the dependency injection (DI) system. The following Startup.ConfigureServices
code registers the AddressStyleTagHelperComponent
and AddressScriptTagHelperComponent
classes with a transient lifetime:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddTransient<ITagHelperComponent,
AddressScriptTagHelperComponent>();
services.AddTransient<ITagHelperComponent,
AddressStyleTagHelperComponent>();
}
Registration via Razor file
If the Tag Helper Component isn't registered with DI, it can be registered from a Razor Pages page or an MVC view. This technique is used for controlling the injected markup and the component execution order from a Razor file.
ITagHelperComponentManager
is used to add Tag Helper Components or remove them from the app. The following code demonstrates this technique with AddressTagHelperComponent
:
@using RazorPagesSample.TagHelpers;
@using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
@inject ITagHelperComponentManager manager;
@{
string markup;
if (Model.IsWeekend)
{
markup = "<em class='text-warning'>Office closed today!</em>";
}
else
{
markup = "<em class='text-info'>Office open today!</em>";
}
manager.Components.Add(new AddressTagHelperComponent(markup, 1));
}
In the preceding code:
- The
@inject
directive provides an instance ofITagHelperComponentManager
. The instance is assigned to a variable namedmanager
for access downstream in the Razor file. - An instance of
AddressTagHelperComponent
is added to the app's Tag Helper Components collection.
AddressTagHelperComponent
is modified to accommodate a constructor that accepts the markup
and order
parameters:
private readonly string _markup;
public override int Order { get; }
public AddressTagHelperComponent(string markup = "", int order = 1)
{
_markup = markup;
Order = order;
}
The provided markup
parameter is used in ProcessAsync
as follows:
public override async Task ProcessAsync(TagHelperContext context,
TagHelperOutput output)
{
if (string.Equals(context.TagName, "address",
StringComparison.OrdinalIgnoreCase) &&
output.Attributes.ContainsName("printable"))
{
TagHelperContent childContent = await output.GetChildContentAsync();
string content = childContent.GetContent();
output.Content.SetHtmlContent(
$"<div>{content}<br>{_markup}</div>{_printableButton}");
}
}
Registration via Page Model or controller
If the Tag Helper Component isn't registered with DI, it can be registered from a Razor Pages page model or an MVC controller. This technique is useful for separating C# logic from Razor files.
Constructor injection is used to access an instance of ITagHelperComponentManager
. The Tag Helper Component is added to the instance's Tag Helper Components collection. The following Razor Pages page model demonstrates this technique with AddressTagHelperComponent
:
using System;
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesSample.TagHelpers;
public class IndexModel : PageModel
{
private readonly ITagHelperComponentManager _tagHelperComponentManager;
public bool IsWeekend
{
get
{
var dayOfWeek = DateTime.Now.DayOfWeek;
return dayOfWeek == DayOfWeek.Saturday ||
dayOfWeek == DayOfWeek.Sunday;
}
}
public IndexModel(ITagHelperComponentManager tagHelperComponentManager)
{
_tagHelperComponentManager = tagHelperComponentManager;
}
public void OnGet()
{
string markup;
if (IsWeekend)
{
markup = "<em class='text-warning'>Office closed today!</em>";
}
else
{
markup = "<em class='text-info'>Office open today!</em>";
}
_tagHelperComponentManager.Components.Add(
new AddressTagHelperComponent(markup, 1));
}
}
In the preceding code:
- Constructor injection is used to access an instance of
ITagHelperComponentManager
. - An instance of
AddressTagHelperComponent
is added to the app's Tag Helper Components collection.
Create a Component
To create a custom Tag Helper Component:
- Create a public class deriving from TagHelperComponentTagHelper.
- Apply an
[HtmlTargetElement]
attribute to the class. Specify the name of the target HTML element. - Optional: Apply an
[EditorBrowsable(EditorBrowsableState.Never)]
attribute to the class to suppress the type's display in IntelliSense.
The following code creates a custom Tag Helper Component that targets the <address>
HTML element:
using System.ComponentModel;
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Logging;
namespace RazorPagesSample.TagHelpers
{
[HtmlTargetElement("address")]
[EditorBrowsable(EditorBrowsableState.Never)]
public class AddressTagHelperComponentTagHelper : TagHelperComponentTagHelper
{
public AddressTagHelperComponentTagHelper(
ITagHelperComponentManager componentManager,
ILoggerFactory loggerFactory) : base(componentManager, loggerFactory)
{
}
}
}
Use the custom address
Tag Helper Component to inject HTML markup as follows:
public class AddressTagHelperComponent : TagHelperComponent
{
private readonly string _printableButton =
"<button type='button' class='btn btn-info' onclick=\"window.open(" +
"'https://binged.it/2AXRRYw')\">" +
"<span class='glyphicon glyphicon-road' aria-hidden='true'></span>" +
"</button>";
public override int Order => 3;
public override async Task ProcessAsync(TagHelperContext context,
TagHelperOutput output)
{
if (string.Equals(context.TagName, "address",
StringComparison.OrdinalIgnoreCase) &&
output.Attributes.ContainsName("printable"))
{
var content = await output.GetChildContentAsync();
output.Content.SetHtmlContent(
$"<div>{content.GetContent()}</div>{_printableButton}");
}
}
}
The preceding ProcessAsync
method injects the HTML provided to SetHtmlContent into the matching <address>
element. The injection occurs when:
- The execution context's
TagName
property value equalsaddress
. - The corresponding
<address>
element has aprintable
attribute.
For example, the if
statement evaluates to true when processing the following <address>
element:
<address printable>
One Microsoft Way<br />
Redmond, WA 98052-6399<br />
<abbr title="Phone">P:</abbr>
425.555.0100
</address>
Additional resources
ASP.NET Core
Feedback
Submit and view feedback for