ASP.NET Core MVC Feature Folders
This post will explain how to extend the ASP.NET Core MVC Razor view engine to support feature folders project structure.
Project Structure
After creating a new ASP.NET Core project, the folder structure will be
Project
- Controllers
- Models
- Services
- ViewModels
- Views
The issue with this structure is when the project grow the number of classes inside each folder will keep growing and the situation becomes messy. That’s why I prefer to use features folders structure where each feature is encapsulated inside a folder with all of its related files including Controllers, Views, Models, Services,…
App
- Features
-- Home
---- HomeController.cs
---- Index.cshtml
---- IndexViewModel.cs
---- Service.cs
Most of the files inside the feature folder will just work without issues considering that most of the classes will be constructed via Dependency Injection.
Extend View Locations
The views doesn’t work with the feature folder structure by default and In order to achieve that structure, The MVC view engine must be extended to search for the views inside the features folder. ASP.NET Core MVC provides a new interface IViewLocationExpander
which is used by RazorViewEngine
instances to determine search paths for a view. The following snippet shows how to implement IViewLocationExpander
to extend RazorViewEngine
to search for views inside the feature folder.
public class FeaturesViewLocationExpander : IViewLocationExpander
{
public void PopulateValues(ViewLocationExpanderContext context)
{
context.Values["customviewlocation"] = nameof(FeaturesViewLocationExpander);
}
public IEnumerable<string> ExpandViewLocations(
ViewLocationExpanderContext context,
IEnumerable<string> viewLocations)
{
var viewLocationFormats = new[]
{
"~/Features/{1}/{0}.cshtml",
"~/Features/Shared/{0}.cshtml"
};
return viewLocationFormats;
}
}
The IViewLocationExpander
has two methods PopulateValues
and ExpandViewLocations
which are invoked in sequence.
PopulateValues
PopulateValues(ViewLocationExpanderContext)
is invoked each time a view is requested and the expected outcome is adding a value insideViewLocationExpanderContext
which could be consumed byExpandViewLocations
.- The value added to
ViewLocationExpanderContext
determine a cache key that decides whether theExpandViewLocations
will be called or not. ExpandViewLocations
will be called in the following cases- No result was found in the cache.
- The values cached based on what was added to
ViewLocationExpanderContext
are different from the last time thePopulateValues
was invoked. - The view was not found at the cached location.
ExpandViewLocations
ExpandViewLocations(ViewLocationExpanderContext, IEnumerable<string>)
invoked to is invoked to determine all potential paths for a view.
Configure RazorViewEngine
The last step is to update the ConfigureServices
inside the Startup
class to add the created expander to the RazorViewEngineOptions
services.Configure<RazorViewEngineOptions>(options =>
{
options.ViewLocationExpanders.Add(new CustomViewLocationExpander());
});
Summary
Using the feature folders could be achieved in three steps:
- Update project folder structure
- Extend View Locations using IViewLocationExpander
- Configure RazorViewEngine to use the IViewLocationExpander