How I Architect My ASP.NET MVC Apps (Circa Now, 2014)

I recently got a text message from a former co-worker of mine named Mark. Mark was embarking on a new project using ASP.NET MVC and wanted to know how I organize the code in my solutions. I replied with a few text messages giving a quick summary, and told him I'd write a post about it on my blog that would go into more detail.

Here's how I architect my ASP.NET MVC solutions. I'm not saying this is the way to do it, only that it's my way. A year from now it may be different. Software development is always evolving.

Before diving into the different layers of my solutions, there are certain design principles I adhere to that I find very useful and should be mentioned. These are outside the scope of this blog post, but if you aren't familiar with any of them, I highly recommend researching them. They are:


LAYERS
Each of the following layers is represented by its own project.

Domain Classes - Pretty self-explanatory, this project contains my domain classes, separated into meaningful namespaces and organized into corresponding folders.

Data Layer - I'm a huge fan of Entity Framework, and this layer contains my Code First DbContexts, Data Migrations code, and database-centric stuff (such as specifying column names and indexes). Prior to Code First, I was using Database First and would also have a SQL Database project containing the database definition.

Repository Layer - Again, pretty self-explanatory. I like the Repository Pattern and make use of it in this layer, with one repository for each aggregate. I'm currently making use of two abstract generic repository classes -- one that provides methods to add, edit, and delete, and another that extends it and exposes a single Persist() method to persist entities which implement an interface used to keep track of state in a disconnected environment (such as a web application where entities aren't always within the scope of Entity Framework's change tracker).

Application Layer - This is where my application logic lives, organized into succinct, cohesive classes. There is typically one application class for each bounded context.

UI Layer - This is my ASP.NET MVC project. Recently, these projects also contain Angular.js code, and unit tests of it using the Jasmine Framework. I've seen examples where people place their JavaScript unit tests in a separate project, but that also requires them to go to certain ends to make that project aware of the JavaScript files they're testing which reside in their UI project. I prefer to keep all my JavaScript in the same project. Any view models I need are also contained in this project, and are not referenced outside of it. Because view models are UI-centric, I perform any view model-centric operations (including adapting them to and from domain objects) within this layer.

Infrastructure Layer - This layer contains classes for infrastructure-related functionality, such as caching and logging.

Unit Tests - This layer contains all of my .NET unit tests (my JavaScript unit tests are in the UI layer as previously noted). I tend to stay away from integration tests and just write pure integration tests, using Moq to mock any dependencies. If any integration tests are necessary, it's important that they don't leave side effects.

Non Layer-Specific Base Classes - So what's this? This layer contains base classes that aren't specific to any specific layer, for the purposes of not having duplicate code repeated throughout the layers. An example of this is a class that contains logical to recursively log inner exceptions. I had found that I was repeating this same logic in different layers, so I added this layer and these base classes to avoid repeating myself (remember -- DRY!).

This is how I do it now. Again, a year from now I could be doing it differently. But right now, this approach works for me, and provides a good, testable, extensible foundation with a good separation of concerns.


Comments

Anonymous said…
Thanks for taking the time to write up how you architect your ASP.NET MVC code. It's straight forward and easy to comprehend. Lot's of food for thought regarding the design principles you referenced as well. Much appreciated. Mark

Popular Posts

Resolving the "n timer(s) still in the queue" Error In Angular Unit Tests

Silent Renew and the "login_required" Error When Using oidc-client

How to Get Norton Security Suite Firewall to Allow Remote Desktop Connections in Windows

Fixing the "Please add a @Pipe/@Directive/@Component annotation" Error In An Angular App After Upgrading to webpack 4

How to Determine if a Column Exists in a DataReader