outline
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 has 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.