Dependency Injection in Asp.Net Core (Singleton vs Transient vs Scoped)

Dependency Injection in Asp.Net Core is very important for the architecture of the application. Asp.Net Core framework provides built-in support for the Dependency Injection it means there is no need to install any third-party library to implement Dependency Injection.

View or Download sample code

In this Post, You will learn the Dependency Inject in .Net Core with example.

What is Dependency Injection in Asp.Net Core?

Dependency Injection is a principle in software architecture that provides a loosely coupled communication between two classes.

For example the Communication between –

  • Controller and Repositories
  • Controller and Services (Email sender, Logger, etc.)
  • Business layer and Data layer
  • Etc.

The concept of Dependency Injection is same for Asp.Net Core MVC and Asp.Net Core Web API.

Benefits of using Dependency Injection in Asp.Net Core?

There are lots of benefits of using Dependency Injection in your application –

  • IOC Implementation – Dependency Injection is used to implement the Inversion Of Control principle.
  • Dependency Injection provides a Loosely Coupled Communication between two layers (classes).
  • Writing the Unit Test Cases are super easy with Dependency Injection.
  • Help to implement SOLID principles as D in SOLID stands for Dependency Injection.
  • Dependency Injection provides a Flexible Architecture of the application that helps to update the consumed classes without updating the caller classes.
  • Abstraction – As the Controllers are completely unaware of the implementation of services.
  • Etc.

How to configure Dependency Injection in Asp.Net Core?

  • Asp.Net Core provides the built-in support for Dependency Injection.
  • Dependencies are registered in containers and the container in asp.net core is IServiceProvider.
  • The best place to registers the dependency in the asp.net core is the ConfigureServices method of the Startup class.
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    
    // Register the dependencies here
}

Example of Dependency Injection in Asp.Net Core –

using Microsoft.AspNetCore.Mvc;

namespace SampleAspNetCore.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class BooksController : ControllerBase
    {
    }
}
  • Create a new Models folder and add a new BookModel class inside this Models folder.
BookModel in the Models folder
  • Add the following properties in the BookModel class.
public class BookModel
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Price { get; set; }
}
  • Now, Create a new folder with the name Repositories at the root level or a new class library project in the solution (As per your choice or application architecture). And create a new BookRepository class in this folder/ classLib project. I am going to create a new folder at the root level.
Repository layer in asp.net core
Repository layer in asp.net core

We are not using any database in this example. So let’s create some in methods and in-memory data in this BookRepository class.

using SampleAspNetCore.Models;
using System.Collections.Generic;

namespace SampleAspNetCore.Repositories
{
    public class BookRepository
    {
        /// <summary>
        /// Hold in-memory books data
        /// </summary>
        private List<BookModel> books = new List<BookModel>();

        /// <summary>
        /// Add a new book in the books list
        /// </summary>
        /// <param name="book"></param>
        /// <returns>Id of new book</returns>
        public int AddBook(BookModel book)
        {
            book.Id = books.Count + 1; // Create the incremental Id
            books.Add(book);
            return book.Id;
        }

        /// <summary>
        /// Gets all books
        /// </summary>
        /// <returns>All books</returns>
        public List<BookModel> GetAllBooks()
        {
            return books;
        }

    }
}

We need to use this BookRepository class in the BookController to Add and Get the books data.

If we do not want to follow the concept of Dependency Injection then we can simply create the object of BookRepository using new keyword and use both the methods of this BookRepository class.

But, If we are following the concept of Dependency Injection then we need to create an interface IBookRepository for BookRepository. We will only expose this IBookRepository to the controller and because of this the controller will never know about the implementation i.e. BookRepository.

using SampleAspNetCore.Models;
using System.Collections.Generic;

namespace SampleAspNetCore.Repositories
{
    public interface IBookRepository
    {
        int AddBook(BookModel book);
        List<BookModel> GetAllBooks();
    }
}

Make sure to inherit the BookRepository class from IBookRepositoryinterface.

public class BookRepository : IBookRepository

Inject the Dependency using Constructor in Asp.Net Core –

The Repository layer is ready. Now we need to make some changes in the controller class and inject the dependency using constructor.

using Microsoft.AspNetCore.Mvc;
using SampleAspNetCore.Models;
using SampleAspNetCore.Repositories;

namespace SampleAspNetCore.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class BooksController : ControllerBase
    {
        private readonly IBookRepository _bookRepository;

        /// <summary>
        /// Constructor Injection
        /// </summary>
        /// <param name="bookRepository"></param>
        public BooksController(IBookRepository bookRepository)
        {
            _bookRepository = bookRepository;
        }

        [HttpPost("")]
        public IActionResult AddBook([FromBody] BookModel book)
        {
            int id = _bookRepository.AddBook(book);
            return Ok(id);
        }

        [HttpGet("")]
        public IActionResult GetAllBooks()
        {
            var books = _bookRepository.GetAllBooks();
            return Ok(books);
        }
    }
}

In the above code, you can check that we are not using BookRepository anywhere in the HomeController. We have created the types using IBookRepository interface.

If you will run this application at this stage then you will get an error. This is because that at this time even the application also does not know about the implementation of the IBookRepository interface. We can tell the implementation to the application by registering our service in the container.

