Easy Way To Use Tag Helpers in ASP.NET MVC 6
ASP.NET MVC gives you a powerful, patterns-based way to build dynamic websites that enables a clean separation of concerns and that gives you full control over markup for enjoyable, agile development. ASP.NET MVC includes many features that enable fast, TDD-friendly development for creating sophisticated applications that use the latest web standards.
Tag Helpers enable server-side code to participate in creating and rendering HTML elements in Razor files. For example, the built-in ImageTagHelper can append a version number to the image name. Whenever the image changes, the server generates a new unique version for the image, so clients are guaranteed to get the current image (instead of a stale cached image). There are many built-in Tag Helpers for common tasks – such as creating forms, links, loading assets and more – and even more available in public GitHub repositories and as NuGet packages. Tag Helpers are authored in C#, and they target HTML elements based on element name, attribute name, or parent tag. For example, the built-in LabelTagHelper can target the HTML <label>
element when the LabelTagHelper
attributes are applied. If you’re familiar with HTML Helpers, Tag Helpers reduce the explicit transitions between HTML and C# in Razor views. Tag Helpers compared to HTML Helpers explains the differences in more detail.
Writing a Custom Tag Helper in ASP.NET MVC 6
Let’s say we want to write a custom tag helper for rendering a <time>
tag based on aDateTime
. Those <time>
tags can be used to represent dates and times in a machine-readable format. However, they require a very specific date format that we shouldn’t have to repeat over and over again. Here’s how we would use our tag helper:
@{ var exampleDate = new DateTime(2016, 12, 02, 14, 50, 31, DateTimeKind.Utc); } <time asp-date-time="@exampleDate" />
The output should be something along the lines of the following:
<time datetime="2016-12-02T14:50:31Z" title="Wednesday, December 2, 2016 02:50 PM UTC">December 2, 2016 2:50 PM</time>
We’ll start by creating a custom class that derives from the TagHelper
class found in theMicrosoft.AspNet.Razor.TagHelpers
namespace. We’ll also create a property to hold the datetime that’s passed in through the asp-date-time
attribute:
public class TimeTagHelper : TagHelper { [HtmlAttributeName("asp-date-time")] public DateTime DateTime { get; set; } }
However, we only want to apply our tag helper to <time>
tags that specify the asp-date-time
attribute, so we’ll explicitly restrict it to those using the HtmlTargetElement
attribute on the tag helper class:
[HtmlTargetElement("time", Attributes = DateTimeAttributeName)] public class TimeTagHelper : TagHelper { private const string DateTimeAttributeName = "asp-date-time"; [HtmlAttributeName(DateTimeAttributeName)] public DateTime DateTime { get; set; } }
To specify our tag helper’s behavior, we’ll override the Process
method and add our datetime manipulation logic inside of it. We’re setting both a machine-readable datetime
attribute and a human-readable title
attribute:
[HtmlTargetElement("time", Attributes = DateTimeAttributeName)] public class TimeTagHelper : TagHelper { private const string DateTimeAttributeName = "asp-date-time"; [HtmlAttributeName(DateTimeAttributeName)] public DateTime DateTime { get; set; } public override void Process(TagHelperContext context, TagHelperOutput output) { output.Attributes["datetime"] = DateTime.ToString("yyyy-MM-dd'T'HH:mm:ss") + "Z"; output.Attributes["title"] = DateTime.ToString("dddd, MMMM d, yyyy 'at' h:mm tt"); } }
Note that we’ll also have to add a line to _ViewImports.cshtml for our tag helper to be recognized within Razor views:
@addTagHelper "*, YourTagHelperAssemblyName"
If we now render a <time>
tag using this simple version of the tag helper, we get both attributes, but no inner HMTL (no content). Let’s extend our tag helper such that it adds a default piece of inner HTML if the <time>
tag doesn’t define any child content. To do this, we’ll await and inspect the GetChildContentAsync
method, which means that we’ll have to override ProcessAsync
instead of Process
:
[HtmlTargetElement("time", Attributes = DateTimeAttributeName)] public class TimeTagHelper : TagHelper { private const string DateTimeAttributeName = "asp-date-time"; [HtmlAttributeName(DateTimeAttributeName)] public DateTime DateTime { get; set; } public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { output.Attributes["datetime"] = DateTime.ToString("yyyy-MM-dd'T'HH:mm:ss") + "Z"; output.Attributes["title"] = DateTime.ToString("dddd, MMMM d, yyyy 'at' h:mm tt"); var childContent = await output.GetChildContentAsync(); if (childContent.IsEmpty) { output.TagMode = TagMode.StartTagAndEndTag; output.Content.SetContent(DateTime.ToString("MMMM d, yyyy h:mm tt")); } } }
Now we should get the output we want:
<time datetime="2016-12-02T14:50:31Z" title="Wednesday, December 2, 2016 02:50 PM UTC">December 2, 2016 2:50 PM</time>