Extending Razor Views in ASP.NET Core

Thursday, February 4, 2016

If you need to add capabilities to Razor views in ASP.NET Core, there are a few different approaches you can take.

@inherits

By default, a Razor view inherits from RazorPage<T>, where T is the type of model as set by the @model directive. With no XML based configuration around, the @inherits directive is now available to change the default base class. Currently, @inherit only appears to work when the base class of the base class is RazorPage<object>, but using a custom base class does give you the ability to define additional properties and methods for a view to consume.

@inherits BaseView
<div>
    ...
</div>
public abstract class BaseView : RazorPage<object>
{              
    public bool IsAuthenticated()
    {
        return Context.User.Identity.IsAuthenticated;
    }
}

Note that one downside to @inherits (other than encouraging a messy inheritance hierarchy) is that the @inherits and @model directives are mutually exclusive, so you can’t use both in the same view. Thus, the “one base class to rule them all” approach is unfeasible. Fortunately, there are better approaches.

@functions

The @functions directive is similar to the @helper directive in previous versions of Razor. ASP.NET generates a class after parsing a view, and code inside of the @functions block are added as members to the class. In most cases you’ll want to add only functions to the generated class, but it is technically possible to add fields, properties, and events.

@functions
{
    public bool IsAuthenticated()
    {
        return Context.User.Identity.IsAuthenticated;
    }

    readonly string message = “test”;
}

<div>
    @if (IsAuthenticated())
    {
        <div>...</div>
    }
    else
    {
        <div>@Message</div>
    }
</div>

Currently there doesn’t appear to be an easy way to share @function blocks across multiple views, but there is one more option, which is, I think, the best option.

@inject

The @inject directive creates a new property in the generated code for a view. ASP.NET will set the property by asking the IoC container for a service that matches the type of the property. With the following Razor code, we’ll inject the IHostingEnvironment service which is available in every web application by default, and use the service to display the current environment name.

@inject IHostingEnvironment Host;


<div>
    Running in @Host.EnvironmentName
</div>

The class ASP.NET generates for a view with an @inject directive looks like the following:

[Microsoft.AspNet.Mvc.Razor.Internal.RazorInjectAttribute]
public IHostingEnvironment Host { get; private set; }

You might see @inject as an abuse of the MVC design pattern. Why inject services directly into a view when I could use the same services inside a controller and build a proper model in a testable manner? A fair criticism, and ‘@inject abuse’ will be a design flaw to avoid. However, a view does need to execute code and there are a reasonable set of services that can help a view. For example, a localization service. In fact, several in-built capabilities of Razor views are injected as services by default, like the Url, Html, and Component helpers. You’ll find the following properties in the code by default for every Razor generated view.

[RazorInjectAttribute]
public IUrlHelper Url { get; private set; }

[RazorInjectAttribute]
public IViewComponentHelper Component { get; private set; }

[RazorInjectAttribute]
public IJsonHelper Json { get; private set; }

[RazorInjectAttribute]
public IHtmlHelper<dynamic> Html { get; private set; }

Having an IUrlHelper injected into a view instead of being inherited from a base class means we are using composition instead of inheritance, which gives us more flexibility and extensibility points than ever before.

But wait, there’s more!

@inject and _ViewImports

ASP.NET core adds a new magic file by the name of _ViewImports. Similar to _ViewStart, this is a file that when placed into a folder will influence all views in the folder hierarchy. One reason for _ViewImports to exist is to provide some default using statements to bring namespaces into scope for all views in the same hierarchy (again, because we have no XML configuration to work with and XML is where we used to specify default namespaces). However, you can also use _ViewImports to register tag helpers, and the @inject attribute also works from _ViewImports. Let’s say you place the following code into a _ViewImports.cshtml file in the Views folder itself. The namespaces and injected properties are available for every view in the Views folder, and all child folders.

@* These namespaces now available to all views *@
@using Microsoft.AspNet.Hosting

@* These services now available to all views *@
@inject IMyViewHelpers Helpers;
@inject IHostingEnvironment Host;

Summary

Forget @functions and don’t use @inherits. The @inject directive allows you to compose functionality into Razor views in a flexible, extensible, and testable manner.


Comments
Comments are closed.

My Pluralsight Courses

K.Scott Allen OdeToCode by K. Scott Allen
What JavaScript Developers Should Know About ECMAScript 2015
The Podcast!