outline

Tag Helpers Rendering
Part 2

In the previous post, I discussed how to setup an exploration environment that allows to step into and over the execution of the ASP.NET Core framework and specifically in the Mvc and the Razor projects code bases in order to understand the Tag Helpers rendering mechanism.

The tag helpers used in cshtml views are rendered by the C# classes generated.The ability to follow its execution step by step proved to be useful.

At the end of the last post, I showed the C# class originating from a very simple cshtml file.

Let’s now analyze its execution starting with the generated razor page.

Razor Page Class

Here is the simple cshtml file that we are considering:

The previous chtml view is compiled to the following C# code:

You will notice that the previous code listing was formatted, actually I removed all the #line and #pragma directives and I simplified the fully qualified class names, which makes for code that is less painful to read.

Observations

After scanning the previous code we can make some observations:

First off, I did not know that it was possible to put using statements inside a namespace scope to include name spaces. Every day is a school day…

Then, The generated _Views_Home_Index_cshtml class inherits from the RazorPage abstract class parametrized with the dynamic type. Here the dynamic type is used because the view is not strongly typed.

_Views_Home_Index_cshtml overrides the ExecuteAsync method in which the template rendering logic is located.

Finally, some properties in the _Views_Home_Index_cshtml are set up to contain instances of Tag Helper related classes: TagHelperExecutionContext, TagHelperRunner, TagHelperScopeManager and ImageTagHelper.

Diving in ExecuteAsync

We are going to focus on the tag helpers related objects, specifically on the following snippet(further tweaked for readability) extracted from the ExecuteAsync method:

The TagHelperScopeManager Creates a TagHelperExecutionContext

The TagHelperScopeManager.Begin method returns a TagHelperExecutionContext after taking the following arguments:

  • The name of the to be generated tag
  • The mode of the to be generated tag (Open-close, self-closing…)
  • A UniqueId string
  • A function object that executes any nested tag helpers

Looking inside the TagHelperScopeManager class shows that it encapsulates a TagHelperExecutionContext instances pooling behavior which might be there to allow reuse of existing instances.

The responsibility of the scope manager through the Begin method is to perform checks on the arguments it gets passed, to create a dictionary object needed by the TagHelperExecutionContext and to delegate the instantiation of the TagHelperExecutionContext to the ExecutionContextPool private class.

By default, the scope manager creates a new items Dictionary object. But if it detects that a parentExecutionContext exists for the current scope it creates a CopyOnWrite dictionary that is initialized with the parent’s item dictionary (parentExecutionContext.Items).

The arguments listed earlier along with the created items dictionary are passed to the ExecutionContextPool object that either returns a freshly created TagHelperExecutionContext or a reinitialized existing one.

The TagHelperScopeManager.End seems to be used in nested tag helpers scenarii, although not sure, it looks like it keeps track of levels of nesting.

So to make it short the scope manager creates a TagHelperExecutionContext.

Tag Helper Execution Context

The TagHelperExecutionContext object is used to contain the created tag helpers and their attributes.

In our example, the ExecuteAsync method creates two tag helpers: the expected ImageTagHelper and a UrlResolutionTagHelper.

The creation of the UrlResolutionTagHelper is a bit strange, in our example we just made use of the ImageTagHelper.

Here we must remember that tag helpers can be activated by attribute and by html element name. It turns out that the UrlResolutionTagHelper gets activated on the img html element, among others, in order to resolve relative assets path.

Both the UrlResolutionTagHelper and the ImageTagHelper as well as their html attributes are added to the TagHelperExecutionContext via the Add, AddHtmlAttribute and AddTagHelperAttribute methods.

The responsibility of the tag helper execution context is to hold the state related to multiple tag helpers that gets activated on the same html element.

The constructor of the TagHelperExecutionContext class accepts the following arguments:

  • The name of the tag that is going to be generated
  • The mode of the tag that is going to be generated (Open-close, self-closing…)
  • The Items dictionary created by the scope manager.
  • The UniqueId string
  • The function object that executes any nested tag helpers
  • The startTagHelperWritingScope callback
  • The endTagHelperWritingScope callback

Then it creates a TagHelperOutput object and a TagHelperContext object which will be respectively assigned to the Output and Context public properties. All the elements needed to fulfill the previous instantiation are passed to TagHelperExecutionContext’s constructor.

The previous instances will be necessary in order to call the ProcessAsync method on the tag helpers that needs to be rendered.

The two last arguments of the TagHelperExecutionContext: startTagHelperWritingScope and endTagHelperWritingScope are callbacks defined under the RazorPageBase class and are used in the TagHelperExecutionContext.SetOutputContentAsync which executes the nested tag helpers if any. The responsibility of these callbacks is not clear yet, some more on this next time.

Rendering The Tag Helpers

After being populated the with the tag helpers objects and their attributes the TagHelperExecutionContext is passed to the TagHelperRunner.RunAsync method which performs the following actions:

  1. Orders the tag helper objects contained in the context(recall the TagHelper.Order property)
  2. Iterates over the ordered tag helpers and calls TagHelper.Init on them
  3. Iterates over the ordered tag helpers and calls ProcessAsync on each one. The TagHelperOutput and TagHelperContext contained in the execution context are used.

The result of the processing is available in the TagHelperExecutionContext.Output property and is further written to the current page by the RazorPage.Write.

Looking inside the Write method shows that the TagHelperOutput.WriteTo is used to generate the final html content.

Summary

The complexity introduced by the collaboration between the TagHelperScopeManager, TagHelperExecutionContext and TagHelperRunner is necessary to handle multiple tag helpers activation on the same html element as well as to manage nested tag helpers processing.

Nested tag helpers processing is still not clear and is going to be the subject of an upcoming post some day.