Using the old HtmlHelper inside tag helpers

In a recent asp.net core project, I needed to create a somewhat complex list editor widget and I decided to encapsulate it in a tag helper.

Since it would be painful to build the widget's content using interpolated strings under the tag helper's Process method, I started searching for a way to render a cshtml template similar to what @Html.Partial providers but usable inside the Process method.

After doing some googling in vain, I posted an issue on the asp.net core Mvc project to suggest adding a mechanism for rendering cshtml templates from tag helpers. frankabbruzzese pointed out then in the discussion that it was possible to get a working IHtmlHelper inside a custom tag helper and to use its IHtmlHelper.Partial method.

In this post we are going to see how to:

  • get and properly initialize an Htmlhelper in a custom tag helper
  • pass data to the cshtml template

As usual the full project code is available on Github. But first off let's discuss why you should not do this.

Razor view components

When I was building the list editor widget I was not aware of the new Razor View Component mechanism , which basically allows to render a partial template, to pass it data and to do some processing before the actual rendering.

It seems that using View Components is a cleaner approach as we are going to see in an upcoming post.

But still one might need to use an HtmlHelper inside a tag helper maybe for some reason I am not anticipating. That's why I want to share what I learned.

Getting an IHtmlHelper through Dependency Injection and properly initializing it

By using the dependency injection mechanism in asp.net core it is possible to get an IHtmlHelper instance, the only catch is that the provided instance is not ready for use; it needs to be contextualized i.e. to be set with the ViewContext object of the view in which the tag helper is rendered.

This is achieved by the (htmlHelper as IViewContextAware).Contextualize(ViewContext); statement which needs the current ViewContext instance.

In the previous example the TagHelper constructor will be used by the dependency injection context to provide an IHtmlHelper instance and the ViewContext property slapped with the [ViewContext] attribute will be populated by the current ViewContext. Notice how the html helper is contextualized at the start of the ProcessAsync method.

The HtmlHelper is then used to render a cshtml view (~/Views/Shared/Template.cshtml in the example), by calling the HtmlHelper.Partial method.

Passing data to the view

Passing data to the razor view is similar to passing data to the Controller.View method, you have basically 3 ways:

  • passing a model object to a strongly typed view
  • populating the ViewData dictionary
  • populating the ViewBag expando object

Under the tag helper ProcessAsync method it is possible to use the ViewData and ViewBag properties contained in the HtmlHelper instance.

Notice how the model as been passed explicitly and how the ViewData and its expando object facade the ViewBag are passed implicitly. The data is then accessed in the rendered razor partial template as in the following example:

Creating a new ViewData dictionary instance

One last thing: While creating the list editor widget, I had to actually create a new instance of the ViewData dictionary instead of using the one in the HtmlHelper instance. I will skip the details of the issue I encountered in the interest of brievety.

In asp.net core the most convenient constructor that allows to create an instance "from scratch" in the ViewDataDictionary class has the following signature: ViewDataDictionary(IModelMetadataProvider metadataProvider, ModelStateDictionary modelState).

The required IModelMetadataProvider instance can be requested through dependency injection and the ModelStateDictionary can be instantiated by using its parameterless constrcutor.