Update: In the latest version of ASP.NET 5, the GetChildContextAsync, that was previously available in the TagHelperContext class, is now in TagHelperOutput.
If you’ve been following my posts you probably know that I have been a critic of MVC because of its poor support of reuse. Until now, the two main mechanisms for reuse – partial views and helper extensions – had some problems:
Partial views cannot be reused across projects/assemblies;
Helper methods cannot be easily extended.
A tag helper behaves somewhat like a server-side control in ASP.NET Web Forms, without the event lifecycle. It sits on a view and can take parameters from it, resulting on the generation of HTML (of course, can have other side effects as well).
Tag helper can either be declared as new tags, ones that do not exist in HTML, such as <animation>, <drop-panel>, etc, or can intercept any tag, matching some conditions:
Having some pre-defined tag;
Being declared inside a specific tag;
Having some attributes defined;
Having a well-known structure, like, self-closing or without ending tag.
A number of these conditions can be specified, for example:
All IMG tags;
All A tags having an attribute of action-name;
All SPAN tags having both translate and from-language attributes;
All COMPONENT tags declared inside a COMPONENTS tag.
ASP.NET 5 includes a number of them:
AnchorTagHelper: generates links to action methods in controllers;
CacheTagHelper: caches its content for a number of seconds;
EnvironmentTagHelper: renders its content conditionally, depending on the current execution environment;
FormTagHelper: posts to an action method of a controller;
ImageTagHelper: adds a suffix to an image URL, to control caching of the file;
ValidationMessageTagHelper: outputs model validation messages;
ValidationSummaryTagHelper: outputs a validation summary.
Enough talk, let’s see an example of the TagHelper class with some HtmlTargetElement attributes (no API documentation available yet):
This tag helper will intercept the following tag declarations, inside a view:
Tag helpers need to be declared in a view before they can be used. They can come from any assembly:
Properties declared on the markup will be automatically mapped to properties on the TagHelper class. Special names, such as those having –, will require an HtmlAttributeName attribute:
If, on the other hand, we do not want such a mapping, all we have to do is apply an HtmlAttributeNotBound attribute:
The TagHelper class defines two overridable methods:
Process: where the logic is declared, executes synchronously;
ProcessAsync: same as above, but executes asynchronously.
We only need to override one. In it, we have access to the passed attributes, the content declared inside the tag and its execution context:
You can see that I am redefining the output tag, in this case, to DIV, and, also, I am clearing all content and replacing it with my own.
Now, let’s see a full example!
Microsoft makes available for developers the Bing Translator API. It allows, at no cost (for a reasonable number of requests) to perform translations of text through a REST interface (several API bindings exist that encapsulate it). Let’s implement a tag helper that will automatically translate any text contained inside of it.
In order to use this API, you first need to register at the Microsoft Azure Marketplace. Then, we need to create a Marketplace application. After we do that, we need to generate an authorization key. There are several tools that can do this, in my case, I use Postmap, a Google Chrome extension that allows the crafting and execution of REST requests.
The request for generating an authorization key goes like this:
client_id: <client id>
client_secret: <client secret>
The parameters <client id> and <client secret> are obtained from our registered applications’ page and grant_type, client_id, client_secret and scope are request headers. The response should look like this:
"access_token": "<access token>",
Here, what interests us is the <access token> value. This is what we’ll use to authorize a translation request. The value for expires_in is also important, because it contains the amount of time the access token is valid.
A translation request should look like this:
GET http://api.microsofttranslator.com/V2/Ajax.svc/Translate?to=<target language>&text=<text to translate>&from=<source language>
Authorization: Bearer <access token>
The <target language> and <text> parameters are mandatory, but the <source language> one isn’t; if it isn’t supplied, Bing Translator will do its best to infer the language of the <text>. Authorization should be sent as a request header.
Let’s define an interface for representing translation services:
Based on what I said, a Bing Translator implementation could look like this:
Let’s register this service in ConfigureServices:
Of course, do replace <access token> by a proper one, otherwise your calls will always fail.
In order to make our solution more scalable, we will cache the translated results, this way, we avoid unnecessary – and costly – network calls. ASP.NET 5 offers two caching contracts, IMemoryCache and IDistributedCache. For the sake of simplicity, let’s focus on IMemoryCache, On the ConfigureServices method of the Startup class let’s add the caching services:
And now let’s see it all together.
Putting it All Together
The final tag helper looks like this:
It tries to obtain the translated context from the cache – if it is registered – otherwise, it will issue a translation request. If it succeeds, it stores the translation in the cache. The cache key is a combination of the text (in lowercase), the source and destination languages. The only strictly required service is an implementation of our ITranslationService, the cache is optional. In order use the tag in a view, all we need is:
I hope I managed to convince you of the power of tag helpers. They are a powerful mechanism and a welcome addition to ASP.NET MVC. Looking forward to hearing what you can do with them!