A feature folder structure is organizing your app by feature as opposed to technology (Controllers, Views, etc.), which is the default structure in MVC. A feature folder structure is easier to maintain than the default structure and even Areas (which is a poor man’s feature folder structure). It looks something like this:
Why would you do this?
Anyone who has built any ASP.NET MVC app of any size often runs into the same problem. You’re implementing a feature, and you end up spending a lot of time bouncing between folders while you’re implementing the feature. You keep opening and closing the Controllers folder, the Views folder, the View Models (or Models) folder, the Scripts folder, the Content/CSS folder, the Shared folder under Views, etc. As your app grows, there are more and more files in these various folders that you have to wade through to find the files you actually need. All the while, you get the sense you’re fighting your folder structure and fighting the default framework conventions.
Wouldn’t it be nice for all these things that make up the feature to live in the same folder? That way, you’re just living in one folder (on the front end anyways, business logic and data access logic is likely in another project), and everything is laid out nicely next to each other. I’ll show you exactly how to do that in ASP.NET Core. Instead of “feature” you could use the word component, module, etc., but I like the word feature best in this scenario. I actually like the word component better as a more consistent descriptor across other web stacks (such as Angular, React, etc.), but because View Components are a new feature of ASP.NET Core, the name “component” might be a bit misleading in ASP.NET Core land. Naming is hard.
I’m not alone in liking this structure
This is not a unique idea. Not even close. Jimmy Bogard does it. Tim Thomas wrote a blog post on it. Matthew Renze wrote a blog post about doing it in ASP.NET 4.x. Even other stack experts like Angular guru John Papa structures his apps this way. And there are many, many other people who like this structure better, even if it goes against the default conventions that Microsoft expects.
If a ton of people do this, why write this blog post?
Because I haven’t seen an implementation of this for ASP.NET Core.
Ok, so how do we do this?
To do this, we simply need to do move some files around and change some configuration about Views. JavaScript, CSS, ViewModels, etc. are not things known by MVC’s conventions, so we don’t have to worry about those, and Controllers are automatically picked up.
1. Rename the Views folder to Features
2. Add a custom ViewLocationExpander to tell ASP.NET where to find the Views
a. Create a folder to house this config. I usually call it Startup Customizations or Infrastructure and place it under the root of your csproj, but you can call it whatever you’d like.
b. Create a new file under this folder called FeatureLocationExpander.cs under the folder from the step above and copy the code at this gist (shown below):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
using Microsoft.AspNetCore.Mvc.Razor; | |
namespace FeatureFolderStructureDemo.StartupCustomizations | |
{ | |
public class FeatureLocationExpander : IViewLocationExpander | |
{ | |
public void PopulateValues(ViewLocationExpanderContext context) | |
{ | |
// Don't need anything here, but required by the interface | |
} | |
public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations) | |
{ | |
// The old locations are /Views/{1}/{0}.cshtml and /Views/Shared/{0}.cshtml where {1} is the controller and {0} is the name of the View | |
// Replace /Views with /Features | |
return new string[] {"/Features/{1}/{0}.cshtml", "/Features/Shared/{0}.cshtml"}; | |
} | |
} | |
} |
3. Wire up the FeatureLocationExpander in Startup.cs
a. Add the code at this gist (shown below) to your ConfigureServices method in your Startup.cs after the .AddMvc()
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Include this after .AddMvc under ConfigureServices in Startup.cs | |
services.Configure<RazorViewEngineOptions>(options => | |
{ | |
options.ViewLocationExpanders.Add(new FeatureLocationExpander()); | |
}); |
4. Move any feature-specific View Models, JS, and CSS to their respective feature folders.
5. Move the Controllers into their respective feature folders and delete the Controllers folder
6. That’s it!
The only other thing you probably want to do is run your CSS and JS through some sort of task runner (to bundle, minify, and copy to wwwroot), whether that task runner’s Gulp, Grunt, or just plain ol’ NPM scripts which seems to be what the cool kids are doing these days. There are plenty of other blog posts that explain how to do that, including on the official ASP.NET Core documentation.
Final Thoughts
This leaves us with a much easier to maintain folder structure that allows us to stay focused and productive while writing code, and not wasting time hunting for files in different folders.
UPDATE 8/24/2017: For Resharper users – click here to figure out how to fix Resharper’s intellisense. Thanks again to Bill Sorensen in the comments below who first pointed out this solution to me.