A Gentle Introduction to Dependency Injection

How to reduce complexity while making testing easier in .NET 5

Manfred Lange
Jan 24 · 13 min read

In this article:

  • Benefits of Dependency Injection (DI) and Inversion of Control (IoC)
  • What is the problem to be solved?
  • Basic DI concepts
  • DI in a console application
  • Autofac in an ASP.NET web application in .NET 5.0
Image for post
Image for post

Introduction

As software engineers we are continuously tackling complexity. Any technique that makes our lives easier is welcome. Dependency injection (DI), which is one way to achieve Inversion of Control (IoC), is one such technique. It helps us breaking complex software components down into smaller, much more manageable pieces of code. In this article I’ll provide a gentle introduction to using Dependency Injection in .NET 5.0.

When we write software, we want our code to be easy to maintain and easy to test. Typically, this is the case, when we make sure that a given piece of code does exactly one thing and one thing only. By “thing” I mean one piece of program logic. Other “things” are then the responsibility of other pieces of code. This is also called the single responsibility principle, which is one of the SOLID design principles.

In object-oriented programming languages such as C# we can use a class to represent a single responsibility. Unfortunately, for a meaningful software component we have to implement many classes and those classes have dependencies between them.

While we will not be able to remove the dependencies, we can deal with them in different ways. DI is one design technique that helps us managing dependencies in a flexible way.

Let’s first look into the problem, though, in a bit more detail.

Source Code

The entire source code for this article is available at https://github.com/RimuTec/gentle-di

The only prerequisites are .NET 5.0 which you can download for your platform from https://dotnet.microsoft.com/download/dotnet/5.0 and an IDE, e.g., VS Code.

What is the Problem to be Solved?

As a simple example, we’ll look at a controller class that handles incoming web requests. The controller requires some business logic which is implemented in a service class. The service class, in turn, uses a repository class to write and read data from some storage, e.g., some sort of a database.

Image for post
Image for post

The code for the controller class may look something like this (code simplified to keep it simple):

Image for post
Image for post

In line 7, this code creates a new instance of the service class which may look something like the following:

Image for post
Image for post

Here, too, the code instantiates a new repository object in line 7. This doesn’t look too bad. So, what is the problem with these classes?

Maintainability

The first problem is: What if for some reason we want the controller class to use a different implementation of the service? In that case we would have to change existing code because in line 7 of the controller class we would have to use the constructor for the different implementation.

One incarnation of this first problem can be custom solutions where we need a different service implementation for each of our paying clients. Obviously, we want to reuse as many of our other components as possible.

Testability

The second problem and my favorite is: How can we test the controller without having to instantiate the service? And how are we going to test the service without having to instantiate the repository? The answer to these two questions is: We can’t. Both are hard-wired to their dependencies because they explicitly state the name of the class they instantiate.

Why this is an Issue

Firstly, Systems where classes are hardwired to each other are harder to change. Requirements can and will change over time. As a result, we need to change how our system is implemented. If classes can be combined in different ways more easily, time-to-market is reduced.

Also, by using the single responsibility principle combined with decoupling classes, we can reduce the amount of existing code that needs to be changed to meet the new requirements.

Secondly, imagine, you had a large set of tests and all of them would have to indirectly interact with a database server. Instead of running several thousand tests in a minute, you may be able to execute only a few hundred. Slow running test suites put a penalty on testing. As a result, developers will execute them less often.

Speed of tests is also important when we consider the overall build and deployment pipeline. Imagine you had to deploy a code change quickly, the deployment process will be slowed down if the test suite takes a long time to run.

Therefore, testing a single class in isolation has clear benefits.

So, with all of these issues, how could we improve this code? Ideally, we’d like to use an option that addresses both, maintainability and testability. Dependency Injection allows us to do precisely that.

Injecting a Dependency

As a first step we can change the service class by passing an instance of the repository as a constructor parameter:

Image for post
Image for post

With this step, the service class no longer instantiates the repository class. This is an improvement as we no longer use “new” to create the repository instance.

Now the repository has to be instantiated elsewhere, though. As a consequence, we change the controller class accordingly:

Image for post
Image for post

That looks better but it still has a problem: The constructor is tied to a very specific implementation of the PurchaseOrderService. To address this issue, we can extract an interface from class PurchaseOrderService.

Using Interfaces for Better Decoupling

The new interface is called IPurchaseOrderService and looks as follows:

Image for post
Image for post

The class PurchaseOrderService changes only slightly:

Image for post
Image for post

In line 3 the class PurchaseOrderService now specifies that it implements the interface “IPurchaseOrderService”. The introduction of the interface allows us to change the constructor of the controller class to the following:

Image for post
Image for post

