A Feature-Oriented Directory Structure For C# Projects

After working on .NET applications for the past six years, I recently spent a few months using Ember.js and AngularJS. Both originally supported organizing files in a project by type: separate top-level directories for models, controllers, views, etc. But this has changed over the past few years to prefer organizing by feature area—Ember with pods Angular with modules.

It’s worked well for Javascript apps, so I recently experimented by converting one of my C# apps to follow the same principles.

Angular Project Layout

Consider the structure of the angular-seed app:


app/                    --> all of the source files for the application
  app.css               --> default stylesheet
  components/           --> all app specific modules
    version/              --> version related components
      version.js                 --> version module declaration and basic "version" value service
      version_test.js            --> "version" value service tests
      version-directive.js       --> custom directive that returns the current app version
      version-directive_test.js  --> version directive tests
      interpolate-filter.js      --> custom interpolation filter
      interpolate-filter_test.js --> interpolate filter tests
  view1/                --> the view1 view template and logic
    view1.html            --> the partial template
    view1.js              --> the controller logic
    view1_test.js         --> tests of the controller
  view2/                --> the view2 view template and logic
    view2.html            --> the partial template
    view2.js              --> the controller logic
    view2_test.js         --> tests of the controller
  app.js                --> main application module
  index.html            --> app layout file (the main html template file of the app)
  index-async.html      --> just like index.html, but loads js files asynchronously
karma.conf.js         --> config file for running unit tests with Karma
e2e-tests/            --> end-to-end tests
  protractor-conf.js    --> Protractor config file
  scenarios.js          --> end-to-end scenarios to be run by Protractor

Note that instead of grouping view1.html and view2.html together in a views directory, they’re placed beside the corresponding controller and tests. You can read more about comparing this with the traditional Angular project structures in this post by Cliff Meyers. I’ll use the rest of this post to discuss the concepts in a .NET context.

Typical C# Project Structure

Here’s an example of your typical WPF MVVM project structure. There’s a Models directory, a Views directory, and a ViewModels directory. Easy, right? If you need to find the Customer model, go check the models directory.


App.sln
  Proj1.csproj
    Views/
      Customer.xaml
      Order.xaml
    ViewModels/
      CustomerViewModel.cs
      OrderViewModel.cs
    Models/
      Customer.cs
      Order.cs
    Common/
      Utilities.cs
      SharedCode.cs
  Proj1UnitTests.csproj
    ViewModels/
      CustomerViewModelTests.cs
      OrderViewModelTests.cs
    Models/
      CustomerTests.cs
      OrderTests.cs
    UtilityTests.cs

What happens if we need to add a new feature for shopping cart functionality? We just add the new ShoppingCart.cs tests and model, the new viewmodel files, and finally the view itself. This looks like 90+% of the .NET projects I’ve ever come across. How does it compare to a module oriented approach like that of the angular-seed app?

Feature-Oriented C# Project Structure


App.sln
  Proj1.csproj
    Customer/
      Customer.cs
      Customer.xaml
      CustomerViewModel.cs
    Order/
      Order.cs
      Order.xaml
      OrderViewModel.cs
    Common/
      Utilities.cs
      SharedCode.cs
  Proj1UnitTests.csproj
    Customer/
      CustomerViewModelTests.cs
      CustomerTests.cs
    Order/
      OrderTests.cs
      OrderViewModelTests.cs
    UtilityTests.cs

Now our directories are organized by feature (Customer, Order) instead of by type (Model, View, ViewModel). Need to find the Customer model? It’s right where you’d expect, in the Customer directory. Instead of being beside other models on disk, it’s now co-located with the other Customer files that actually use it. What if we need to add in new shopping cart functionality? Just create the new ShoppingCart*.cs files all in the same folder and follow the same pattern for the tests.

If using the feature approach appeals to you, I think there’s one more logical step to take. The project structure above still has a duplicate directory tree for the tests. What if we move the unit tests beside the code they’re testing?


