A Beginner’s Tutorial On Understanding and Implementing Dependency Injection in ASP.NET Core

In this article we will look at how Dependency Injection works in ASP.NET Core. We will revisit the DI fundamentals and see how Di is being treated as a first class citizen in ASP.NET Core.

The main focus of the article will be talking about DI in ASP.NET core. For the fundamental concepts related to DI, DIP, IoC please refer to the prerequisite article here: An Absolute Beginner’s Tutorial on Dependency Inversion Principle, Inversion of Control and Dependency Injection[^]

Background

Before we get started with talking about Dependency Injection(DI), lets try to understand dependency Inversion Principle(DIP) and inversion of control(IoC). Once we understand these constructs, we will see how Dependency Injection lets us create classes that adhere to these principles.

Important: As mentioned above, the below explanation on DIP, IOC and DI is only the definition part. It is highly advisable to read the following article in case the full understanding of these concepts is not clear: An Absolute Beginner’s Tutorial on Dependency Inversion Principle, Inversion of Control and Dependency Injection[^]

A Quick recap at basics

Dependency Inversion Principle

Dependency inversion principle is a software design principle which provides us the guidelines to write loosely coupled classes. According to the definition of Dependency inversion principle:

  1. High-level modules should not depend on low-level modules. Both should depend on abstractions.
  2. Abstractions should not depend upon details. Details should depend upon abstractions.

Inversion of Control

Dependency inversion was a software design principle, it just states that how two modules should depend on each other. Now the question comes, how exactly we are going to do it?

The answer is Inversion of control. Inversion of control is the actual mechanism using which we can make the higher level modules to depend on abstractions rather than concrete implementation of lower level modules.

Dependency Injection

Dependency Injection is mainly for injecting the concrete implementation into a class that is using abstraction i.e. interface inside. The main idea of dependency injection is to reduce the coupling between classes and move the binding of abstraction and concrete implementation out of the dependent class.

Benefits of Dependency Injections

The major benefit of having DI in place is that we have loosely coupled code. Since no class is dependent on another class and they only know about the contracts, the actual classes can be injected at runtime. This gives us immense benefits in terms of Extensibility, Testability and Maintainability of our code.

We have already seen how dependency injection let us achieve Dependency inversion principle but it is also important to note that having DI also lets us have code that is conforming to Open Closed Principle (OCP) as the classes now only depend on the interfaces and the concrete implementations are getting injected in them. This would mean that all the classes have hooks in place in form of interfaces and new implementations (new concrete classes) can be added easily for new functionality without even modifying the existing code.

IoC Containers

All the three approaches we have discussed for dependency injection are ok if we have only one level of dependency. But what if the concrete classes are also dependent of some other abstractions. So if we have chained and nested dependencies, implementing dependency injection will become quite complicated.

That is where we can use IoC containers. IoC containers will help us to map the dependencies easily when we have chained or nested dependencies.

Understanding Dependency Injection in ASP.NET Core

The good thing about ASP.NET Core is that Dependency Injection is being treated as first class citizen by the framework itself. ASP.NET Core comes with an IoC container out of the box.

In fact the DI is so prevalent in ASP.NET core framework that even the dependencies within the framework like configurations, routing, logging etc are pre-configured to use the inbuilt dependency injection mechanism.

ASP.NET Core support constructor based and function based dependency injection. For constructor based injection we just need to define our constructors accepting contracts/interfaces as the parameter and if the dependency is properly registered, the actual concrete object will get injected automatically. To understand this better, lets first look at the steps involved in configuring the dependency injection in ASP.NET Core and then we will create a small application to see this in action.

Steps required to configure Dependency Injection

  1. Defining Contract/interface – Define the interface that will be the contract for the consuming classes
  2. Implementing Contract/interface – Implement a concrete class adhering to the above contact. This will get injected into the consuming classes.
  3. Registering Services – Registering the contract/interface to concrete class mapping so that the framework knows what concrete class should be injected for which interface.
  4. Implementing Services using Contract/interface – Passing the interfaces as parameters in constructor of controllers to let the framework inject actual concrete class at runtime.

Now before we get started with the actual implementation, we need to understand one important thing regarding third point above (Registering Services) e.g. Service lifetime.

Understanding Service Lifetime

When we are registering a service i.e. a mapping of interface to concrete class, the framework will take care of instantiating the concrete class and injecting it in the consuming classes. Now a few questions comes to mind after hearing this:

What will be the lifetime of the instantiated class? Can we control the lifetime of these classes that are getting instantiated?

The answer to these questions is YES. ASP.NET core provides various ways of registering the service dependency that will ultimately determine the lifetime of the object that is being instantiated. There are of three types:

  • Singleton: As the name implies, only one instance will be created with this option and this instance will be shared by all consuming objects.
  • Transient: Components are created every time they are requested. Instances are never shared with other objects.
  • Scoped: Here the object instance will be created for every HTTP request. So from the application perspective, every request has its own object but from the consuming classes perspective, we can think of it as REQUEST-SINGLETON.

We will see how to use this options in details when we look at the code.

Using the code

Lets create a sample application to see all this in action. Lets take the step by step approach of create a dummy application where we can see how the dependency injection work in ASP.NET core.

Defining Contract/Interface

Let’s start with defining the contract that will be used in our application. Let’s take an example of creating a repository for retrieving a list of books from the database. The sample interface for this repository will look like following:

The model that is being used in this interface is the Bookmodel.

With the basic contract/interface in place, lets move on to the actual implementation of the repository.

Implementing Contract/interface

To implement the IBooksRepository, lets create a concrete class called InMemoryBooksRepository. This repository will simply add a few books on the books collection and will return this collection back to the caller.

Now we have a concrete class ready implementing our contract. Lets now see how we can register this dependency.

Registering Services

To register the dependencies we need to look for the ConfigureServices(IServiceCollection) in our Startup class. We need to use the Add() method of IServiceCollectionto register a our dependencies with the built in ASP.NET Core IoC container.

First lets check what are the possible options for registering the dependency from a object lifetime perspective.

When we use this Add method, the default service lifetime is singleton. If we want to override the behavior, we need to pass the behavior also as a parameter.

Apart from using the add method and passing the lifetime option, we can also use the extension methods provided by framework to configure the dependency with the required lifetime in following manner:

Now that we have seen how we can register the dependencies with possible lifetime options, lets see what makes sense for our current example. Since we want our repository class to be instantiated every time it is being requested, we will configure this as Transient only.

Now we have our dependency registered with the built in IoC container. Let’s now look at how consuming classes should be written so that the object gets injected in them.

Implementing Services using Contract/interface

We know that the built in container supports the constructor based injection. So we need to create a controller where the constructor is taking this interface argument. We can then use this interface handle to call the actual repository functionality.

With this code in place, we have all the actions required to declare, define, configure and use our dependencies. Lets run the application and see the results in action.

A note on method based injection

In case we want to have the method based injection, the method parameter should be decorated with an attribute FromServicesfor the framework to be able to identify that the dependency needs to be injected for this parameter. Here is the sample code for this.

Point of interest

In this article, we looked at how we the dependency injection is a first class citizen in ASP.NET Core framework. We also looked at how we can use DI in ASP.NET core with all possible lifetime options. This has been written from a beginner’s perspective. I hope this has been somewhat informative.

Also, in case some wants to get an idea of how IoC containers work internally, here is a small rudimentary IoC container I wrote just for fun. This can be used to get an idea of internal workings of IoC containers: Lets write a Tiny IoC Container to learn(and for fun)[^]

Please download the sample code from here: ASPNETCoreDISample