Moving to ASP.NET Core:
Replacing HTML Helpers that have Dependencies

The HTML Helper is born

To make it easier to add content to a view, you can take advantage of something called an HTML Helper. You can use HTML Helpers to generate standard HTML elements such as textboxes, links, dropdown lists, and list boxes. They make your life easier by reducing the amount of HTML and script that you need to write.

The documentation from the original HTML Helper implementation makes it pretty clear that they were originally intended to promote the reuse of HTML…I mean, they even have HTML in their name. 😉

Over the years, I’ve seen custom HTML Helpers used to nicely encapsulate some pretty heinous blocks of repeated HTML.

However, I’ve also seen them pushed past their original intent because, in some instances, ASP.NET didn’t natively offer a more elegant solution for the needed functionality inside the Razor views.

The Issue

HTML Helpers are implemented as static extension methods on static classes. As such, it’s best that they not have state nor should they require dependencies outside of those passed as arguments from the Razor views.

In ASP.NET, it is/was fairly common practice to create HTML Helpers to do things like clean or localize strings in a Razor view. However, when that HTML Helper needs to access an external service or database to complete its business, then we have a dependency …and so does every Razor view using that HTML Helper.

Service Locator

By far, the pattern I’ve seen most used in the wild to resolve a needed dependency inside an HTML Helper is Service Locator. This pattern is generally implemented similar to this:

public static async string GetCleanString(this IHtmlHelper helper, string key)
{
    // grab the concrete implementation of ICleaningService 
    //  from some container that we assume is setup correctly.
    ICleaningService cleaningService = Container.GetService<ICleaningService>();

    return await cleaningService.GetCleanString(key);
}

This actually still works in ASP.NET Core with the built-in Dependency Injection container, but there is a better option in ASP.NET Core as you’ll see below.

What’s wrong with Service Locator?

Service Locator has mostly been branded an anti-pattern for a decade or two. However, a quick online search will show arguments both for and against the usage. As with anything in software development, it depends. The main issue generally cited by those against its use are that it hides the underlying dependencies.

In some cases, the HTML Helper code above is inside a Nuget package or is compiled code that the caller can’t access. When the developer calls the Html.GetCleanString() from inside their Razor view, they are trusting that there are no dependencies in order to clean their string and, if there are, that some other process has taken care of configuring them. That’s a pretty big assumption and unfortunately it’s not until runtime that they’ll find out if that trust was misplaced.

The Service Locator pattern also hinders the testing of the HTML Helper for the same reason that the dependencies are not known at compile time. Fortunately, ASP.NET Core now offers an elegant way to avoid the Service Locator pattern in ASP.NET Core HTML Helpers.

View Injection

That’s right, you can now inject into your Razor Views! It’s very straight-forward requiring the simple addition of the @inject keyword followed by the Interface and a variable name. You can then access it in your Views exactly as you would any other C# code.

@inject ICleaningService cleaningService

<div>
    @*  Don't forget to await any async calls  *@
    @await cleaningService.GetCleanStringAsync(DIRTY_STRING)
</div>

...

Replacing HTML Helpers with Tag Helpers

While HTML Helpers will still work in later versions of ASP.NET Core, the guidance is to instead move to Tag Helpers. The Microsoft documentation around Tag Helpers offers a good comparison of HTML Helpers and Tag Helpers and outlines some of the other deficiencies in HTML Helpers. However, in some very large conversions, a wholesale move from HTML Helpers that do not have dependencies to Tag Helpers may not be practical or within budget. Targeting only the HTML Helpers with dependencies (as outlined above) has been a successful strategy for us on recent engagements.

Just Give Me the Codez

I created a quick sample project demonstrating Dependency Injection into Razor views. GitHub Repo for Replacing HTML Helpers that have Dependencies

More Information