Better feature folders

Previously, I wrote an article how to create a Razor view engine which supports feature folders. This view engine had one drawback.

The view engine I described previously required view names to be unique across the solution. For example, you could not have view name "Index.cshtml," but had to use a controller name as a prefix like "HomeIndex.cshtml." It is not very handy.

There is a better way how to resolve views for controllers. I have created a view engine which resolves views by controller path (namespace). I found an article by Imran Baloch which led me in the right direction.

I have created a NuGet package available it.

Install-Package FeaturesViewEngine

The package contains one base class which could be used for your custom view resolution and another class with the default implementation.

The default view engine - DefaultControllerFeaturesViewEngine resolves paths relative to the controller in three locations:

%feature%/{0}.cshtml
%feature%/Views/{0}.cshtml
%feature%/Views/{1}{0}.cshtml

Here {0} is an action name and {1} is a controller name as usual. But %feature% is a custom placeholder which will be replaced by the path to the controller. The path is resolved by the controller's namespace, but it removes assembly name in front of the namespace. For example, you have an assembly My.Web which has a controller like this:

namespace My.Web.Features.Home
{
    public class HomeController : Controller
    {
    }
}

Then the path to the controller's feature will be Features/Home. It removes My.Web from the path.

If you have different view paths relative to the controller, then you can implement your feature engine using the abstract ControllerFeaturesViewEngine class as a base. You can take a default view engine as an example:

public sealed class DefaultControllerFeaturesViewEngine : ControllerFeaturesViewEngine
{
    public DefaultControllerFeaturesViewEngine()
    {
        var paths = new[]
        {
            $"{FeaturePlaceholder}/{{0}}.cshtml",
            $"{FeaturePlaceholder}/Views/{{0}}.cshtml",
            $"{FeaturePlaceholder}/Views/{{1}}{{0}}.cshtml"
        };

        ViewLocationFormats =
            paths
                .Union(ViewLocationFormats)
                .ToArray();

        PartialViewLocationFormats =
            paths
                .Union(PartialViewLocationFormats)
                .ToArray();
    }
}

Create your view engine and modify the paths according to your needs. Here I am using FeaturePlaceholder which is a constant of %feature%.

If you have a custom namespace prefix which doesn't match assembly name, then you can override the NamespacePrefixToRemove method with your prefix override logic.

Summary

This view engine allows having more flexibility when resolving views for controllers. But you have to remember to configure partial view resolving separately as described in Razor view engine for feature folders article.

There is one drawback to this view engine I didn't mention. It disables view caching because of some restrictions of the base MVC Razor engine.