Extend Built In Label Tag Helper In ASP.NET Core Application

ASP.NET Core, Tag Helpers

0 ( 0 votes) | 628

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 helpers is one of the new feature 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:

  1. https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro
  2. https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/authoring
  3. https://docs.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms

Now let`s move on to actual problem. Consider the following scenario:

  1. We have a form with an input field for entering email which is mapped to an Email property on view model.
  2. This Email property is marked as required using Required data annotation attribute.
  3. 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):



Generated HTML output is as shown below:



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:



Generated HTML output is as shown below:



Now we are able to cover all the points mentioned in our requirement list. But there are multiple problems with above solution:

  1. We are losing all the goodness of built in label tag helper.
  2. If we remove the required attribute from our Email property, then we need to manually remove the asterisk sign. This can be tedious if we need to do this in multiple places. 

To understand the first problem in more detail, let`s look at the source code for the label tag helper. Source code for all the built in tag helpers can be found at https://github.com/aspnet/Mvc/tree/dev/src/Microsoft.AspNetCore.Mvc.TagHelpers.

Code for label tag helper is as shown below (To keep the focus on main method code snippet is kept small):

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:
                    // 
                    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);
                    }
               }
      }
}

(Source code for label tag helper: https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc.TagHelpers/LabelTagHelper.cs )ProcessAsync is the method that gets executed to render label element. In above source code, output.GetChildContentAsync() method will get content added between opening and closing label HTML element which is Email* and will render it as it is. Now if we use DisplayAttribute for our Email field and change the display name to Email Address, then this change will not get reflected in view. Because as per the above source code, it will try to render the content between opening and closing label tag as it is and will ignore the DisplayAttribute. To solve above problem, let`s create a new tag helper which will extend the built in label tag helper and also display an asterisk sign if the property is marked as required using Required data annotation attribute.

Code for the new custom tag helper is as shown below.

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);
            }
        }
    }
}

 Let`s go through the code:

  1. HtmlTargetElement attribute specifies that our new tag helper will only execute for HTML label element.
  2. Attributes field specifies that this tag helper will only execute for label element having asp-for attribute.
  3. It is extending from built in LabelTagHelper which is in Microsoft.AspNetCore.Razor.TagHelpers assembly instead of standard TagHelper class.
  4. 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 constructor of our base class. Instance of IHtmlGenerator will be provided by built in IOC container while execution.
  5. 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.
  6. Then using the For property of 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 following line in _ViewImports.cshtml file to enble our custom tag helper.

@addTagHelper *,WebApplication1

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


Generated HTML label output:

As we can see, the Razor markup for label using tag helper is still the same, but still we are able to extend the built in LabelTagHelper and modify the output. All conditions in our requirement list are satisfied. Source code for above custom tag helper is available on GitHub

Conclusion:

In this article, we talked about how to extend the built in LabelTagHelper. I hoped you enjoyed reading the article.

Happy Coding!

Manoj Kulkarni - Dotnetcontext

Manoj Kulkarni

I am programmer, passionate blogger and foodie. I live in Nashik. I am a .Net developer. I like to learn new technologies to keep my self-updated and share that knowledge with my blog readers, friends and colleague.

Add a new comment