The Very Basics of Nesting for Tag Helpers

In Asp.Net Core it is possible to create custom tag helpers that nests within other custom tag helpers. Consider the following example borrowed from another interesting post:

Here modal, modal-body and modal-footer are custom tag helpers, the modal tag helper encapsulates all the verbose html of the bootstrap modal. The modal-body and the modal-footer tag helpers allows the programmer to respectively specify the content of the modal’s body and footer.

modal-body and modal-footer are tag helpers created to be used only inside the modal tag helper.

In this post we are going to see as usual a very basic example that will show how to create nested tag helpers.

I encourage you to check out Dave Paquette’s post that shows how to encapsulate a bootstrap modal in a tag helper and more, if you want to see more elaborated examples.

How nested tag helpers work?

We are going to create two tag helpers: ParentTagHelper and ChildTagHelper. Obviously ChildTagHelper is going to be nested inside ParentTagHelper. The following is the typical usage example:

<parent> <child></child> </parent>

Now let’s see how the two tag helpers are defined:

The ParentTagHelper.ProcessAsync method performs the following steps:

  1. Create an instance of the ParentChildContext class which will act as a data transfer object between the parent and child tag helpers
  2. Register the ParentChildContext instance in the context argument
  3. Await the rendering of it’s children content
  4. When the children’s content is available, set the output content by using data in the ParentChildContext instance populated by the ChildTagHelper

Notice here how the ParentTagHelper creates the ParentChildContext instance (parentChildContext) and sets it to be passed to it’s nested tag helpers (child in our case) by adding the instance to the context.Items dictionary.

In fact, the context argument in the TagHelper.Process method is used to pass data between parent and child tag helpers; Items in the context.Items dictionary are made available to the nested tag helper’s Process method.

The ChildTagHelper exhibits a slightly different Process behavior compared to usual tag helpers; the content it generates is not passed to the output argument, it is instead passed to the ParentChildContext instance. Furthermore the output is forced to be suppressed with the output.SuppressOutput() statement.

The parent tag helper relies on the previously described child behavior to get the content generated by the nested tag helpers. The parent is then responsible for assembling the final content that is going to be rendered in the razor view.

Finally the ParentTagHelper class is slapped with the [RestrictChildren] attribute that allows to specify the children tags that can be nested inside the parent tag helper.

Why defining a custom context?

In the previous example the ParentChildContext class as been created so that its instances act as data transfer objects.

This is necessary as we cannot rely on context.Items to pass data from child to parent. This inability is due to the fact that the TagHelperContext instance (context argument) passed to the child tag helper is actually a copy of the context available in the parent tag helper.

This means that modifications on the context.Items dictionary in the child tag helper does not affect the context.Items in the parent tag helper.

On the flip side, objects referenced by the parent’s context.Items dictionary are available in the child’s context.Items, so we basically do as in ParentTagHelper.ProcessAsync; we create a custom context, register in context.Items and then it can be used by the parent tag helper and its children.