Introducing TagHelpers in ASP.NET Core

TagHelpers are introduced in ASP.NET Core MVC as a new way of writing server-side code that renders HTML tags (elements), that is much closer to the HTML format than to Razor.  TagHelpers represent a mechanism to add server-side processing to a regular HTML tag, which in many ways is very similar to Angular or React directives.

Compared to Razor, the code is way cleaner, there is no context-switching and no need to use @ escape sequence like in Razor.

But for a beginner, it might be difficult to understand at first, given the fact that the syntax resembles so much to HTML and it might have a steep learning curve.

What I really enjoy about TagHelpers is the fact that when you have multiple attributes to add, you have full IntelliSense support and the view code will still look clean.

And even if you have server-side code here and there, that is very blended in the view, it still looks consistent.

Here's an example:

Razor:

@Html.TextBoxFor(m=>m.FirstName, new { @class = "form-control", placeholder=”add a first name” })

Vs

<input asp-for="FirstName" placeholder=”add a first name” class="form-control" />

You can use a TagHelper to extend existing HTML tags, to replace them with entirely new content or to create completely new ones. You can restrict the usage area, make two or more TagHelpers communicate with each other, have access to the current request Context and enforce rules. What more can you wish for?

Don’t get me wrong – they don’t intend to replace anything (such as partial views, for example). Their aim is to aid with reusability and maintenance and to provide to the developers a more HTML-like experience when rendering pieces of the server-side code. Or, maybe, attract developers that are more front-end oriented.

Whether you like TagHelpers or not, is a matter of personal preference. But using them is necessary – at least the standard ones, as you have no other choice. Integrating custom TagHelpers into your code saves you considerable time and effort, all while creating consistency for the entire team.  Of course, you can easily write an entire application without any custom TagHelper or fall into the other extreme and make everything a custom TagHelper. Try to find the right balance for your project’s need and you will reap the rewards afterward.

Implementing a TagHelper

We always want to have loosely coupled components in our applications, but we often forget that this principle shouldn’t apply only between services, layers, and so on. Why shouldn’t we apply and want the same principle in our ‘UI’ complex components?

For example, in an MVC app, we often have dropdowns that are populated with data that comes from the database, and that logic is often passed through a controller. In this case, we could very easily step it up and use only a repository that brings us the right data, extract an interface for it and inject that in a custom TagHelper constructor that will bring only the data we need. Let’s say that the usage of a custom TagHelper abstracts away the need of ever using a List<SelectListItem >.

This way we can make full use of the build in Dependency Injection mechanism to obtain a repository or maybe a service (if it’s the case) since these are registered at runtime through the same mechanism.

Processing a TagHelper

When you start writing a TagHelper, you should know what are the available methods and properties that help you generate an output. In the following example, you can see the options are vast.

public override void Process(TagHelperContext context,
TagHelperOutput output)
{
output.PreElement.SetHtmlContent("<section>Pre Element</section>");
output.PreContent.SetHtmlContent("<div>Pre Content</div>");
output.Content.SetHtmlContent("<div>Actual content Content</div>");
output.PostContent.SetHtmlContent("<div>Post Content</div>");
output.PostElement.SetHtmlContent("<section>Post Element</section>");
}

Using the TagHelperOutput’s properties, you will get something like this as a result, and you can manipulate that content in what way you want:

<section>Pre Element</section>
<content>
<div>Pre Content</div>
<div>Actual content Content</div>
<div>Post Content</div>
</content>
<section>Post Element</section>

Discovering and Targeting TagHelpers

Any custom TagHelper needs to implement the ITagHelper interface and, for them to be discoverable inside the project, you need to add them explicitly inside the view.

_ViewImports.cshtml is a good place to set common layout, since it will be executed for all views, and if needed, you can override that by each independent view.

To make available the TagHelpers inside _ViewImports.cshtml: 

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

@addTagHelper *, MyAwesomeProject.TagHelpers

There are cases when we want to allow a TagHelper to be valid and used only in certain contexts, or to have a certain parent. By default, it can be used anywhere inside the page, in any context.

To accomplish this, we need to add the HtmlTargetElement annotation to our TagHelper class.

For example:

[HtmlTargetElement("super-hero", ParentTag = "li")]

This will make our custom TagHelper be invalid in all contexts, because we specified that the ParentTag should be a <li> HTML element, and as a result, Visual Studio will show a warning in our view -- the code won’t have the right color in the View and won’t appear in solid green (as TagHelpers appear in my Visual Studio color scheme).

Furthermore, if we want to add attributes on our custom TagHelper, we should make use of the “Attributes” field, which allows us to add multiple properties, separated by commas, and will make them required.

Example:

[HtmlTargetElement("super-hero", ParentTag = "li", Attributes=”first-name, last-name”)]

