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:
- 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.
- 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.
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.
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.
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.
Many thanks for the interesting post, I think it will come in handy to me.