Creating a SOLID Visual Studio Solution
 
Published: 14 Jun 2011
Unedited - Community Contributed
Abstract
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.
by Steven Smith
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 36953/ 39

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

 



User Comments

Title: :)   
Name: hi
Date: 2011-12-10 6:29:57 AM
Comment:
the sample link broken
Title: :)   
Name: ChrisA
Date: 2011-06-15 11:55:02 AM
Comment:
I agree with everything you said...
Title: :)   
Name: ChrisA
Date: 2011-06-15 11:12:05 AM
Comment:
Sorry... Not meaning to rip your examples, since you are doing a good thing by trying to help people learn...

I just have wanted to have a serious discussion with someone about this since my opinion has been getting pretty strong with all the code I keep seeing that drives me crazy...

:)
Title: We Actually Agree   
Name: Steve Smith
Date: 2011-06-15 11:12:05 AM
Comment:
@ChrisA
I think we pretty much agree. Let me ask and answer some questions myself this time.

Does the sample app in this article *need* any IOC container? Nope. Again, it's just there so people see how it's wired up.

Should you always start with an IOC container? Always? No. Like everything else, It Depends. But if you and your team are comfortable with them and you know the application is going to be of sufficient scope that in your experience one will be beneficial, then it's not a YAGNI violation to start with it. If you're not sure or lack that experience, start without, or gain experience with them in a spike solution (perhaps using my sample here as a starting point).

Do people mis-use and over-use IOC containers? Yes. Can they lead to over-complicated solutions? Yes. Are they rather "trendy" at the moment amongst .NET developers? Yes. Does that mean we definitely should or should not use them? No - use your judgment.

You disagree with much of this?
Title: :)   
Name: ChrisA
Date: 2011-06-15 10:52:29 AM
Comment:
1) I do think they have their uses for this, but I think that in a lot of cases it leads to a lack of understanding of how to properly architect a solution. Yes it makes it easy to resolve dependencies sometimes on complex object graphs, but is that a good thing? Depends... Could lead to someone not understanding how the application works very well and if it is not necessary and adds confusion I say get rid of it...

2) There is a lot of validity in this if you mean a "correct" usage of IOC containers and only when "needed"... I just inherited a project and stripped out StructureMap.. It was a huge issue in the app, served no purpose and had actually confused the previous developer... Someone told him he needed to "wire up" his app with StructureMap... So he did...

You are able to achieve the SOLID principles in your example without the IOC container... Right now I would argue that the IOC container is just an illusion of decoupling....

Remove the reference to Infrastructure and then run your tests or UI... Yeah you are decoupled...

I have used an IOC container in one application and that was to facilitate automated testing in the UI... I didn't need it in any of the unit tests or integration tests... I needed to be able to plugin mock services to what was normally a live application... I needed the ability to instantiate based on the config file... Outside of that, in the extremities of my application (tests, ui) most times I need only 1 ISomething, I know what that ISomething is, I have a ref to the project and I just instantiate it... Why use an IOC container... What is going to change that I am going to want one later? YNGNI?????

I just see a huge amount of overuse and I really think it leads to bad architectural practices...
Title: Questions for you   
Name: Steve Smith
Date: 2011-06-15 10:31:32 AM
Comment:
@ChrisA,
So, two questions for you:
1) Do you think that IOC containers are good at managing object graphs and reducing coupling within complex applications?

2) If so, do you think it's more likely that an application will evolve in a testable and loosely-coupled manner with an IOC container managing these dependencies from the start, or if some months down the road the team needs to decide whether to try and incorporate one into an app that hasn't been using one and thus requires touching a large percentage of the codebase in order to eliminate direct instantiations, etc. (and of course at this point the team is under deadline pressure, etc.)?

I realize the 2nd question is somewhat loaded...
Title: mister pickles   
Name: ChrisA
Date: 2011-06-15 10:21:59 AM
Comment:
I understand the need to include it, but I guess I feel that people rely on IOC containers in bad ways and your example seems to add to the problem...

