Tag Helpers 101
Tags: beginners, webdev, aspnetcore
Published: 2019-03-02 13:41:52

---
title: Tag Helpers 101
published: true
description: <tag-helpers are="kinda awesome">
tags: beginner, webdev, aspnetcore
---

For a big web project of mine, I recently changed from the full .NET Framework to .NET Core. One of the many new tools in your arsenal when doing this is [Tag Helpers](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro?view=aspnetcore-2.2).

This post will cover the basics of using a Tag Helper and making your own. It does expect you to have had a basic look over ASP.NET, particularly with forms.

The examples below will be based on the following model:

```csharp
class PersonViewModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
```

## Background

Prior to Tag Helpers, something like a form would more usually look like this in a Razor template:

```html
@using (Html.BeginForm("Edit"))
{
	@Html.AntiForgeryToken()

	<div class="field">
		@Html.LabelFor(m => m.FirstName)
		<div class="input-container">
			@Html.TextBoxFor(m => m.FirstName)
		</div>
		@Html.ValidationMessageFor(m => m.FirstName)
	</div>
	<div class="field">
		@Html.LabelFor(m => m.LastName)
		<div class="input-container">
			@Html.TextBoxFor(m => m.LastName)
		</div>
		@Html.ValidationMessageFor(m => m.LastName)
	</div>
	<div class="field">
		<button>Continue</button>
	</div>
}
```

Things that would make it better like `@helper` aren't ([currently](https://github.com/aspnet/AspNetCore/issues/5110)) in .NET Core. You could use [partial views](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/partial?view=aspnetcore-2.2) but here that isn't really a great option. You could look at [View Components](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components?view=aspnetcore-2.2) but it really isn't the right tool here.

Let's look at how Tag Helpers might make this a bit easier to read and avoid some of the weird quirks with syntax highlighting and bracing that normally plague Razor templates.

## Basic Tag Helper

Let's look at an `input` tag helper for our `FirstName` property.

```html
<input asp-for="FirstName" />
```

Yep, that's it - we are done. We have a Tag Helper binding the `input` tag to our `FirstName` property.

*Thanks for reading! Like, subscribe and comment!*

No seriously, that is all the `InputTagHelper` actually is set to require. By specifying `asp-for`, the normal HTML element actually becomes a processed tag on the server by the `InputTagHelper`.

You will know it is applied as the tag colours in Visual Studio will change:

