Input Tag Helper With Placeholder Support

Earlier, I wrote about Tag Helpers and showed how useful they are when creating HTML forms. In that post, we saw how simple it is to use Tag Helpers to generate an input element:

<input asp-for="Email"></input>

The .NET Framework code responsible for this is the InputTagHelper class. What’s missing, however, is support for the placeholder attribute. What would be really nice is if we could simply write this as:

<input asp-for="Email" asp-placeholder-for="Email"></input>

and have it automatically use the value of either the Name or Prompt properties of the Display attribute for the placeholder attribute. That allows us to keep the content in the model as localizable metadata.

Thankfully, Tag Helpers are pretty easy to write, and the ASP.NET Core docs have a pretty good tutorial. What’s particularly nice is that you can extend other tag helpers.

Let’s do that, and extend InputTagHelper to add support for an asp-placeholder-for attribute.

The code for doing that is:

using System;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;

namespace Cadru.TagHelpers
{
    [HtmlTargetElement("input", Attributes = PlaceholderAttributeName, TagStructure = TagStructure.WithoutEndTag)]
    public class InputPlaceholderTagHelper : InputTagHelper
    {
        private const string PlaceholderAttributeName = "asp-placeholder-for";

        public InputPlaceholderTagHelper(IHtmlGenerator generator) : base(generator)
        {
        }

        /// <summary>
        /// An expression to be evaluated against the current model.
        /// </summary>
        [HtmlAttributeName(PlaceholderAttributeName)]
        public ModelExpression Placeholder { get; set; }


        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            base.Process(context, output);

            var placeholder = GetPlaceholder(Placeholder.ModelExplorer);
            TagHelperAttribute placeholderAttribute;

            if (!output.Attributes.TryGetAttribute("placeholder", out placeholderAttribute))
            {
                output.Attributes.Add(new TagHelperAttribute("placeholder", placeholder));
            }
        }

        private string GetPlaceholder(ModelExplorer modelExplorer)
        {
            string placeholder;
            placeholder = modelExplorer.Metadata.Placeholder;

            if (String.IsNullOrWhiteSpace(placeholder))
            {
                placeholder = modelExplorer.Metadata.GetDisplayName();
            }

            return placeholder;
        }
    }
}

This class inherits from InputTagHelper, which it lets do most of the work. The HtmlTargetElement attribute indicates that this Tag Helper targets the input tag and that it adds an asp-placeholder-for attribute. We then associate the Placeholder property with the new HTML property using the HtmlAttributeName attribute. Finally, the Process method is where all of the magic happens. This method first lets the base InputTagHelper do its work to generate the appropriate input tag for the model proprty. Then it looks at the model metadata and retrieves the Placeholder property value, or if the placeholder is empty the display name. If the placeholder attribute has alredy been provided, then it’s value is used; otherwise, the attribute is added to the output attributes.

Now we can simply add the asp-placeholder-for attribute to our input tag and it automatically use the value of either the Name or Prompt properties of the Display attribute for the value of the placeholder attribute. If the model doesn’t include a Display attribute, the metadata will automatically return the property name, in much the same way as the LabelTagHelper.