Revisiting Programmatic Tag Helper Rendering

In the Rendering a tag helper inside another tag helper post, I showed how it was possible to instantiate a tag helper, to run its Process method and to get the generated result as a string.

Recently, after doing some investigation about the tag helper rendering process I had some insights about how to create TagHelperOutput and TagHelperContext objects as well as how to leverage existing behaviors to get the generated content as a string.

We are going to bring back the renderInnerTagHelper method from the example of the Rendering a tag helper inside another tag helper post:

Now let’s review this chunk of code step by step.

Creating the TagHelperOutput

The TagHelperOutput object gets populated by the generated content inside the tag helper’s Process method.

We need to supply some arguments to the constructor of the TagHelperOutput class to create one:

  • The TagName of the tag being rendered
  • The list of html attributes related to the tag being rendered
  • A callback that asynchronously returns any eventual child content contained inside the tag being rendered

The razor template engine seems to provide the TagName value for ‘standard’ html tag helpers such as the form and img tag helpers. So it is the responsibility of the creator of the TagHelperOutput to provide this value. Nevertheless it can be safe to ignore this argument for custom tag helpers that sets their TagName by them selves.

The attribute list argument is straightforward; just supply the list of desired html attributes.

The callback argument is generated by the razor template engine when rendering nested tag helpers. It should contain code that generates child content and that runs any nested tag helpers. We can safely assume that it is safe to provide an async function that returns empty TagHelperContent when the tag helper does not contain child content.

Finally, the razor template engine also sets the TagMode property on the TagHelperOutput object. It is wise to do so when creating TagHelperOutputs manually.

Creating the TagHelperContext

The responsibility of the TagHelperContext is to contain data that is going to be passed from parent to child tag helpers when they are Processed.

In the previous example, we just used the context object provided by the razor template engine. But let’s see how to create one still.

The constructor for TagHelperContext takes the following arguments:

  • The same attribute list used for creating the TagHelperOutput
  • An Items Dictionary<object, object> that contains the data passed along with the context
  • A unique identifier for the rendering scope

The first two arguments are straightforward.

The last argument is used internally by the ASP.NET Core framework for some caching mechanism and is provided by the razor template engine, furthermore the value generated of the unique Id generated by razor seems to be a Guid striped of its hyphens(726e2629c3b34a6b92dd8436f55ad968 for example). So I think it is OK to provide a bogus value since we are not soliciting the caching behavior when rendering tag helpers programmatically.

Finally, I would like to point out that the TagHelperContext instance might not be used if there are no nested content involved.

Getting the generated content as a string

In the renderInnerTagHelper method, the tag helper output is manually converted into a string and an additional renderHtmlAttributes method is defined to generate the html attributes.

Actually, the TagHelperOutput class implements the IHtmlContent interface and provides an implementation for the WriteTo method that writes the output content into a supplied TextWriter.

TagHelperOutput.WriteTo takes two arguments:

  • A TextWriter object, here we just pass a new StringWriter
  • An HtmlEncoder object, that we can get by dependency injection

All we have to do after calling WriteTo is to read the results from the supplied TextWriter.

Clearly, this is the way to go to get the rendered content.

Revisiting the previous example

The following is the updated renderInnerTagHelper method, you can see that we took into account the previous guidelines:

The full code is, of course, available in the RenderingTagHelperInsideAnother Github repository.