Dependency Injection Lifetime in Asp.Net Core –

Because we are not creating the repository class object directly in the controller, so Asp.Net Core framework provides us few methods to handle the lifetime of the object.

  • Singleton
  • Scoped
  • Transient

Let’s learn about the implementation and difference among Singleton and Scoped and Transient services.

Singleton Service lifetime in Dependency Injection –

  • Singleton services can be registered using AddSingleton<> method.
  • There will be only one instance of the Singleton service throughout the application.

Let’s open the Startup class and register the BookRepository using Singleton service.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    services.AddSingleton<IBookRepository, BookRepository>();
    // Register other dependencies here
}

Now, we have registered the dependency using the Singleton service and everything is ready to test. Let’s run the application and send a request from any client tool (postman, fiddler, etc.)

Test the Singleton Service –

Send a POST request at http://localhost:xxxx/api/books with the following JSON in the body.

{
    "name": "C#",
    "price": 300
}
Single service request

Just send a few more requests by updating the JSON data in the body. and finally, use the GET call on the same URL.

Get all books

Observations –

  • The same instance of the service is shared among all the HTTP requests because the entire data is stored in memory and we can access it using a separate HTTP request.
  • Once you will restart the application then in-memory data will get lost because there will always be a new instance for each run of the application.

Example with multiple instances –

Let’s validate the Singleton service with multiple instances of same service.

Let’s create one more instance of the IBookRepository in the BooksController.

private readonly IBookRepository _bookRepository;
private readonly IBookRepository _bookRepository2;

/// <summary>
/// Constructor Injection
/// </summary>
/// <param name="bookRepository"></param>
/// <param name="bookRepository2"></param>
public BooksController(IBookRepository bookRepository, IBookRepository bookRepository2)
{
    _bookRepository = bookRepository;
    _bookRepository2 = bookRepository2;
}

Just for testing let’s use both the instances in the AddBook method of BooksController and this time we will perform the following two operations in this same method.

  1. Add the new book using the first instance i.e. _bookRepository
  2. Get all books using the second instance i.e. _bookRepository2

Let’s update the AddBook method of BooksController.

[HttpPost("")]
public IActionResult AddBook([FromBody] BookModel book)
{
    _bookRepository.AddBook(book); // Add book using first instance
    var books = _bookRepository2.GetAllBooks(); // Get all books using secind instance
    return Ok(books);
}

Run the application again send the post request with some data. This time once you will send the POST request (to add a new book), You will receive all the books in the output.

Keep sending at least five requests with updated body JSON and every time in the output you will see all previously added books are available.

Write the output of the HTTP requests with your thoughts in the comment section below this post.

In case you are using Asp.Net Core MVC then to test the multiple instances, you can add the books using the first instance and use the second instance to display all the books by injecting them directly in the View.cshtml

Scoped Service lifetime in Dependency Injection –

  • Scoped services can be registered using AddScoped<> method.
  • A new instance of the service will be created for the new HTTP Request.
  • Example: Let’s say the controller A is using a Scoped service S two times in the same HTTP Request, then there will be only one (1) instance of this S service for this request.

Let’s register the Scoped Dependency in the Startup class.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    //services.AddSingleton<IBookRepository, BookRepository>();
    services.AddScoped<IBookRepository, BookRepository>();

    // Register other dependencies here
}

Note: You can register n number of services in the ConfigureServices method.

Test the Scoped lifetime in Dependency Injection –

We have already setup everything, Now without making any further changes in the code let’s run the application and test the Scoped lifetime.

There is no change in the request also. You can send the previously (That one we were using with Singleton lifetime) request.

Scoped lifetime in Dependency Injection
Scoped lifetime in Dependency Injection

Update the JSON body and send another request.

Scoped lifetime in Dependency Injection example -2
Scoped lifetime in Dependency Injection example -2

Observations:

  • We are getting only the current request data in the response of the HTTP Request. It means the instances are not shared across HTTP Requests.
  • The instances are being shared for the same HTTP Request because we are using _bookRepository to add the new book and _bookRepository2 to get all the books.

Transient Service lifetime in Dependency Injection –

  • Transient services can be registered using AddTransient<> method.
  • A new instance of the service will be created every-time it is requested.
  • Example: Let’s say the Controller A is using a Transient service S 2 times in the same HTTP Request, then there will be 2 separate instances of this S service.

Let’s register the Transient Dependency in the Startup class.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    //services.AddSingleton<IBookRepository, BookRepository>();
    //services.AddScoped<IBookRepository, BookRepository>();
    services.AddTransient<IBookRepository, BookRepository>();

    // Register other dependencies here
}

Test the Scoped lifetime in Dependency Injection –

Because we have not made any change in the Controller and Service so let’s send the HTTP Request with same data.

Transient lifetime in Dependency Injection
Transient lifetime in Dependency Injection

Observations:

  • As per our code structure, there is no output of this HTTP Request in the Transient service. This is because we are using two separate instances of the BookRepository in the same HTTP Request. And as per the Transient service, a separate instance will be created every time it is requested.
  • Transient service does not share data between multiple instances hence it is a valid case in most of the scenarios.

I hope the Concept of Dependency Injection with their lifetimes is clear now 🙂

Watch Free and Complete Tutorial on Asp.Net Core MVC.