Notice how we have changed the parameter type in the constructor (line 5) as well as the type of the private field (line 17) to “IPurchaseOrderService”. As a result, we no longer tie the controller class to a specific implementation of the service class. All the controller class expects is an object that satisfies the interface.

This also means, we can now pass objects to the constructor that just “pretend” to be a purchase order service. This is useful for writing tests. For example, we could inject a mock or a fake instead of the “real thing”. For instance, a repository mock wouldn’t access the database and therefore run much faster. By introducing the interface and by being able to pass any object that implements that interface to the controller class, we have decoupled the controller class from the specific implementation of the service class.

Extracting an Interface from the Repository Class

Similar to what we did for the service class we are going to extract an interface from the repository class as well. This interface looks as follows:

Image for post
Image for post

We change the repository class accordingly:

Image for post
Image for post

In the service class we need to adjust the type of the parameter passed to the constructor and and the type of the field for the repository as well:

Image for post
Image for post

Both our controller class and the service class now look much better. They no longer depend on a specific implementation of their respective dependency.

We still need to answer the question where the service and the repository will get instantiated, and how all the objects will be “wired up”.

That is what we will look at next.

Dependency Injection in .NET 5.0

The good news is, injecting dependencies has become a very popular design. In fact, .NET Core, the open-source and cross-platform version of .NET, is built from the ground up using DI. The newest version .NET 5.0 uses DI as well. Let’s find out how we can leverage what comes out of the box.

Dependency injection comes in different flavors, depending on how a dependency is injected. In this article we’ll be using constructor injection. Other options include property or method injection. .NET Core and it’s successor .NET 5.0 use constructor injection.

DI Containers

We also need to have a rough understanding of the container concept in DI. Simplistically speaking, a DI container is a runtime context that takes care of instantiating and garbage collecting components as needed. A DI container has a list of all available components, how many instances should exist of each of them, what their dependencies are, how long each of the instances should exist, etc.

There are different implementations of DI containers (you can find a comprehensive list at the end of this article). Different DI container implementations have different feature sets. .NET 5.0 and in particular ASP.NET allow plugging in your favorite DI container framework. Here we’ll use Microsoft’s flavor first and then look at Autofac.

In general, there are three steps to set up DI:

  1. Configure all dependencies, aka components or services
  2. Create a service provider
  3. Requesting an object from the service provider

In the case of ASP.NET this looks slightly differently:

  1. Plug-in your favorite DI container
  2. Configure all dependencies, aka components or services

We’ll look at an example of this a little later in this article.

Configuring Dependencies

To use Microsoft’s DI we need to add the NuGet package “Microsoft.Extensions.DependencyInjection” with the following command, which we execute in the folder containing the file CmdLine.csproj:

dotnet add package Microsoft.Extensions.DependencyInjection

Then we add a static method “ConfigureServices()” to the class “Program”:

Image for post
Image for post

This method receives an IServiceCollection as a parameter. In line 23 we register the PurchaseOrderRepository and in line 24 the PurchaseOrderService.

The registration of the controller class in line 25 looks a bit different. Notice, how the first template parameter is not an interface (see orange arrow). Remember that our controller class does not implement an interface. This is typical for a controller class in an ASP.NET application.

Note that in a typical ASP.NET application we don’t have to register controllers. The framework does that for us.

Simulating a Post Request

Once we have configured the services, we can simulate a post request with as little as two lines of code:

Image for post
Image for post

The service provider is passed as a parameter. In line 17 we request an instance of the controller. In line 18, we then call the “Post()” method that we implement earlier.

The “GetRequiredService<>” call handles instantiating the required controller and all it’s direct or indirect dependencies. This call replaces using “new”. By relying on the DI framework, all dependencies are resolved and instantiated as needed.

The complete class “Program” now looks as follows:

A word of caution: If you find a lot of calls to “GetRequiredService<>()”, this usually indicates a code smell. See “Conforming Container” link in the reading list for further details.

Dependency Injection with Autofac

The Microsoft extension “Microsoft.Extensions.DependencyInjection” is not the only option we have available. Let’s look at another option. I like Autofac in particular because it can be plugged in easily into ASP.NET as well to replace Microsoft’s DI package. In addition, Autofac has a feature called “Type Interceptors” which can be used for a design approach called Aspect-Oriented Programming (AOP). I’ll cover aspects with Autofac in a future article.

Swapping to Autofac

Using Autofac instead of Microsoft’s DI package is quite simple. Removing Microsoft’s package is done by running the following command in the folder that contains CmdLine.csproj:

dotnet remove package Microsoft.Extensions.DependencyInjection

Then we add Autofac with the following command:

dotnet add package Autofac.Extensions.DependencyInjection

We replace the method ConfigureServices with the following ConfigureContainer():

Image for post
Image for post