![Input Tag, with Tag Helper styling](https://thepracticaldev.s3.amazonaws.com/i/by5n2977qwm3i3jnu8we.png)

You might be asking why is this better than `@Html.TextBoxFor(m => m.FirstName)`? It really comes down to flexibility, simplicity and the power you can perform with Tag Helpers.

Let's look at another example:
```html
<form asp-controller="Person" asp-action="Edit" method="post">
    <!-- Input and Submit elements -->
</form>
```

The Tag Helper for the `form` tag is actually enabled without needing to specify any attributes - another cool feature of Tag Helpers. In the example above, I am using `asp-controller` and `asp-action` to set where I want the form to submit to. 

Because it is still *just* HTML, nothing stops you adding a `class` attribute and specify anything you need like you would to any other HTML element.

```html
<form asp-controller="Person" asp-action="Edit" method="post" class="my-super-special-form-class">
    <!-- Input and Submit elements -->
</form>
```
If you wanted to set a class when using the old method, it gets a lot messier:

```csharp
using (Html.BeginForm("Edit", "Person", FormMethod.Post, new { @class = "my-super-special-form-class" }))
```
So Tag Helpers can help you keep using HTML mostly the same way you have been already without needing to work with the Razor syntax. Tag Helpers also have better IntelliSense support - what look like strings in `asp-for` are actually model expressions. Same for other custom attributes you might make, everything is type-safe.

## Full example with Tag Helpers

Now you have seen a little about what Tag Helpers are and how they can work, let's see what that first example would look like if everything was tag helpers:

```html
<form asp-action="Edit" method="post">
	<div class="field">
		<label asp-for="FirstName"></label>
		<div class="input-container">
			<input asp-for="FirstName" />
		</div>
		<span asp-validation-for="FirstName"></span>
	</div>
	<div class="field">
		<label asp-for="LastName"></label>
		<div class="input-container">
			<input asp-for="LastName" />
		</div>
		<span asp-validation-for="LastName"></span>
	</div>
	<div class="field">
		<button>Continue</button>
	</div>
</form>
```

## Make your own Tag Helper

First we should probably answer: Why would I want to make my own Tag Helper?

Partly the flexibility it offers, partly making the HTML markup easier to work with and partly because you can do some really cool stuff.

We are going to make our "Continue" button look nicer with adding our own Tag Helper to define some classes for us based on an attribute we set.

To make a Tag Helper, you will need to make a new class that extends `TagHelper` - I am going to call mine `ButtonStyleTagHelper`. It will use the attribute `button-style` to apply itself to the tag.

This use case for a Tag Helper can work great if you are writing [utility-first CSS](https://frontstuff.io/in-defense-of-utility-first-css).

We need to specify a few attributes on the class to define what tags we want our Tag Helper to apply for and what attributes we need define.

```csharp
[HtmlTargetElement("button", Attributes = "button-style", TagStructure = TagStructure.NormalOrSelfClosing)]
[HtmlTargetElement("a", Attributes = "button-style", TagStructure = TagStructure.NormalOrSelfClosing)]
public class ButtonStyleTagHelper : TagHelper
{
    
}
```

The example above says we are allowing our Tag Helper to work for `<button>` and `<a>` as long as `button-style` is set as an attribute.

Now let's define the property our attribute will go on and its type:

```csharp
[HtmlTargetElement("button", Attributes = "button-style", TagStructure = TagStructure.NormalOrSelfClosing)]
[HtmlTargetElement("a", Attributes = "button-style", TagStructure = TagStructure.NormalOrSelfClosing)]
public class ButtonStyleTagHelper : TagHelper
{
	[HtmlAttributeName("button-style")]
	public Style ButtonStyle { get; set; }

	public enum Style
	{
		Primary,
		Secondary
	}
}
```

We have our property `ButtonStyle` explicitly bound to the attribute `button-style` and using our custom `Style` enum, allowing us to pick either `Primary` or `Secondary`.

We need to define our binding for the `Style` enum to the classes we want:

```csharp
private static readonly Dictionary<Style, string> ButtonStyles = new Dictionary<Style, string>
{
	{ Style.Primary, "whatever-classes i-might-specifically want-for-all primary-button-styles" },
	{ Style.Secondary, "the-classes i-might-specifically want-for-secondary button-styles" }
};
```

To actually do work with a Tag Helper, we need to override the `Process` method and give it something to do.

```csharp
public override void Process(TagHelperContext context, TagHelperOutput output)
{
	var classes = ButtonStyles[ButtonStyle];
	var classAttr = output.Attributes.Where(
		a => a.Name.Equals("class", StringComparison.InvariantCultureIgnoreCase)
	).FirstOrDefault();

	if (classAttr != null)
	{
		output.Attributes.Remove(classAttr);
		classes += " " + classAttr.Value.ToString();
	}

	classAttr = new TagHelperAttribute("class", classes);
	output.Attributes.Add(classAttr);
}
```

That might look a little daunting but let's break it down:

```csharp
var classes = ButtonStyles[ButtonStyle];
var classAttr = output.Attributes.Where(
	a => a.Name.Equals("class", StringComparison.InvariantCultureIgnoreCase)
).FirstOrDefault();
```

First we get the classes we defined based on button style that we set. The property `ButtonStyle` has the enum value we define in HTML so we just pass that to the dictionary.

Because you may have defined a `class` attribute already on the element, we want to make sure we just prepend our new classes.

```csharp
if (classAttr != null)
{
	output.Attributes.Remove(classAttr);
	classes += " " + classAttr.Value.ToString();
}

classAttr = new TagHelperAttribute("class", classes);
output.Attributes.Add(classAttr);
```
We now check whether there was a `class` attribute, remove it from the tag and apply those classes to our ones defined for our `ButtonStyle`.

Finally, we recreate a new attribute object and apply that to our tag.

Before we can use our tag, we need to add it to the `_ViewImports.cshtml` file, something like `@addTagHelper *, <YourAssemblyName>` will add all Tag Helpers in that assembly to all Razor templates.

Your final class should look something similar to this:

```csharp
[HtmlTargetElement("button", Attributes = "button-style", TagStructure = TagStructure.NormalOrSelfClosing)]
[HtmlTargetElement("a", Attributes = "button-style", TagStructure = TagStructure.NormalOrSelfClosing)]
public class ButtonStyleTagHelper : TagHelper
{
	private static readonly Dictionary<Style, string> ButtonStyles = new Dictionary<Style, string>
	{
		{ Style.Primary, "whatever-classes i-might-specifically want-for-all primary-button-styles" },
		{ Style.Secondary, "the-classes i-might-specifically want-for-secondary button-styles" }
	};

	[HtmlAttributeName("button-style")]
	public Style ButtonStyle { get; set; }

	public enum Style
	{
		Primary,
		Secondary
	}

	public override void Process(TagHelperContext context, TagHelperOutput output)
	{
		var classes = ButtonStyles[ButtonStyle];
		var classAttr = output.Attributes.Where(
			a => a.Name.Equals("class", StringComparison.InvariantCultureIgnoreCase)
		).FirstOrDefault();

		if (classAttr != null)
		{
			output.Attributes.Remove(classAttr);
			classes += " " + classAttr.Value.ToString();
		}

		classAttr = new TagHelperAttribute("class", classes);
		output.Attributes.Add(classAttr);
	}
}
```

Our end result would turn something like this:

```html
<button button-style="Primary">Continue</button>
```

Into this:
```html
<button class="whatever-classes i-might-specifically want-for-all primary-button-styles">Continue</button>
```

This is just one example of Tag Helpers, there are many other things you could do with Tag Helpers. I may touch on a few of these in a later article.

## Conclusion

Tag Helpers are not just a cleaner way of doing what we could before but a powerful tool to add more functionality to our Razor templates.

## Further Reading
- [Tag Helpers in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro?view=aspnetcore-2.2)