What Happened To @Helpers In ASP.NET Core?

When you port your Razor-based application to ASP.NET Core, whether its an MVC application or a Web Pages application, you might notice that adding a @functions block to your .cshtml file works as expected, but any attempt to add a @helpers block doesn't work at all.

A Helper in pre-ASP.NET Core Razor is a reusable snippet of Razor syntax exposed as a method, intended for rendering HTML to the browser. ASP.NET Core offers a number of other options for creating reusable snippets of HTML - partial pages, taghelpers and view components. The difference between these approaches and a method defined in a @helper block is that the @helper block can be declared in a page. So, while it may not be so reusable across an application, it's a very useful thing if you only want to use the method in one page. It means less switching between files.

You might have a page that includes a number of unordered lists, all requiring slightly different styling. Here's how you might declare a pre-ASP.NET Core Razor helper to assist with that:

@helper RenderList(IEnumerable<string> items, string style){
    <ul>
        @foreach(var item in items){
            <li class="@style">@item</li>
        }
    </ul>
}

This is called within the page using the following syntax:

@RenderList(new[]{"A","B","C"}, "pretty")

The @helper syntax wasn't included in ASP.NET Core. Instead, in ASP.NET Core 2.x or earlier, you could use a Templated Razor Delegate, which has been around since Razor was first introduced. Early examples of templated Razor delegates tend to make use of the dynamic type, probably because Razor itself is rooted in the Web Pages framework which made heavy use of the dynamic type. Therefore it is common to see templated Razor delegate declarations like this:

Func<dynamic, object> template = @<div>@item</div>

The dynamic parameter can represent any type. It is exposed within the Razor template by the special parameter named item. Unlike the @helper example which can be designed to accept any number of parameters, the templated Razor delegate can only take one. If you wanted to apply this approach to replicate the RenderList helper defined earlier, you would create a class to package up the list items and the string. Then you can define your delegate:

@{
    class ListPackage
    {
        public string[] ListItems { get; set; }
        public string Style { get; set; }
    }
    
    Func<dynamic, object> template = @<ul>
        @foreach (var listItem in item.ListItems)
        {
            <li class="@item.Style">@listItem</li>
        }
    </ul>;
}

While the example above shows the template being declared as a local method (in a Razor @{ } block), you can also declare the template in a @functions block. You can use the template in the content part of the page/view like this:

@template(new ListPackage { ListItems = new[] { "A","B","C" }, Style = "pretty" })

The dynamic approach offers the potential of a little flexibility, in that you can pass any object in to the delegate, so long as it has a ListItems property that can be enumerated, and a Style property that can be rendered.

You can take a more strongly typed approach by declaring your template as a Func<[some type], IHtmlContent>. The definition of the template changes to the following:

@{
    Func<ListPackage, IHtmlContent> template = @<ul>
        @foreach (var listItem in item.ListItems)
        {
            <li class="@item.Style">@listItem</li>
        }
    </ul>;
}

This approach can only be employed as a local method. It will not work in a @functions block. You will also need to add a using directive to reference Microsoft.AspNetCore.Html. The usage within the content part of the page remains the same.

ASP.NET Core 3

A few things slipped relatively quietly into ASP.NET Core 3.0, largely outshone by the glare of Blazor. One of those things is a more formal replacement for the Razor helper. You can now include HTML markup in the body of a method declared in a code block as a local method as previously, or in an @functions block. The method should return void, or Task if it requires asynchronous processing. Here is how the list helper is written in ASP.NET Core 3:

@{
    void Template(string[] listItems, string style) 
    {
        <ul>
        foreach (var listItem in listItems)
        {
            <li class="@style">@listItem</li>
        }
        </ul>
    }
}

Much like the old helper approach, the methods can take any number of parameters. The syntax for calling the method in the place where the output is to be rendered differs slightly because it returns void, so it needs to be called in a local code block:

@{ Template(new[] { "A","B","C" },  "pretty" ); }

It should be noted that all of these methods are based on the use of Razor, so they will only work in a Razor (.cshtml) file. They are not applicable in a standard C# class file.

Summary

Most of the time, one of the solutions that involve extra files will be the best option for delivering reusable snippets of HTML. But if your reuse case doesn't extend beyond the current page, templated Razor delegates will serve you well in ASP.NET Core 2.x or lower, while ASP.NET Core sees the introduction of a much neater solution - markup within a method body.