Razor sections allows us to write a block of code in one view and render it later in another. The typical example would be the
scripts section that takes a script block from the page and renders it in the layout after all common scripts. This works fine until you try to use
sections from the partial view or from the display template.
I wanted to add charts to the Ether reporting tool that I wrote to track KPI metrics for one of my projects at work. The tool generates a few different report types, each of which is a separate .NET class, but they all rendered by one Razor Page. To avoid me having to write switch/case to distinguish between different report types, I use Display Template feature of Razor to do this for me. Naturally, different report types will require different charts to be displayed, some might not have them at all, so, I quickly dismissed the idea of including all necessary scripts into a base page. Those scripts need to be in specific display template, but here is a problem, sections do not work inside partial view or display template.
I quickly remembered that I came across somewhat similar problem back in 2015 when working with Sitecore CMS. There too, you cannot use sections as Sitecore does its custom rendering that completely overrides standard MVC rendering. The solution was found by my colleague at the time, Derek Hunziker, he proposed to write an HtmlHelper that will capture the markup on the page and render it on the layout. While this works fine in Sitecore or in regular AspNet MVC application, it would not work in AspNet Core application as it relays on
System.Web features, like
WebViewPage.OutputStack that are no longer available and hardly would be in the “spirit” of AspNet Core application. While not having
WebViewPage.OutputStack exposed as an API, AspNet Core has one feature that can actually capture markup, Tag Helpers.
Tag Helpers is an adequate addition to the Razor markup. In essence, they meant to replace HtmlHelper class with the more robust way of combining C# and Html. Here is a simple example of a tag helper:
The tag helper part is in
asp-page-handler attribute that is added to the
form html tag. This attribute is a hint to the Razor engine that this tag needs additional processing. The final result will look like this:
Tag helpers are an ideal solution in this case, as they are easy to use, elegant and certainly AspNet Core friendly. In fact, I think, it is hard to find a solution that would be easier to use, since it is literally just adding an attribute to an html tag. To solve this, there need to be two tag helpers, one that will capture the script block and the second one that will render it.
Authoring custom tag helpers is remarkably easy, it can be done in three steps. The first step is to create a class that inherits base
TagHelper class. Second, is to mark this class with
HtmlTargetElement attribute specifying what html element to target, in this case, it is
script, and which attributes to look for. This is needed for Razor to know when to apply tag helper. The third step is to override
ProcessAsync methods that will hold a tag helper logic.
Tag helper can bind attribute value to a C# property, this is done by marking the property with
HtmlAttributeName and specifying attribute name. In the example above there is “Capture” property being bind to the corresponding html attribute. If
ViewContext instance needs to be accessed by the tag helper, it is done by adding a property to a tag helper class and decorating it with “ViewContext” attribute. this is specially injected as it needs to represent the current instance of the “ViewContext”, other dependencies can be injected through normal dependency injection. The content of the tag can be accessed through the
output.GetChildContentAsync method, it returns an instance of
TagHelperContent, to get a string representation of a content,
GetContent method of
TagHelperContent needs to be called. Since the content of the script block is captured for future rendering, it cannot be left as is, because it will be rendered in the middle of a page. To prevent this
output.SuppressOutput method needs to be called.
Here I store captured content in
HttpContext.Items dictionary using
capture attribute value as a key. This is simplified implementation, complete implementation can be found at ScriptCaptureTagHelper.cs
Similar to capture tag helper, render tag helper is implemented in its own class that inherits from base
TagHelper class, but instead of storing current content it reads the value from
HttpContext.Items dictionary, using the same key value and calls
SetHtmlContent on the output object:
This is simplified implementation, complete implementation can be found at ScriptRenderTagHelper.cs
@addTagHelper *, ScriptCaptureTagHelper needs to be added to the
_ViewImports.cshtml file to register tag helpers.
ScriptCaptureTagHelper is the name of the assembly where tag helpers are located. To actually use tag helpers add a
capture tag on the script block in the display template or in the partial view.
In the view where the script needs to be rendered, add an empty
script tag with the
render attribute, specifying the same id as for
The complete implementation is available on the ScriptCaptureTagHelper GitHub page. Also, there is a ScriptCaptureTagHelper NuGet package available for download. Complete implementation has more features then just capturing single script block. It allows capture of multiple blocks under ther same ID with ordering and preserves attributes, which makes possible to capture script reference tag.
Happy coding :)