In ASP.NET 5, a new type of pre-compiled user-interface component is available for ASP.NET MVC developers, the TagHelper. It is hard to compose large HTML and JavaScript user interfaces with complex interactions and capabilities. In this article, we’ll introduce the TagHelper, learn about its advantages and disadvantages, explore some sample TagHelpers, and review the deployment experience to a cloud-based hosting provider.
What is a TagHelper
The TagHelper construct fills an interface abstraction need by providing markup generation that is contextually sensitive to its placement on the MVC View. We can use these TagHelpers on the view in the same way that we would refer to any HTML tag, but instead we can use markup and attributes of our own design. A new, unique TagHelper could be referenced in our view with syntax like this:
<thumbnail src="/images/Banner-01-Everleap.png"
caption="Learn More about Everleap!"
alt="EverLeap">
</thumbnail>
That’s completely unique, non-standard HTML that ASP.NET will understand and call the TagHelper for the thumbnail tag and process its contents to transmit to users requesting this view. You can also attach TagHelper capabilities to standard HTML markup, and designate attributes that, when present, trigger the TagHelper to process content. Consider this markup for a standard anchor element:
<a asp-action="Index" asp-controller="Home">Home Page</a>
In this case, the presence of the asp-* attributes triggers the action code within the TagHelper thanks to an if-statement in the source of the AnchorTagHelper. The resultant HTML delivered after the AnchorTagHelper processes this markup will look like this:
<a href="/">Home</a>
Writing a New TagHelper
A TagHelper is defined abstractly by the Microsoft.AspNet.Razor.Runtime.TagHelpers.TagHelper
class in the Microsoft.AspNet.Razor.Runtime
package. TagHelpers can run on both the standard .NET 4.6 framework and the .NET Core framework, so they are truly cross-platform capable.
To define a new TagHelper, you can create a class either in your ASP.NET 5 project or in a new ASP.NET 5 Class Library project and inherit from the TagHelper
class. There are two methods you can implement: Process
and ProcessAsync
. These two methods are where ASP.NET passes control so that you can modify the format of the tag, its attributes, and all of the content contained within the tag.
public class MyTagHelper : TagHelper
{
public override void Process(TagHelperContext context, TagHelperOutput output)
{
base.Process(context, output);
}
public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
return base.ProcessAsync(context, output);
}
}
I recommend you implement the Process
method, and omit the ProcessAsync
method unless you have some asynchronous processing that needs to be accomplished in your code.
The current coding standard of the ASP.NET team dictate that we should name our TagHelper classes with a TagHelper suffix. By convention, the name preceding TagHelper is the name of the HTML element that will activate this TagHelper. In the sample code above, the <my></my> markup will activate the Process method.
You can override this convention by decorating your TagHelper class with a TargetElement attribute that specifies the element name to trigger your code:
[TargetElement("m")]
public class MyTagHelper : TagHelper
{
Let’s take a look at some real examples of creating some TagHelpers to help make implementing bootstrap components easier.
Bootstrap Headers
To implement the bootstrap responsive web framework, their site instructs us to reference two stylesheets and a script file from their CDN. We can automate this in the header of our web pages by implementing a HeadTagHelper
that inserts these elements for us. The initial code for the HeadTagHelper
class will look like this:
public class HeadTagHelper : TagHelper
{
public override void Process(TagHelperContext context, TagHelperOutput output)
{
base.Process(context, output);
}
}
Note that I am not including a TargetElement
attribute for this class, as I want it to trigger for the standard HTML head element. Next, I want to ensure that there is no prior bootstrap references currently defined in the content of my head element. This will prevent a double reference to bootstrap libraries:
public override void Process(TagHelperContext context, TagHelperOutput output)
{
base.Process(context, output);
if (!output.Content.GetContent().Contains("bootstrap"))
{
We can inspect the TagHelperOutput
object to review the content already contained within the head tag. By default, the TagHelper does not modify the content or the attributes of the tag it is operating on. At this point in the Process
method, nothing has changed in the head element. By calling the GetContent()
method we will retrieve all inner content originally written in the razor markup as a continuous string to inspect.
With the assurance that bootstrap is not referenced in the head element, I can then append my CSS and JavaScript references:
if (!output.Content.GetContent().Contains("bootstrap"))
{
var cdn = "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/";
output.PostContent.Append(@"<link rel=""stylesheet"" href=""" + cdn + @"css/bootstrap.min.css"">");
output.PostContent.Append(@"<link rel=""stylesheet"" href=""" + cdn + @"css/bootstrap-theme.min.css"">");
output.PostContent.Append(@"<script src=""" + cdn + @"js/bootstrap.min.js""></script>");
}
Great. Now I can add a directive to my TagHelper in my layout page and enforce that these references are always present:
@addTagHelper "HeadTagHelper, TagHelperSample.Helpers"
<!DOCTYPE html>
The addTagHelper
directive takes a fully qualified class-name to direct the razor interpreter to include those classes while operating on the razor markup. You can optionally use a wildcard character (*) to indicate that all classes in the assembly name after the comma are to be referenced. Finally, the addTagHelper directive only applies to the razor page that it has been added to and does not cascade into other pages that may be referenced or included.
A More Complex Example – Thumbnails
In this example, we’ll review creating a TagHelper for the bootstrap thumbnail component. We won’t get into delivering a full-size image in this sample, just delivering a thumbnail component that shows a browser-scaled version of the full-size image. The desired output that should be delivered to the browser will look like the following:
<div class="thumbnail">
<img src="IMAGE SOURCE" alt="ALT TEXT" />
<div class="caption">
<h3>CAPTION TEXT</h3>
</div>
</div>
We can define these three custom inputs as attributes on a custom thumbnail element that will look similar to the following in razor:
<thumbnail src="IMAGE SOURCE" caption="CAPTION" alt="ALT TEXT"></thumbnail>
We already know how to start a TagHelper class, but this time we will require the src attribute in order to proceed.
[TargetElement("thumbnail", Attributes = "src")]
public class ThumbnailTagHelper : TagHelper
{
public override void Process(TagHelperContext context, TagHelperOutput output)
{
The Attributes argument on the TargetElement
attribute is a comma-separated list of attribute names that are required to appear before this TagHelper class will be used on the razor page. In this case, if the src attribute is missing, the thumbnail markup will be emitted in the HTML delivered to the requesting client. Let’s start transforming the output of this tag to yield the outer div tag with the thumbnail class and drop the thumbnail element:
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "div";
output.Attributes["class"] = "thumbnail";
By setting the TagName
property to "div", razor will swap out the original thumbnail tag with a div tag and keep all of the original attributes attached to the element. The second command sets the class attribute of the div to "thumbnail" as required by bootstrap for this component.
var sb = new StringBuilder();
sb.AppendFormat("<img src=\"{0}\"", context.AllAttributes["src"].ToString());
if (context.AllAttributes.ContainsKey("alt"))
{
sb.AppendFormat(" alt=\"{0}\"", context.AllAttributes["alt"]);
}
sb.Append("/>");
Next, I use a StringBuilder to collect the elements that are being constructed and output. First, the image tag is defined and if alt-text is specified in the attributes of the original thumbnail element, that text is output in the alt-attribute of the image tag.
if (context.AllAttributes.ContainsKey("caption"))
{
sb.AppendFormat("<div class=\"caption\"><h3>{0}</h3></div>", context.AllAttributes["caption"]);
}
If a caption attribute was specified on the thumbnail element, we output that with the prescribed markup from the bootstrap documentation. Next, I want to remove the extra attributes from the thumbnail element that we do not want emitted on our shiny new div element:
foreach (var item in new[] { "src","alt","caption" })
{
output.Attributes.Remove(item);
}
We can then clear out the content from the original thumbnail element and replace it with the markup stored in the StringBuilder:
output.Content.SetContent(sb.ToString());
base.Process(context, output);
}
A final note about using the TagHelpers, when we start using this thumbnail tag in our code, it looks like this in Visual Studio by default:
<thumbnail src="http://lorempixel.com/400/400/cats"
caption="A random cat picture"
alt="Cats!">
</thumbnail>
Notice that the tag name changes to a bold purple color. This indicates that this tag is being processed as C# code before it will be passed to the requesting client.
Publishing to the Cloud
I’ve chosen to publish my sample content to the cloud using a premium provider like Everleap. Everleap has all of the rich cloud elastic resources like you would expect, and supports running ASP.NET 5 web applications already. Here’s how easy it was for me to deploy this application to the Everleap cloud.
I started by downloading my Everleap publish profile from my console at cp.everleap.com:
I entered the publish dialog for my project by right-clicking on the project name in the solution explorer and choosing "Publish"
I clicked Import and chose the profile that was downloaded from the Everleap control panel. After the profile is imported, click back on the profile option in the left list and change the profile to an FTP publish:
On the next screen, change the site path to the "site" folder and key in your password:
Next, configure the publish settings with the appropriate DNX framework version, uncheck the delete all existing files option, and uncheck the "Publish with Powershell" option.
That’s it ... click publish and your application will be loaded onto your Everleap server. Remember, in the new ASP.NET 5 model the DNX framework is portable and modular. You will initially be loading all of the parts of the DNX framework that your application needs to run. In subsequent publications, only your changed files will be loaded to the servers.
Summary
ASP.NET 5 is a complete rethinking of the ASP.NET runtime and how the framework works together. The programming model for MVC developers has not changed significantly, but with the introduction of TagHelpers our task of building views has become much simpler with reusable code that can be placed in shared libraries and NuGet packages for your whole organization to use. Loading your new application onto a cloud provider like Everleap is easy to do thanks to their planning and adoption of these new standards as soon as they were available.
You can see the TagHelpers demo site at http://1786-4973.el-alt.com/
For more information about cloud hosting for ASP.NET applications, visit Everleap.com.