Instead of using AddScoped(), Autofac uses RegisterType() but otherwise, the registration is pretty similar (see lines 23 to 25). With the exception in line 25 (orange arrow) where we do not specify an interface for the controller. In line 26 we create the DI container, which is essentially an object from which components and services can be resolved.

Autofac separates between components and services. A service is essentially an interface. A component is a class that implements one or more interfaces, so provides those services. In Autofac parlance “RegisterType<T>()” registers T as a component whereas the “As<TInterface>()” call registers the service “TInterface” as a service that this component offers.

The method to simulate the post request changes slightly as well:

Image for post
Image for post

In line 15 we start a new scope within the container. Let’s talk briefly about scopes here.

It is possible to resolve services directly from the container. This is what we did when we used the Microsoft DI container. We could do the same with Autofac as well. The problem, however, is that if did that, the DI container wouldn’t be able to tell how long to keep an instance of a service around. Scopes address this problem. A service instance is tied to the scope. When the scope is no longer required, all service instances associated with the scope will be garbage collected as well.

In this code example the scope starts in line 15 and once execution leaves the method, the scope will be garbage collected along with the services and components resolved from that scope. If we continued resolving components and services without a scope, i.e., directly from the container, we would eventually run out of memory.

Here is the class “Program” with the changes for Autofac:

Using Autofac with ASP.NET 5.0

Now that we know that both DI containers offer similar functionality, let’s have a look at how we can substitute Microsoft’s DI container with Autofac in ASP.NET 5.0. To get started, we’ll use a new boilerplate ASP.NET application. The complete source code is included in the git repository for this article. See https://github.com/RimuTec/gentle-di

Generating the ASP.NET application is easy. Just go to the root of the repo, then execute the following command:

dotnet new mvc -n WebApp

Then add the new project to the solution file with

dotnet sln add ./WebApp/WebApp.csproj

Next, we need to tell the application that we want to use Autofac as the DI container. To accomplish that we change the boilerplate code in “Program.cs” from:

Image for post
Image for post

to the following:

Image for post
Image for post

All we have to do in this method is adding one line that tells the host builder to use Autofac (line 22). Here is what the Program class looks like now:

The second piece is a controller that makes use of dependency injection. Fortunately, the boilerplate code already contains a class named HomeController. The constructor expects a logger to be injected. We’ll use this controller for demonstration purposes and add as a second dependency “IPurchaseOrderService”.

For the implementation of IPurchaseOrderService we’ll copy the code from the CmdLine example that we used in the first part of this article.

Then in the Startup class we add the following method and leave everything else unchanged:

Image for post
Image for post

The method will be called by convention. The parameter type ContainerBuilder (line 55) is the class from Autofac that we used earlier in the CmdLine example as well. In lines 57 and 58 we register the repository. The complete Startup class now looks as follows:

In essence, we have made two changes to the boilerplate code:

  1. Added one line in Program.cs to tell the framework that we want to use Autofac
  2. Added one method in Startup.cs to register the services

Armed with that we can now add the service to the controller constructor as a dependency:

Image for post
Image for post

The complete class “HomeController” including a new action method that makes use of the service looks as follows:

And that’s all! From here you can make use of all features that Autofac has to offer, including interceptors which I will demonstrate in a future article.

Summary

We covered quite some ground in this article. We started with a simple console application where the few classes we had were tightly coupled. Using dependency injection, we decoupled the classes and made them easier to test. For example, we could now test the controller without having to instantiate a repository.

Then we looked at using the default dependency injection in .NET 5.0 and how we can implement DI with Autofac.

Finally, we looked at using Autofac in an ASP.NET web application in .NET 5.0. All we needed were one line of code in one class and one method in a different class. No other code change was required. Easy as!

In a future article, we’ll be looking at how we can use an Autofac interceptor to implement transactional behavior for different classes such as controllers, services and repositories.

Please post your questions and suggestions as responses to this article below. Thank you!

Thank you for reading!

Bonus: List of .NET DI Frameworks

Here is a list of DI frameworks that might be worth a look. All of them implement Microsoft.Extensions.DependencyInjection, which means they can be plugged into ASP.NET. Before making a decision, you should check and compare their feature sets as well as other factors such as performance, whether they are well maintained, licensing and similar more.

The Startup

Medium's largest active publication, followed by +775K people. Follow to join our community.

Manfred Lange

Written by

I’m a Principal Consultant at boutique firm HYPR Innovation in New Zealand. Currently, my main focus is helping clients to build scalable SaaS products.

The Startup

Medium's largest active publication, followed by +775K people. Follow to join our community.

Manfred Lange

Written by

I’m a Principal Consultant at boutique firm HYPR Innovation in New Zealand. Currently, my main focus is helping clients to build scalable SaaS products.

The Startup

Medium's largest active publication, followed by +775K people. Follow to join our community.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store