[ASP.NET Core 2.0] Custom Tag Helper

ASP.NET Core introduces an extremely natural way to build input, label and a few other tags with the asp-for keyword; they are called tag helpers

You can also create your own tag helpers to render whatever tags you want

1. A few tag helpers

ASP.NET MVC5

ASP.NET Core

ASP.NET MVC5 ASP.NET Core
@Html.TextBoxFor() <input asp-for=""/>
@Html.DropDownListFor() <select asp-for="" asp-items=""/>
@Html.LabelFor() <label asp-for=""></label>
@Html.ValidationMessageFor() <anytag asp-validation-for=""></anytag>

2. The problem with the label tag helper

label is the tag you’ll customize quite a lot in your asp.net core app. The reason is that you want a red * on required inputs, and you also want (500 characters) when the input only allows 500 characters.

Of course, all of that can easily be added by … adding it by hand

ASP.NET MVC, and later ASP.NET CORE, lets you build a model with DataAnnotations to tell the database the limits or the definition of a column, while also validating its input field in client-side code

[Required]
[Display(Name = "User name")]
[StringLenght(15)]
public string Username { get; set; }

However, when creating the input field for this username property, you still have to add the * and the max 15 characters text by hand

<label asp-for="Username"></label>
<input asp-for="Username"/>
<span asp-validation-for="Username"></span>

the code above generates the following html

<label for="Username">User name</label>
....other lines...

3. Customize Label tag helper

We’ll create a custom tag helper named requiredlabel

3.1. Code

namespace YourNamespace.Extensions.TagHelpers
{
    using System;
    using System.IO;
    using System.Text.Encodings.Web;
    using Microsoft.AspNetCore.Mvc.Rendering;
    using Microsoft.AspNetCore.Mvc.TagHelpers;
    using Microsoft.AspNetCore.Mvc.ViewFeatures;
    using Microsoft.AspNetCore.Razor.TagHelpers;
 
    // Source code for label tag helper: Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/LabelTagHelper.cs
    // https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc.TagHelpers/LabelTagHelper.cs
 
    [HtmlTargetElement("requiredlabel", Attributes = ForAttributeName)]
    public class RequiredLabelTagHelper : TagHelper
    {
        private const string ForAttributeName = "asp-for";
 
        // Will be used as highlight-class in html
        public string HighlightClass { get; set; }
 
        /// <summary>
        /// Creates a new <see cref="LabelTagHelper"/>.
        /// </summary>
        /// <param name="generator">The <see cref="IHtmlGenerator"/>.</param>
        public RequiredLabelTagHelper(IHtmlGenerator generator)
        {
            Generator = generator;
        }
 
        /// <inheritdoc />
        public override int Order => -1000;
 
        [HtmlAttributeNotBound]
        [ViewContext]
        public ViewContext ViewContext { get; set; }
 
        protected IHtmlGenerator Generator { get; }
 
        /// <summary>
        /// An expression to be evaluated against the current model.
        /// </summary>
        [HtmlAttributeName(ForAttributeName)]
        public ModelExpression For { get; set; }
 
        /// <inheritdoc />
        /// <remarks>Does nothing if <see cref="For"/> is <c>null</c>.</remarks>
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }
 
            if (output == null)
            {
                throw new ArgumentNullException(nameof(output));
            }
 
            var requiredMarkTagBuilder = new TagBuilder("span");
            requiredMarkTagBuilder.AddCssClass(HighlightClass);
            requiredMarkTagBuilder.InnerHtml.Append(" *");
 
            var tagBuilder = Generator.GenerateLabel(
                ViewContext,
                For.ModelExplorer,
                For.Name,
                labelText: null,
                htmlAttributes: null);
 
            if (For.ModelExplorer.Metadata.IsRequired)
            {
                using (var writer = new StringWriter())
                {
                    requiredMarkTagBuilder.WriteTo(writer, HtmlEncoder.Default);
                    tagBuilder.InnerHtml.AppendHtml(writer.ToString());
                }
            }
 
            output.TagName = tagBuilder.TagName;
            output.MergeAttributes(tagBuilder);
            output.Content.SetHtmlContent(tagBuilder.InnerHtml);
        }
    }
}

3.2. Registering it

Open View/_ViewImports.cshtml

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
 
@* Add the following line *@
@addTagHelper *, YourNamespace.Extensions

This code lets your tag helper work inside razor views. With a different namespace name you have to change that code accordingly

Read more at: https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro, section Managing Tag Helper scope

4. Usage

Extremely simple

<requiredlabel asp-for="Username" highlight-class="red bold"></requiredlabel>

renders the following html

<label for="Username">User name<span class="red bold"> *</span></label>