NOTE: I have written a more updated tutorial on Tag Helpers. Check it out!

I mentioned in Welcoming the Coming Death of WebForms that one of the features I was most excited about in ASP.NET 5 was Tag Helpers. I've finally gotten some time to install VS 2015 CTP 6 and try this feature out, and I'm pretty impressed by these early examples.

Why use Tag Helpers?

Because it gets us closer to the ideal webpage that's written only in HTML and Javascript. Tag Helpers are a way of removing clunky C# code from our Razor views and making them look more like straight HTML. It also assists non-programmers (e.g. designers) who may not be familiar with the Razor HtmlHelpers in continuing to design the web pages they need to handle.

A Simple Example

Here's a sample form, written in MVC5 Razor using HtmlHelpers:

@Html.ActionLink("Back to Users", "Index", new { @class = "back-link" } )

@using(Html.BeginForm("Add", "User", FormMethod.Post), new { @class = "myform" })
{
    <div>
        @Html.AntiForgeryToken()
        @Html.ValidationSummary()
        <div>
            @Html.LabelFor(x => x.FirstName)
            @Html.TextBoxFor(x => x.FirstName, new { @class = "name_input" })
            @Html.ValidationMessageFor(x => x.FirstName)
        </div>
        <div>
            @Html.LabelFor(x => x.LastName)
            @Html.TextBoxFor(x => x.LastName, new { @class = "name_input" })
            @Html.ValidationMessageFor(x => x.LastName)
        </div>
        <div>
            @Html.LabelFor(x => x.DateOfBirth)
            @Html.EditorFor(x => x.DateOfBirth)
            @Html.ValidationMessageFor(x => x.DateOfBirth)
        </div>
        <div>
            <input type="submit" value="Go!" />
        </div>
    </div>
}

There's a surprising amount of C# code in that example, and a lot of calls to @Html. It's almost like we are approximating using HTML using C#.

Maybe instead of approximating HTML, we can make our markup look like actual HTML.

Here's that same form, written using TagHelpers:

<a asp-action="Add" class="back-link">Back to Users</a>

<form asp-controller="User" asp-action="Add" method="post" asp-anti-forgery="true">
    <div>
        <div asp-validation-summary="ValidationSummary.ModelOnly">
        <div>
        	<label asp-for="FirstName"></label>
            <input asp-for="FirstName" class="name_input" />
            <span asp-validation-for="FirstName"></span>
        </div>
        <div>
            <label asp-for="LastName"></label>
            <input asp-for="LastName" class="name_input" />
            <span asp-validation-for="LastName"></span>
        </div>
        <div>
            <label asp-for="DateOfBirth"></label>
            <input asp-for="DateOfBirth" />
            <span asp-validation-for="DateOfBirth"></span>
        </div>
        <div>
            <input type="submit" value="Go!" />
        </div>
    </div>
</form>

That looks a lot like regular HTML, and IMHO looks quite a bit nicer than the Razor/C# syntax.

What are all those "asp-" attributes? They are how Tag Helpers work; they add server-side attributes to HTML elements without needing calls to C#.

Take a look at the <form> for example. It has three non-HTML attributes:

  • asp-controller: Specifies the name of the controller
  • asp-action: Specifies the name of the action
  • asp-anti-forgery: Specifies whether this form should be using AntiForgeryTokens (which you should always use)

Also note that Tag Helpers still follow the convention-over-configuration ideal that's omnipresent in MVC: the anchor tag only needs asp-action, not asp-controller, because it assumes there's an action specified in the present controller (which is the same assumption made by some overloads of Html.ActionLink).

Those attributes don't get rendered to the page; rather, the new Razor engine processes them and creates the HTML attributes (or, in the case of asp-anti-forgery, the HTML element) specified by these psuedo-attributes.

Some of you might be wondering that this looks kind of similar to WebForms outputs, where the attributes of the <asp:> controls didn't necessarily map one-to-one with actual HTML attributes. This is not the case here, there's no control lifecycle or anything being implemented by these helpers. Tag Helpers are just a nice compact way of showing what you want that element to do server-side.

How Does This Actually Work?

What is actually happening here is that ASP.NET is taking the HTML code from the Razor view and decoding it using a set of TagHelpers (GitHub source). There is a tag helper for each kind of tag that can use these attributes, and the code for them is actually rather simple; I highly encourage you to peruse that source.

Let's take, for example, the AnchorTagHelper.
When the rendering engine encounters an anchor tag on a CSHTML view, the content of that tag is fed through AnchorTagHelper, with each attribute checked to see if it matches one of the possible server-side attributes. If any of the Tag Helper attributes are found, they are run through a converter that ultimately produces the correct HTML for the specified Attributes. Note that, in the specific case of AnchorTagHelper, an exception is thrown if both href and controller or action exist, since the anchor won't be able to decide which one to use.

Important Notes on Tag Helpers

There is some setup that you need to do in order to make Tag Helpers work. TagHelpers is a NuGet package so you'll have to install that. You will also have to specify what DLL will be handling implementing the helpers using the @addtaghelper Razor syntax (I placed this in the _ViewStart view):

@addtaghelper "Microsoft.AspNet.Mvc.TagHelpers"

Second, the "asp-" prefix is optional, and actually is only supported starting in beta 3. I'm using the prefix above because I personally like being able to immediately distinguish between attributes that invoke server-side code and regular HTML attributes, but you can use them either with or without the prefix.

All in all, I'm impressed with this idea and its execution, and am looking forward to VS 2015 and ASP.NET 5 so I can start using this in my projects.

Check out Damian Edwards' TagHelperStarterWeb project on GitHub, read Scott Hanselman's blog post, and watch the TagHelpers demo for more examples.

Happy Coding!