In a previous post, I dived into view results, and how the razor view engine executes a razor view. Part of the post discussed a feature called view location expanders.

An expander does what its name describes. It expands on locating a view within your application, that is, a way to change how your .cshtml files are located.

View Localisation

A good example of a view location expander, is the built-in LanguageViewLocationExpander. It allows the view engine to search for culture-aware views. To use the language view expander, you can add it using startup configuration:

services.Configure<RazorViewEngineOptions>(
    options => options.ViewLocationExpanders.Add(
        new LanguageViewLocationExpander(
            LanguageViewLocationExpanderFormat.SubFolder)));

With the expander enabled, searching for the typical Home -> Index view will search in the following locations:

Views/Home/en/Index
Views/Home/Index
Views/Shared/en/Index
Views/Shared/Index

The ASP.NET team have also provided a helper method for this expander:

services
    .AddMvc()
    .AddViewLocalization(
        LanguageViewLocationExpanderFormat.SubFolder);

This achieves the same as the previous example.

Adding localisation to our views, means that we can serve different content and hard-coded text based on the locale of our user. This is very powerful, as website content can be focused for specific cultures.

A Custom Example

Creating your own view location expander is straight-forward. It requires the implementation of the IViewLocationExpander interface:

public interface IViewLocationExpander
{
    IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations);

    void PopulateValues(ViewLocationExpanderContext context);
}

The interface declares two methods. The PopulateValues method gives us a chance to take the current context and extract data from it. The ViewLocationExpanderContext class itself provides the action context, and so we can retrieve all the associated data for a request. The ExpandViewLocations method allows us to manipulate and extend existing view locations.

The following is an example of a location expander that checks for a route value with key “alt”. If a value exists, then it searches within the folder named after the route value:

public class AlternativeViewLocationExpander : IViewLocationExpander
{
    private const string ValueKey = "alt";

    public IEnumerable<string> ExpandViewLocations(
        ViewLocationExpanderContext context,
        IEnumerable<string> viewLocations)
    {
        context.Values.TryGetValue(ValueKey, out var value);

        if (string.IsNullOrWhiteSpace(value))
        {
            return viewLocations;
        }

        return ExpandViewLocationsCore(viewLocations, value);
    }

    private IEnumerable<string> ExpandViewLocationsCore(IEnumerable<string> viewLocations, string value)
    {
        foreach (var location in viewLocations)
        {
            yield return location.Replace("{0}", value + "/{0}");
            yield return location;
        }
    }

    public void PopulateValues(ViewLocationExpanderContext context)
    {
        context.Values[ValueKey] = context.ActionContext.RouteData.Values[ValueKey]?.ToString();
    }
}

The populate values method uses the context to retrieve the route data value. If the value does not exist, then the value will evaluate to null.

The ExpandViewLocations method does nothing if no value was specified. The folder name searched for is the value of the route data. Given a route value of “alt-views”, the following view locations will be searched by the razor view engine:

/Views/Home/alt-views/Index.cshtml
/Views/Home/Index.cshtml
/Views/Shared/alt-views/Index.cshtml
/Views/Shared/Index.cshtml

To use this view location expander, you can add it to the RazorViewOptions class, similar to the default localisation expander:

services.Configure<RazorViewEngineOptions>(
    options => options.ViewLocationExpanders.Add(
        new AlternativeViewLocationExpander()));

Summary

With a simple implementation, we have extended the razor view engine, and provided a unique way of finding views.

Location expanders are useful for multi-tenancy, localisation, and context-sensitive view lookup.

What other use-cases can you think of?