Feature Folder Structure in ASP.NET Core

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:

View post on imgur.com

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):


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()


// 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.

31 thoughts on “Feature Folder Structure in ASP.NET Core

  1. I saw your blog article featured on today’s asp.net community standup. I too am a big fan of placing all the UI related files together rather than spreading them out over many directories (views, controllers, viewmodels, etc).

    I have even requested that the asp.net tooling team make add tooling that would help support this. You can read about it here and it’d be great if you could add your voice to the request if you find that the requested tooling would be helpful. https://github.com/aspnet/Tooling/issues/361

    Thanks!

    • Thanks for the comment, Ron. Appreciate you taking the time.

      I like where your head’s at with that proposal. Only thing I don’t like is the controller would be repeated for each view that’s part of your controller, right? Like it would show under Create.cshtml, Edit.cshtml, etc. (in the standard CRUD views). Might be a little confusing, because right now a file only shows up one time in Solution Explorer. Again, I appreciate what you’re going after though.

      I really like your idea of having a shortcut to the View Model when you’re in a view. A lot of times I just scroll to the top and peek definition if I need to tweak the View Model and the file’s not open already. Commented as such in your GitHub issue.

      The Controller has always been a bit of an odd duck as to where to put it and what to do with it, so I feel your pain there. My screenshot has the Controller in the Features folder with the rest of the front-end, but some like to have a Controllers folder to separate it out under the Features/FeatureName/Controllers or something like that.

  2. I hear ya Scott. if a person is really creating separate views for inserting, updating and viewing a record then I can see how they wouldn’t want three controllers for that. But I think it begs a more fundamental question which is: should there be separate views for viewing, creating and editing to begin with?

    In most cases the views for creating and editing are nearly identical and the view for viewing is again just about the same except that the fields are not editable. To me this totally violates DRY. Such an approach means that when a developer wants to add a field to a view model that field has to be added to three separate views just for basic CRUD operations. I know that’s how the default scaffolding works in Visual Studio for MVC, but it’s never made any sense to me and I don’t do it that way.

    I think a much better approach in most cases is to have a single view that can handle inserting the record, updating the record and viewing it. And that view is handled by it’s matching controller. In such a world it starts to make sense that a view, controller and view model are a trio that work together and belong in the same directory. And in such a world, Visual studio should have hot keys for jumping from any one of the three to the other two. I hope my approach makes more sense now.

    • Agree 100% on trying to limit views and provide a better UX. I usually go that route as well (as always, it depends), but I just chose the CRUD views as a simple example. I just don’t find myself only having 1 View in a Controller in general.

      A few examples where I have more than 1 View in a Controller:
      – Grouping like things together (such as Forgot Password, Forgot Username, or grouping public info together in the same Controller such as About Us, Contact Us, etc.),
      – A confirmation view (to confirm what you’re about to submit)
      – Wizard

      I do use CTRL M, CTRL G to swap between Controllers and Views (albeit it’s not perfect like when your Actions can return different Views). Just wish there was another to swap to the View Model, as you said. That would be really nice, and seems like it should be totally doable when on the View given that you have to define the model explicitly Seems like something Mads Kristensen or Resharper should’ve written by now. 🙂

  3. Good post, thanks for sharing. However I always struggle with naming things.

    If I had two logins (roles) , admin/employee and admin can create/edit/delete employees and employees can view/edit do you end up with something like

    /Features
    ./AdminEmployees
    For admin to create, add edit delete
    ./Employees
    For employee to edit/view

    Seems naming is hard, This is what areas was supposed to help us with

    • Rippow – For that my preference is this:

      /Features (or whatever you want to call this root, maybe /Site)
      ./Admin
      Employee.cshtml (I’d use this one view for inserting, updating and viewing
      EmployeeList.cshtml
      ./Employee
      Account.cshtml (this view would be used for the employee to edit and view their info

      Naming is hard, but it’s a super important aspect of organizing a bunch of abstract concepts into something that is intuitive and easily manageable for the developer and user.

      • Why is this pingpack connected to my comment? I find the headline of his linked page offensive.

    • That’s a great question, Josef. I’ve never had the need for that. Read your post though, great solution.

      Based on your example, it seems like the “HeadingBlock” could be a View Component in ASP.NET Core land, which is the replacement to Child Actions, which I used (and abused) in previous MVC versions for components-like things like reusable components and just separating logic on non-reusable components such as navigation. I’ve got a link to View Components above, but here it is again: http://docs.asp.net/en/latest/mvc/views/view-components.html.

      I’m not sure if View Components are a great fit for your scenario though, as I’m not certain what HeadingBlock does. Thanks for the comment and sharing your blog post!

  4. What you do is just change the name of Views folder and then move Controller/css/js to that folder, don’t you?

    We can keep the old name for Views folder and get nearly the same result without Expander class and having full support for future. The only “problem” is that our Features folder is called “Views”. Is ireally a problem?

    • As someone who likes meaningful names… yes it is a problem to me. 🙂 It’s easy enough to configure and it’s most definitely supported for the future. They provide this override specifically so you can do things like this.

      That said, the folder name is less important than the overall of idea of grouping your tightly coupled pieces together.

    • mojodk – That’s a good question. There are a few different options, but the ultimate idea is you’re going to need a tool to bundle up your code and put it in the wwwroot folder. I like to think of the wwwroot folder as the “bin” folder, and treat it just for output, not the original source code.

      You can use the BundlerMinifier used by the Microsoft-shipped ASP.NET Core templates. More on that here: https://github.com/madskristensen/BundlerMinifier/wiki#command-line-interface-net-core. This is also available as a VS extension and will be more comfortable for VS devs who aren’t familiar with JavaScript build tools.

      Or if you want to use a JavaScript-based build tool I would choose Webpack or Gulp. Webpack is definitely the more popular one in the JavaScript community, but it has a fairly steep learning curve. It took me an entire day of diving deep into it for it to click.

      If you’re using Feature Folders in ASP.NET 4.x apps and not ASP.NET Core apps, you could use the BundleConfig. More on that here: https://docs.microsoft.com/en-us/aspnet/mvc/overview/performance/bundling-and-minification .

      Hope that helps. Thanks for the comment!

  5. Hi,

    I think you missed entry in project.json:

    “publishOptions”: {
    “include”: [

    “Features/**/*.cshtml”,

    ]
    },

    If this part is missing app dosen’t work after publish

    • Hi Walter – thanks for the comment. That’s a good point, you will need that if you have the specific path spelled out in your project.json.

      I just typically follow what the default templates do which is “**/*.cshtml”. I think one of the earlier templates had “/Views/**/*.cshtml” hardcoded in before they changed it.

      You shouldn’t need anything special with .csproj files now that VS 2017 and the 1.0 of the tooling have RTM’d. RIP project.json. 🙁 I liked it, but also like the new .csproj changes.

  6. What I ended up doing was treating the Features as a area and moved everything up a level. Thus I had this:

    Features
    -> Area
    -> Controller
    -> Action
    – Page.cshtml

    For example:

    Features
    -> Admin
    -> Customers
    -> List
    – Page.cshtml
    – Page.js (if any)
    – ListController.cs (Just has the Page with the title being returned + any routing logic)
    – ListQueryController.cs (Ajax call to get the list data)
    – DeleteCommandController.cs (Ajax call to delete a customer)
    – etc…

    I’m using CQS here and was making 1 call per controller (since you can break them up and still have the routing work fine) givins us true SRP through the entire chain.

    All the code/css/JS specific to the page ends up in the same folder as everything else with the Page.cshtml automatically being pulled in via the configuration

Leave a Reply to Scott SauberCancel reply