AspAlliance.com LogoASPAlliance: Articles, reviews, and samples for .NET Developers
URL:
http://aspalliance.com/articleViewer.aspx?aId=2064&pId=-1
Creating a SOLID Visual Studio Solution
page
by Steven Smith
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 37150/ 63

Introduction

The SOLID acronym describes five object-oriented design principles that, when followed, produce code that is cleaner and more maintainable.  The last principle, the Dependency Inversion Principle, suggests that details depend upon abstractions.  Unfortunately, typical project relationships in .NET applications can make this principle difficult to follow.  In this article, I'll describe how one can structure a set of projects in a Visual Studio solution such that DIP can be followed, allowing for the creation of a SOLID solution.  You can download the sample solution and use it as a starting point for your new solutions if you like.

Dependency Inversion

The Dependency Inversion Principle states that "High-level modules should not depend on low-level modules.  Both should depend on abstractions.  Abstractions should not depend on details.  Details should depend on abstractions."  What does this mean, in plan old C# English?  Abstractions in this case means interfaces (generally - sometimes base classes).  Details means specific,  concrete implementations of functionality.  High-level modules are your business logic - the big picture of What Your App Does, while low-level modules are the implementation details - How Little Things Are Done.  The goal here is to decouple how things are done from what you need to do, and to make sure your infrastructure implementations depend upon your high level modules and abstractions, not the other way around.

Data Centric Solution Organization

Far too many .NET solutions look something like this:

The black arrows represent project references - dependencies.  Dependencies are transitive, so in this case everything depends upon the Data Access Layer shown in red.  Since this layer is responsible for the details of how it communicates with your database, it's a low-level module full of details.  It's exactly the kind of thing the DI principle is saying NOT to have your high level modules (business logic) depend on.

Domain Centric Solution Organization

A better approach, and the one we'll be building here, is to construct your solution so that your Core or Business Logic project has no dependencies on other projects, like this one:

How do you do this?  Typically by following the Strategy pattern.  You define interfaces in your core project for things like IRepository<Customer> and then you implement these in your Data or Infrastructure project with a particular implementation like SqlCustomerRepository.  Not shown in the above is the reference between UI and Data.  The Data assembly will need to be in the UI's bin folder at runtime in order for it to be used, and a simple way to achieve this is to add the reference from UI to Data.  But you want to avoid at all costs any direct calls from UI to the Data project directly, as this will eliminate the benefits of following the Dependency Inversion Principle (by adding tight coupling between UI and Data).

Solution Organization

Our solution is going to include the following projects:

Core

The Core project holds almost all of the interfaces used, as well as your Model objects, which should be Plain Old CLR Objects (POCOs).

Infrastructure

The Infrastructure project holds your implementations of various interfaces for things that need to communicate outside of your process.  These include web services, database access repositories, email routines, system clocks, file system, etc.  Just about anything that I've discussed on my blog as being a dependency should have its implementation in the Infrastructure project.

Infrastructure depends on Core.

UI

Your UI project.  This might be a web project, Windows Form, WPF, Silverlight, or even Console Application.  You might even have multiple UIs within your solution.

UI depends on Core and Infrastructure (at least transitively).  If you are not using a separate DependencyResolution project, then UI will need to provide the actual implementations from Infrastructure to plug into the interfaces it uses.

 

These are the only projects you *need* to have.  However, I usually include the following as well:

UnitTests

You might create multiple unit test projects.  However, unit tests should not reference Infrastructure, typically, because they are meant to only test your code, not dependent code.  Integration tests (see below) should be used to test how your code interacts with other systems.  If you only create one UnitTests project for your whole solution, you will want it to reference Core and any other project you are testing.

IntegrationTests

Integration tests are automated tests that verify how your application works with other systems (like your database) outside of your running process.  It should reference your Core, Infrastructure, and DependencyResolution projects and should ideally use the same object graph that you will use in production (or something close to it).

DependencyResolution

This project is simply responsible for handling registration of types and interfaces and resolving types that have dependencies.  That is, if you use the Strategy pattern to say that a particular class requires an IFileSystem as part of its constructor, you can wire up the WindowsFileSystem class to the IFileSystem interface in the DR project, and then use the DR project to create your class such that it will automatically have its dependency provided (as a WindowsFileSystem instance).  This project will generally comprise just a few classes and your Inversion of Control Container of choice (e.g. StructureMap, Unity, NInject, etc).

DependencyResolution should reference Core and Infrastructure.

These next screenshots show the sample solution, which includes all of the moving parts, working DependencyResolution, working Integration Tests, and working Unit Tests.  The focus here is on the references between the projects.

 

You can run the sample, which deals with events that have a start and end date and a service that lists current, expired, or upcoming events, either by running the console application or by running the unit or integration tests.  The console application pre-loads a few events and then updates every 15 seconds with a listing of each category of event.  The unit tests verify whether a given test event is active, expired, or upcoming by allowing you to fake the current time.  The integration tests use the system clock and an in memory listing of events, wired up via DependencyResolution, to test the service.

In the interests of keeping the number of dependencies to a minimum, I didn't install a mocking framework (Moq or Rhino Mocks, for example) for use in the UnitTests project.  I recommend running 'install-package moq' from the Package Management Console to get started using Moq if you don't want to hand-write lots of fake implementations.  I also tend to prefer NUnit but stuck with the built in MSTest, in order to keep dependencies to a minimum.

You can learn more about creating a multi-project solution template for VS2010 here.

Conclusion

The SOLID OOP Principles

Download the Sample Application as a ZIP File

By starting with a SOLID foundation, you'll be much better-equipped to produce maintainable code than if you begin with a data-centric solution organization and try to produce loosely coupled code from there.

Steve's Blog | Follow Steve on Twitter

 


Product Spotlight
Product Spotlight 

©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-03-29 8:07:22 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search