Get in touch today: 0800 133 7948 or Email us
Talk to an expert: hello@atlascode.com

ASP.Net Core Tag Helpers

Posted on: September 14th, 2017 by Dean North

Tag Helpers have been around for quite a while now. But when the ASP.Net team announced RazorPages, a few of my colleagues were afraid that we may be returning to the days of WebForms *shudder*. When you first see Tag Helpers you may be reminded of those darker times, but once you understand how they work, you will realise how awesome they are and how much cleaner they make your code.

What is a Tag Helper?

If you have created a new ASP.Net Core 2 web application with authentication, you have already seen Tag Helpers in action. Let’s take a quick look at an example on the login page.

Login RazorPage

You may have noticed that some of the elements are highlighted in bold purple. This indicates that a Tag Helper is acting on this element. You will also notice that some attributes are bold purple too, this indicates that there are tag helpers being applied to this element that are bound to these attributes. Usually these attributes won’t actually be rendered in your final output.

So what are these Tag Helpers actually doing?

These Tag Helpers do exactly the same job that was being done by the Html Helpers in the previous MVC project templates. If we have a look at what this same page used to look like when it was an old style MVC View we will see it’s quite similar.

Login MVC View

What used to be @using(Html.BeginForm(... is now simply <form> which we can all agree is a lot simpler.

How do Tag Helpers work?

At the point of compiling the razor file, the razor engine will load all of the Tag Helpers which are defined either in the razor file itself, or from the _ViewImports.cshtml files (This file specifies all of the using statements and Tag Helpers to use in all razor files which are in the same folder or any sub-folders). Once it has a list of which Tag Helpers to apply, it checks each element in the page against that list and if it finds a match, it will create an instance of the Tag Helper and pass in the context and output which allows the Tag Helper to control what gets rendered before, inside and after that element. It can even remove the element itself and just render its children if it wants to.

Great, now we have more hidden code modifying our output! How can I find out what is modifying my DOM with all these Tag Helpers everywhere!

Fear not, the tooling has got you covered. When you hover over a purple element or attribute, you will get a tooltip which lists the Tag Helper that is acting on the element. You can’t F12 into the class yet, but at least we can see what is going on and find the Tag Helper class ourselves.

TagHelper Tooptip

Ok I’m on board, how can I make my own Tag Helper?

I’m glad you asked! It’s really easy, let’s look at a small example. If you have ever created a mailto link with an email that has a body, you will have seen the mess that is url encoded mailto links…

<a href="mailto:hello@atlascode.com?=&subject=This%20is%20a%20test%20email&body=Hi%20there,%0D%0A%0D%0AThis%20is%20an%20example%20email%20body%20that%20has%0D%0Amultiple%20lines%20of%20info%0D%0A%0D%0AKind%20Regards%0D%0ATest">Click here to email</a>

Looking at this in the source is not very helpful and to be honest gives me a headache.

A Tag Helper could make this so much nicer from a development perspective. That same link could be entered into the razor page like this…

<mailto text="Click here to email" to="hello@atlascode.com" subject="This is a test email">
    Hi there,

    This is an example email body that has
    multiple lines of info

    Kind Regards
    Test
</mailto>

This is much easier to read and the Tag Helper outputs exactly the same final html.

To make a Tag Helper, all you need to do is create a class with the Tag Helper suffix and have it implement ITagHelper. There is also a Tag Helper abstract class which implements this interface and makes things a bit easier. If you inherit the Tag Helper class, there are 2 Process methods, one synchronous and one asynchronous. Override one of these methods and you will be able to modify the output for the matching elements.

public class MailtoTagHelper : TagHelper
{
    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        ...
    }
}

Tag Helpers match by naming convention by default. As this one is called MailtoTagHelper it will match on any elements with a node name of mailto. You can override the default by adding HtmlTargetElementAttributes to your class. In this instance we actually want to match by node name so we will stick with the default behaviour.

Next we want to be able to read in some attributes from the element, like the text for the link, who the email is to, the subject etc. If we add some public properties to out Tag Helper class, these will automagically be mapped by the time the Process method is invoked.

public class MailtoTagHelper : TagHelper
{
    public string Text { get; set; }
    public string To { get; set; }
    public string CC { get; set; }
    public string BCC { get; set; }
    public string Subject { get; set; }

    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    { 
        ...
    }
}

Now if we look at what this looks like in Visual Studio we will see that any attributes that match these public properties will be highlighted in bold purple.

MailTo usage

You can see that text, to and subject are all purple, but something is still red. This indicates that the former are mapped and the latter is not. By default, mapped attributes will be removed from the output, so in this example, we would still see the something attribute in our output, but none of the others.

Great, so now all we need to do is swap the mailto node name with a and add a href attribute which contains the actual mailto link. Let’s take a quick look at how we can do that (excuse the nightmarish nested string interpolation).

public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
    output.TagName = "a";

    var childContent = await output.GetChildContentAsync();
    string emailBody = childContent.GetContent();

    // Remove all leading whitespace
    emailBody = Regex.Replace(emailBody, @"(\r\n[^\S\r\n]*)", "\r\n").Trim();

    output.Attributes.SetAttribute("href", $"mailto:{To}?={(string.IsNullOrWhiteSpace(CC) ? "" : $"&cc={CC}")}{(string.IsNullOrWhiteSpace(BCC) ? "" : $"&bcc={BCC}")}{(string.IsNullOrWhiteSpace(Subject) ? "" : $"&subject={UrlEncoder.Default.Encode(Subject)}")}{(string.IsNullOrWhiteSpace(emailBody) ? "" : $"&body={UrlEncoder.Default.Encode(emailBody)}")}");
    output.Content.SetContent(Text);
}

Here output represents the mailto element. So setting the TagName will change the node name to a. We then get the child content (which could contain other razor code or tag helpers) at this point the content is rendered to html which we can process further. You may have noticed that in our examples the email body is indented from the parent mailto element. Rather than fight with the auto formatting of visual studio, I decided to just strip this out with a quick regex replace. If you are interested, this works by replacing any non-new line whitespace after a new line with just a new line. We select non-new line whitespace with a double negative selector.

Now we have the email body from the element content and the other properties from the element’s mapped attributes, we can add the href attribute and then finally set the content to be the text within the final a tag.

At runtime this will output the same mailto link that we saw earlier with all the url encoded query string parameters, but at design time, we will be able to see clearly what the content of the email is, it’s subject, who it is to etc.

Now what?

Now that you know what Tag Helpers do and how to create your own, go forth and make the web a better place! The possibilities are endless!

Thanks for reading.

Dean North

Dean founded Bespoke Software Development Company - Atlas Computer Systems Limited and is our technical consultant (aka technical wizard).

Want to stay up to date with the latest software news, advice and technical tips?

Loading
;