This will be the correct context and structure for our custom TagHelper. If you want, you can take an extra step and restrict the applicability to a certain value of a property.

You can obtain this by using a syntax when adding the attributes list. The syntax, similar to the ones written in CSS, is:

[HtmlTargetElement("super-hero", ParentTag = "li", Attributes = "[first-name='clark'], last-name")]

The result will restrict the server-side processing to only those elements that have a certain attribute value. In our case first-name must be equal to “clark”.

A Few Standard TagHelpers

Anchor tag

The anchor tag is one of the most used HTML elements, as we always need to connect and navigate to different pages.

Razor:

@Html.ActionLink("Home", "Index", new { @class="btn btn-primary", role="button" })

Vs

<a asp-action="ActionName" asp-controller="ControllerName" asp-page="RazorPageName" asp-route="RouteName" asp-area="AreaName">...</a>

If you add the asp-action, but not the asp-controller attribute, it will default to the current controller. Also, you can even specify the fragment (the ID of an HTML element) for the same page navigation.

Form tag

Without a form, that collects data from the users in POST request, we would have only informative pages. In the current web world, everything is about fast and reliable interaction. To have that, let’s see how the HTML form tag looks like in ASP.NET Core MVC.

The form tag can be used instead of old Razor Html.BeginForm(), both offering the same features, posting to controller actions, and so on.  The antiforgery token is turned on by default, on any form you will use, and you can switch it off by adding an attribute.

Example:

<form asp-controller="Favorites" asp-antiforgery="false" asp-action="Create">

Razor:
@using (Html.BeginForm("Favorites",
"Create",
FormMethod.Post,
new { @class = "form-horizontal"}))
{
//form content
}

Tag Helper:

<form asp-action="Create" asp-controller="Favorites" method="post" class="form-horizontal"> </form>

Custom TagHelpers

To best understand custom TagHelpers, let’s say you have an app that manages superheroes. You work very often with the names of the superheroes, in different areas of the app. To create this, in MVC you would maybe:

  • Add a property in your model that is of type List<SelectListItem>, and pass it through,
  • Use a ViewBag populated at controller action level that holds the key-value pairs for your dropdown or
  • Have a separate controller that serves you JSONs for your data and uses different repositories for different types of data.

Either way, you might end up have something that works fine but it’s scattered around in different controllers, views, making your code less maintainable.

@Html.DropDownListFor(model => model.FavoriteSuperHero, (List<SelectListItem>)ViewBag.SuperHeroNames, "Select One")

What happens if your project or the team grows very much? How would you enforce consistency across the team?

Well, TagHelpers saves you the trouble and lets you write something just once, and then use it everywhere without additional setup.

This is a very simple example of using a repository to bring some names from the database and to populate a few HTML elements, but the cases where you could use a TagHelper are almost unlimited.

Server-side code:


[HtmlTargetElement("super-hero")]
public class SuperHeroNamesTagHelper : TagHelper
{
private readonly ISuperHeroesRepository superHeroesRepository;

public SuperHeroNamesTagHelper(ISuperHeroesRepository superHeroesRepository)
{
this.superHeroesRepository = superHeroesRepository;
}

public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "select";
output.Attributes.SetAttribute("class", "awesome-select");

var names = superHeroesRepository.GetHeroNamesAndIds().OrderBy(x => x.Nickname);

foreach (var name in names)
{
TagBuilder option = new TagBuilder("option")
{
TagRenderMode = TagRenderMode.Normal
};
option.Attributes.Add("value", name.Id.ToString());
option.InnerHtml.Append(name.Nickname);
output.Content.AppendHtml(option);
}
}
}

As you can see above, on the view side, it is nothing more than a regular tag and you’ll still have Intellisense support and the description (if you add it).

The TagHelpers addition started with ASP.NET Core 1.0 and will continue to be supported even in ASP.NET Core 2.1. Recently, Microsoft announced the LTS for the 2.1 version, meaning that TagHelpers are here to stay.

Overall, the addition of TagHelpers to ASP.NET Core MVC looks to be a powerful one and will prove to be very useful in any modern MVC projects, as long as it is used with care. On the long run, to render HTML from the server side anywhere and as often as we can, TagHelpers are the ideal solution. Using old plain HTML will most definitely come with performance penalties that aren’t easy to solve or might waste precious time to fix.

Image credit: StockEU / Shutterstock

Irina Scurtu is a Software Architect at Fortech, a Microsoft Certified and CompTIA Trainer, workshop host and keynote speaker. She is always in a quest for latest trends and best practices in the .NET scene, with a keen interest in leadership and personal development.

Comments are closed.

© 1998-2024 BetaNews, Inc. All Rights Reserved. Privacy Policy - Cookie Policy.