In this article, we will talk about how to extend built-in label tag helper in ASP.NET Core application.
What Are Tag Helpers?
Tag helper is one of the new features introduced in ASP.NET Core which allows us to add server side code while creating and rendering HTML elements. They are similar to HTML helpers in ASP.NET MVC. ASP.NET Core comes with various built in tag helpers for rendering HTML elements like label, input, img, select etc. Follow the below links to get more information about Tag Helpers.
- https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro
- https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/authoring
- https://docs.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms
Now, let`s move on to the actual problem. Consider the following scenario -
- We have a form with an input field for entering email which is mapped to an Email property on the View Model.
- This Email property is marked as required using Required data annotation attribute.
- While displaying this email input field we want to display an asterisk sign next to label Email so, that user will know that this field is required.
We can use built in label and input tag helper to display the email input field with label Email as shown below ( I am skipping the other HTML elements like form, button to keep the focus on one element),
- <label asp-for="Email"></label>
- <input asp-for="Email"/>
- <label for="Email">Email</label>
- <input type="text" id="Email" name="Email" value="">
We were able to display a label and an input field for Email property of view model. But according to our requirements we need to display an asterisk sign next to label Email to specify that the field is required. So in order to display an asterisk sign we can try out the following solution,
- <label asp-for="Email">Email<sup>*</sup></label>
- <input asp-for="Email"/>
- <label for="Email">Email*</label>
- <input type="text" id="Email" name="Email" value="">
- /// <inheritdoc />
- /// <remarks>Does nothing if <see cref="For"/> is <c>null</c>.</remarks>
- public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
- {
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
- if (output == null)
- {
- throw new ArgumentNullException(nameof(output));
- }
- var tagBuilder = Generator.GenerateLabel(
- ViewContext,
- For.ModelExplorer,
- For.Name,
- labelText: null,
- htmlAttributes: null);
- if (tagBuilder != null)
- {
- output.MergeAttributes(tagBuilder);
- // Do not update the content if another tag helper targeting this element has already done so.
- if (!output.IsContentModified)
- {
- // We check for whitespace to detect scenarios such as:
- // <label for="Name">
- // </label>
- var childContent = await output.GetChildContentAsync();
- if (childContent.IsEmptyOrWhiteSpace)
- {
- // Provide default label text (if any) since there was nothing useful in the Razor source.
- if (tagBuilder.HasInnerHtml)
- {
- output.Content.SetHtmlContent(tagBuilder.InnerHtml);
- }
- }
- else
- {
- output.Content.SetHtmlContent(childContent);
- }
- }
- }
- }
- using Microsoft.AspNetCore.Mvc.Rendering;
- using Microsoft.AspNetCore.Mvc.TagHelpers;
- using Microsoft.AspNetCore.Mvc.ViewFeatures;
- using Microsoft.AspNetCore.Razor.TagHelpers;
- using System.Threading.Tasks;
- namespace WebApplication1.Models
- {
- [HtmlTargetElement("label",Attributes =ForAttributeName)]
- public class LabelRequiredTagHelper: LabelTagHelper
- {
- private const string ForAttributeName = "asp-for";
- public LabelRequiredTagHelper(IHtmlGenerator generator) : base(generator)
- {
- }
- public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
- {
- await base.ProcessAsync(context, output);
- if (For.Metadata.IsRequired)
- {
- var sup = new TagBuilder("sup");
- sup.InnerHtml.Append("*");
- output.Content.AppendHtml(sup);
- }
- }
- }
- }
- HtmlTargetElement attribute specifies that our new tag helper will only execute for HTML label element.
- Attributes field specifies that this tag helper will only execute for label element having asp-for attribute.
- It is extending from built in LabelTagHelper which is in Microsoft.AspNetCore.Razor.TagHelpers assembly instead of standard TagHelper class.
- LabelTagHelper class from Microsoft.AspNetCore.Razor.TagHelpers assembly has only one constructor with one parameter of type IHtmlGenerator. So in order to construct an object of LabelTagHelper, we need to pass an instance of IHtmlGenerator. To resolve this issue, we have added a constructor in our tag helper, which will accept an instance of IHtmlGenerator and then pass that instance using base keyword to the constructor of our base class. An instance of IHtmlGenerator will be provided by built in IOC container while execution.
- In ProcessAsync method, we are calling the ProcessAsync method of our base class LabelTagHelper which will provide all the features provided by built in LabelTagHelper.
- Then, using the "For" property of the base class (LabelTagHelper), we can check that whether the model property is a required property or not. If it is required, then we are adding an asterisk sign else output from LabelTagHelper is rendered.One last step, we need to add the following line in _ViewImports.cshtml file to enable our custom tag helper.
@addTagHelper *,WebApplication1.
In the above statement, WebApplication1 is the assembly name which contains our custom tag helper and * means include all tag helpers in this assembly. - With this new tag helper output is as shown below:
Razor view markup:<!-- Razor Syntax is still the same --><label asp-for="Email"></label>Generated HTML label output:<label asp-for="Email">Email*</label>
As we can see, the Razor markup for label using tag helper is still the same, but, we are able to extend the built in LabelTagHelper and modify the output. All conditions described in our requirement list are satisfied.
In this article, we talked about how to extend the built in LabelTagHelper. I hope you enjoyed reading the article. Happy Coding!

Join the conversation! Your thoughts help the community grow.