In your test you know that you need a certain mock/object/etc to fulfill your interface contract (perhaps done through constructor injection for DI...) Just instantiate it in the test... What purpose does the IOC container make... You are still coupled to the IOC container and its config, and this gives you no benefit in the particular test... In the UI, same thing... You add a ref to the project to basically "hack copy" the dll to the directory so you can jump through hoops to load an object through the container, when you won't just use the dll reference you have... For what? unless you need two ISomethings in your UI and your config sets the dependency I can't see the reason to rely on a container so much...

I have continually seen how overuse of an IOC container leads to ridiculously confusing applications... Go without until you need it to solve a problem... Now people start their app with an IOC container... silly...
Title: KISS and Experience   
Name: Steve Smith
Date: 2011-06-15 9:38:37 AM
Comment:
@ChrisA,
I agree with the KISS principle, however my intent with showing the inclusion of the IOC container in this example is to demonstrate to readers how a real-world non-trivial solution looks, while still being small enough to comprehend. Do I need one for this "application"? Of course not. But there are very few examples out there of solution structures that are both small and simple yet include the basic necessities of building a loosely-coupled application, and for any application of reasonable complexity, IOC containers like SM help. Now, if you're not unit testing, not using the strategy pattern, and/or not using interfaces, then of course you can just skip this whole thing and keep building things in the UI->BLL->DAL manner. It works, it's just not easy to test or refactor without touching the database.
Title: master of ceremony   
Name: ChrisA
Date: 2011-06-15 8:25:13 AM
Comment:
IOC containers are overrated... In this instance, you can remove DependencyResolution and StructureMap. Unless you have services/plugins that need resolving or the same EXE needs to be able to be configured differently, I would argue IOC containers add too much complexity and confusion... Boy are they the flavor of the month though...

In your UI, you know what you need, so instantiate the objects and get rid of the IOC container... Same in your tests... When you need an IOC container, then use it... You don't need it, don't use it... Keep your Core/UI/Infrastructure architecture, but KEEP IT SIMPLE SILLY... KISS should be the overarching principle...
Title: (no title)   
Name: James Peckham
Date: 2011-06-14 6:46:32 PM
Comment:
You're right. Excuse my previous post...

i was definitely confused. The diagram showing data and ui referencing business got me 'excited'.

i think i'm trying to link things that have nothing to do with eachother... such as class library/solution layout and SOLID.

Thanks for this post it's got me re-reading rob martin's agile patterns & practices in c# book...chapter 28.
Title: IOC?   
Name: Steve Smith
Date: 2011-06-14 5:44:08 PM
Comment:
@James,
MSTest has fewer external downloads if you already have the correct SKU of VS installed, is what I meant. Though with Nuget, it's less of an issue to pull in dependencies. I'm toying with adding this as a Nuget package if I make it a proper VS installer, at which point I'm sure I'll make a "lite" one and one that has the third-party tools I prefer to use (like NUnit and Moq and AutoMapper).
I don't see how this design "breaks Inversion of Control". I'm not even sure what you mean by that - can you elaborate? Certainly by having everything depend on Core, which houses the abstractions, I'm following DIP, which is all I claim in the article. And my IOC Container works just fine everywhere I need it to, but I don't think you're referring to Inversion of Control in the context of an IOC container...
Title: (no title)   
Name: James Peckham
Date: 2011-06-14 5:39:07 PM
Comment:
Also i believe having your Ui and Data layers depending on your business layer is also incorrect and breaks Inversion of Control. What you really want to have is a project (class library) of UI contracts/abstracts, Business contracts/abstracts, and data contracts/absstracts. Then the dependencies are inverted toward the "top most" package being in control of injecting them.
Title: (No Title)   
Name: James Peckham
Date: 2011-06-14 5:35:27 PM
Comment:
Just curious... why do you make the assertion that MSTest has less dependencies than NUnit? or do you simply mean 'less to download and configure'?

Product Spotlight
Product Spotlight 





Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-04-20 6:24:18 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search