App.sln
  Proj1.csproj
    Customer/
      Customer.cs
      CustomerTests.cs
      Customer.xaml
      CustomerViewModel.cs
      CustomerViewModelTests.cs
    Order/
      Order.cs
      OrderTests.cs
      Order.xaml
      OrderViewModel.cs
      OrderViewModelTests.cs
    Common/
      Utilities.cs
      UtilityTests.cs
      SharedCode.cs

Now it’s trivial to find related files without needing to traverse the entire app’s directory structure. It’s also clear exactly which files have corresponding unit tests and which ones are missing. Further, if we add in the ShoppingCart files, they all land in a single folder.

Handling Namespaces & Deployment

There are two related issues that still need to be addressed:

  1. Should namespaces still match the directory layout on disk? For a small example project it won’t matter much either way. For now, code in my Common directory is in the default namespace where it is automatically in scope for any source code in nested namespaces. As my project grows, I’ll see what works best.
  2. Do you deploy the unit tests in your dll? For my current app there’s no harm in doing so and it’s default result unless I put in extra effort. In a more commercial/public facing project, the safe answer is to just #ifdef your test code and exclude it for production builds. If references added for testing dlls aren’t actually used by code in the production compilation, the compiler can skip emitting those references in the output dll.

If this feature-oriented approach becomes more popular for .NET code, I’d expect the tooling to improve to support it directly. With the new project.json files for dnx projects, the production build could simply be to include *.cs and exclude *Test.cs files, whereas the test build could default to include all files.

Conclusions

Using a feature-oriented project structure appears as appropriate if not more so than the traditional .NET project structure. I’ll be continuing to experiment with it as my new default going forward. It’s simpler for many scenarios, and I expect it will scale as projects grow since features can easily be refactored into sub-features that use the same recursive directory layout.

 
Conversation
  • Dax Fohl says:

    I completely agree. Even going one step further, I like to put them all in the same file, to be better able to focus on all aspects of that feature at once, and to get rid of the mouse overhead of expanding and collapsing folders. Of course this isn’t possible when there’s a XAML file in with it, but I’ve been migrating away from XAML and just doing all my WPF in code anyway. Beyond a certain amount of complexity, I find XAML works against me as much as it does for me.

    • Dax Fohl says:

      Also I prefer to keep everything in the root namespace. I tried hierarchies, but then it’s tempting to make classes e.g. Root.Feature1.ViewModel and Root.Feature2.ViewModel, which leads to conflicts when using both features from the same file. So simpler namespace hierarchies and more explicit class names has worked out better for me.

      • Dax Fohl says:

        Not to beat a dead horse, but what’s the purpose of namespaces anyway? A last resort at conflict resolution. That’s the only reason they exist. It would be great if they could be used as a lightweight way of preventing cyclical dependencies. I used to use them that way to kind of psuedo-exemplify my dependency structure. But as much as one would like them to work this way, they don’t, and so it’s best not to imagine they can or use them as though they could. You *can* use them to “group” similar things for intellisense, but that’s really only applicable if coders typically fully type out the namespace each time in code rather than using “using” declarations at the top of the file (which of course we never do).

        Beyond that, all they really do is obscure functionality and frustrate people. Case in point, how many times have you been working with JSON, using `Newtonsoft.Json`, and then need something from `Newtonsoft.Json.Linq` but it’s not in scope? I can see how it makes logical sense to separate them, but the actual benefit to the end coder is almost zero whereas the cost to the end coder is, well, higher than if it were all just a single namespace. And that’s only two namespaces. Many other libraries are much worse.

        These days I just put everything into the project’s root namespace, and I’ve been very satisfied with that approach. *Occasionally* if I have multiple implementations of an interface I’ll lump them into a namespace, for “intellisense” purposes, though that’s on a case-by-case, cost-benefit basis. I guess doing a cost-benefit analysis of namespacing is what it comes down to.

  • Ian Brooks says:

    Many thanks for the interesting post, I think it will come in handy to me.

  • Comments are closed.