Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

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

0.00/5 (No votes)
4 Oct 2018 1  
In this article we will look at how Dependency Injection works in ASP.NET Core.

Introduction

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:

Background

Before we get started with talking about Dependency Injection(DI), let's 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:

A Quick Recap on the 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 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 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 the 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 a 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, let's 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 the 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 come 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. These 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 option in detail when we look at the code.

Using the Code

Let's create a sample application to see all this in action. Let's take the step by step approach of creating a dummy application where we can see how the dependency injection works 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 the following:

public interface IBooksRepository
{
	List<Book> GetAll();
}

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

public class Book
{
	public int ID { get; set; }
	public string Name { get; set; }
	public string ISBN { get; set; }
}

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

Implementing Contract/interface

To implement the IBooksRepository, let's 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.

public class InMemoryBooksRepository : IBooksRepository
{
	public List<Book> GetAll()
	{
		List<Book> books = new List<book>();

		books.Add(new Book { ID = 1, ISBN = "Test ISBN 1", Name = "Test Book 1" });
		books.Add(new Book { ID = 1, ISBN = "Test ISBN 1", Name = "Test Book 1" });
		books.Add(new Book { ID = 1, ISBN = "Test ISBN 1", Name = "Test Book 1" });

		return books;
	}
}

Now we have a concrete class ready implementing our contract. Let's 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 IServiceCollection to register our dependencies with the built in ASP.NET Core IoC container.

First, let's check what are the possible options for registering the dependency from a object lifetime perspective.

services.Add(new ServiceDescriptor(typeof(IBooksRepository), typeof(InMemoryBooksRepository)));

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.

services.Add(new ServiceDescriptor(typeof(IBooksRepository), 
      typeof(InMemoryBooksRepository), ServiceLifetime.Singleton));
services.Add(new ServiceDescriptor(typeof(IBooksRepository), 
      typeof(InMemoryBooksRepository), ServiceLifetime.Scoped));
services.Add(new ServiceDescriptor(typeof(IBooksRepository), 
      typeof(InMemoryBooksRepository), ServiceLifetime.Transient));

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

services.AddScoped<IBooksRepository, InMemoryBooksRepository>();
services.AddSingleton<IBooksRepository, InMemoryBooksRepository>();
services.AddTransient<IBooksRepository, InMemoryBooksRepository>();

Now that we have seen how we can register the dependencies with possible lifetime options, let's 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.

public void ConfigureServices(IServiceCollection services)
{
	services.AddTransient<IBooksRepository, InMemoryBooksRepository>();

	services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}

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.

[Route("api/[controller]")]
[ApiController]
public class BooksController : ControllerBase
{
	IBooksRepository m_booksRepository = null;

	public BooksController(IBooksRepository booksRepo)
	{
		m_booksRepository = booksRepo;
	}

	// GET: api/Books
	[HttpGet]
	public List<Book> Get()
	{
		return m_booksRepository.GetAll();
	}       
}

With this code in place, we have all the actions required to declare, define, configure and use our dependencies. Let's 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 FromServices for the framework to be able to identify that the dependency needs to be injected for this parameter. Here is the sample code for this:

public List<Book> GetAllBooks([FromServices]IBooksRepository booksRepo)
{
	return booksRepo.GetAll();
}

Points of Interest

In this article, we looked at how 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 someone 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:

History

  • 4th October, 2018: First version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here