Creating a Custom Tag Helper in ASP.NET Core: Generating HTML
This post is about the exciting part of creating a custom tag helper: generating new HTML to go down to the user in the place of whatever "placeholder" tag you insert into your View or Razor Page.
A quick review of where we are now: In a previous post I talked about the structure of a tag helper (Razor attaches your tag helper to an element and then calls your tag helper's Process or ProcessAsync method). At the start of your Process method, you'll gather data about the context that your tag helper is executing in (the element and View or Razor Page it's part of, among other things) as I described in that post (and, as a bonus, also taked about why tag helpers aren't a complete replacement for HtmlHelpers). Previous to that second post, I first described the fundamentals of using custom tag helpers to improve productivity.
Now it's time to modify the element your tag helper is attached to. You've got six options:
- Change the name of your element
- Add or remove attributes
- Change the structure of your element (switch from a self-closing tag to one with open and close tags)
- Change the content inside the element's open and close tags
- Add content before or after your element
- Produce no HTML at all
The first thing to recognize is that, if you don't take any action, the element your tag helper is attached to will be sent to the browser as is (well, except for any attributes generated from properties on your tag helper class, which are stripped out automatically). So this element
<contact domain-name="phvis.com" class="active" />
will generate this in the page displayed in the browser:
<contact class="active" />
Since the browser ignores any elements not defined in the HTML specification, this particular example won't cause anything to be displayed to the user.
All of the options for generating HTML are available to you through the TagHelperOutput object passed to your Process method in a parameter named output.
Changing the Element, its Attributes and its Structure
For example, to change the name of your tag, all you have to do is set the TagName property on the output parameter to the name you want your new tag to have. So, to turn the element into an anchor tag, all you need is this code:
output.TagName = "a";
To add or remove attributes, you'll use the output parameter's Attributes collection, which has an Add method and various Remove* methods. While you can retrieve an existing attribute, you can't set its Value property, so updating an existing attribute isn't a straightforward task (existing attributes do have a WriteTo property that lets you update the attribute's value as a Stream). For most cases, though, if you want to change the value of an attribute, it's probably easier just to remove the attribute and add it back with a new value.
There is one exception to that rule: the class attribute. The output parameter has a method called AddClass which will update an existing class attribute with the value you pass to it (if a class attribute isn't present, AddClass will add one).
The output parameter also has a property called TagMode that lets you specify whether the element going to the browser is a self-closing tag, an open and close tag or just a start tag (which assumes that some other tag will handle closing the element).
This code, for example, converts the element that the tag helper is attached to into an anchor tag with an href attribute and an updated class atribute:
output.TagName = "a";
output.TagMode = TagMode.StartTagAndEndTag;
Working with Element Content
To change what's between the element's open and close tags, you use the output parameter's Content property. The Content property's Clear method will wipe out any existing content in between the open and close tags (without this, all the element's existing content goes down to the browser by default).
The SetContent and SetHtmlContent methods will let you replace any content (the SetHtmlContent method ensures that any HTML you insert will be recognized by the browser). Rather than add content at all once, you can incrementally add content using the Append, AppendHtml and AppendFormat methods (the AppendFormat method allows you to use placeholders to insert variable data). There's even a WriteTo method if you'd prefer to treat the content as a Stream (I do not).
This code would insert my name in between the start and end tags of my new anchor element:
Putting all those code examples together would generate this HTML from whatever element it was attached to (if the original element already had a class attribute, my ignore setting would be added to the attribute's existing settings):
<a href="http://www.phvis.com" class="ignore">Peter Vogel</a>
Doing anything with the Content property will cause its IsModified property to be set to true. You can clear out the Content property and set the IsModified property to false by calling the output parameter's Reinitialize method instead of its Clear method. If you like the content that the element already has, you can use the output parameter's PreContent and PostContent properties to insert new material before and after that existing content.
If you'd like, you could also add content in the vicinity of the element that your tag helper is attached to. The output parameter's PostElement and PreElement properties have methods similar to the Content property that let you add content before and after the element.
You can even use the output parameter's SuppressOutput method to prevent anything from going to the browser from your tag helper. That seems a little extreme to me, but I can imagine cases where, after gathering data, it turns out that I don't want to do anything.
And, when it comes to doing nothing, I'm your guy.
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.