Demystifying Dependency Injection in .NET
So what is “dependency injection” anyway?
Dependency injection is one of the five principles described by Robert C. Martin in his SOLID design proposal. It basically 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.
After reading that it should make perfect sense right?
Basically what that means is you should write code that does not care about what objects are, only that they have a certain shape.
Consider the following
Lets say you need to create a web application that searches a 3rd party music service. It seems very simple so you will likely layout your solution like this:
MusicSearch.sln
MusicSearch.Web
One project that contains all the code you need, everything from gathering the data from the 3rd party music service to surfacing that data in an MVC application. The one stop shop for all your needs, right?
What happens when you need to unit test functionality specific to the MVC application (i.e testing routes, designing the layout, etc)? What happens when you decide to switch to another 3rd party music service? In either of those cases you will have to perform some pretty heavy lifting in your already completed and working application. What you need is a way to make your code more modular, and easier to maintain. Thats where dependency injection comes in.
If I knew then what I know now
With dependency injection in mind let’s look at our solution structure again:
MusicSearch.sln
MusicSearch.Service.Infrastructure
MusicSearch.Service.Spotify
MusicSearch.Web
As you can see we added two new projects.
- MusicSearch.Service.Infrastructure will contain interfaces on what our service looks like.
- MusicSearch.Service.Spotify will be how we will specifically implement the interfaces for use with Spotify.
So let’s define our service. For our needs we simply want to get artist, albums and songs based on a query.
This interface will live in the MusicSearch.Service.Infrastructure project. This project will also contain our definitions for Album
, Artist
and Song
and anything else that is a shared piece of our applications infrastructure.
Now, in our MusicSearch.Service.Spotify project we will write a concrete class that implements our IMusicSearchService
interface.
So there we have a concrete class that takes the shape of our interface. What now?
In our MVC project MusicSearch.Web, we can now code everything off our interface without caring at all how its implemented. We all know interfaces are contracts for our code so lets use them as such. Take a look at our controller now.
You see how our code now only references the IMusicSearchService
interface instead of a concrete class? It is indifferent to what _service
ultimately is, all it needs to care about is whatever our interface tells it to. Now we have the flexibility to to make any version of our service (so long as it takes the shape we have defined). The only thing we have left to do is tell our MVC code what concrete class to use. For that we need to inject our dependency.
This is where the magic happens
You simply need to create a constructor that takes an argument of type IMusicSearchService
. Seriously, that’s it.
Anytime an instance of your controller is created, an argument of type IMusicSearchService
has to be passed in. This could be done by simply creating a new object and passing it as a parameter, but then we still have our code dictating what concrete to use. Thankfully there is another way; IoC containers. IoC containers are complex, but for our purposes today all you need to know is that it takes care of instantiating objects for you. We just have to tell it how.
Microsoft has included an IoC container in ASP.NET Core, for the purpose of this post I will be showing how to inject dependencies using the built in tool.
Navigate to the Startup.cs
class and look for a method called: ConfigureServices()
, this is where we will define our bindings.
Basically this line of code says that anytime our code needs a “IMusicSearchService”, use “SpotifyMusicSearchService”. We do have other options for scope besides “Transient” (Scoped, Singleton), but for our purposes we will use “Transient”.
Endless possibilities
Remember the problems we dreamt up earlier?
- What happens when you need to unit test functionality specific to the MVC app?
- What happens when you decide to switch to another 3rd party music service?
Both of those are quite easily solved now. We can now create “mock” services that simply just return some dummy data for the purpose of testing our MVC app. If we decide to switch from Spotify to another 3rd party service, we simply create a new project with the new service’s specific code. All thats needed is to change our bindings in the IoC container:
Can you see the benfits of writing modular code? As long as we always code against abstracts, we make our code better.