How to implement dependency injection in .NET Core 9 Restful API

4 min read

Introduction

A RESTful API is a web service that follows REST (Representational State Transfer) principles, making it easy to interact with through HTTP methods like GET, POST, PUT, and DELETE. In .NET Core 9, building a RESTful API with Dependency Injection (DI) makes your code modular, testable, and maintainable. 

For my article, I will explain RESTful APIs and DI in an easy-to-understand way, showing you how to create and use them effectively in .NET Core 9.

What is a dependency injection?

Dependency Injection (DI) is a software design pattern that helps manage dependencies in an application. Instead of a class creating its dependencies, they are injected from an external source, making the code more maintainable, testable, and scalable.

Benefits of dependency injection

  • Loose coupling: Classes depend on abstractions rather than concrete implementations. 
  • Improved testability: Dependencies can be easily replaced with mocks for unit testing. 
  • Better maintainability: Code is more modular and easier to extend. 

Implementing dependency injection in .NET Core 9

In .NET Core, DI is built into the framework and managed via the IServiceProvider interface. It enables services to be registered in a central container and injected wherever needed. 

Steps to use DI: 

  1. Create an interface defining the dependency. 
  2. Implement the interface in a class. 
  3. Register the service in the DI container. 
  4. Inject the service into the required classes. 

Comparing without and with dependency injection

Without dependency injection

In a tightly coupled approach, dependencies are instantiated directly inside the controller, making it hard to test and maintain:

Code block

[ApiController] 
[Route("api/products")] 
public class ProductsController : ControllerBase 
{ 
    private readonly ProductService _productService; 
 
    public ProductsController() 
    { 
        _productService = new ProductService(); // Hardcoded dependency 
    } 
 
    [HttpGet] 
    public ActionResult<List<Product>> Get() 
    { 
        return _productService.GetProducts(); 
    } 
} 
 
public class ProductService 
{ 
    private readonly ProductRepository _productRepository = new(); 
     
    public List<Product> GetProducts() 
    { 
        return _productRepository.GetProducts(); 
    } 
} 
 
public class ProductRepository 
{ 
    public List<Product> GetProducts() => new() { new Product { Id = 1, Name = "Laptop", Price = 999.99M } }; 
}

Problems with this approach:

  • The ProductsController is tightly coupled to ProductService, which in turn is tightly coupled to ProductRepository. 
  • Any change in ProductRepository affects all dependent classes. 
  • Difficult to test because dependencies cannot be easily replaced. 

With dependency injection

By using DI, we inject dependencies instead of creating them inside the controller.

Code Block

[ApiController] 
[Route("api/products")] 
public class ProductsController : ControllerBase 
{ 
 private readonly IProductService _productService; 
 
 public ProductsController(IProductService productService) 
 { 
   _productService = productService; // Injected dependency 
 } 
 
 [HttpGet] 
 public ActionResult<List<Product>> Get() 
 { 
   return _productService.GetProducts(); 
 } 
} 
 
public interface IProductService 
{ 
  List<Product> GetProducts(); 
} 
 
public class ProductService : IProductService 
{ 
  private readonly IProductRepository _productRepository; 
 
  public ProductService(IProductRepository productRepository) 
  { 
    _productRepository = productRepository; 
  } 
 
  public List<Product> GetProducts() => _productRepository.GetProducts(); 
} 
 
public interface IProductRepository 
{ 
  List<Product> GetProducts(); 
} 
 
public class ProductRepository : IProductRepository 
{ 
  public List<Product> GetProducts() => new() { new Product { Id = 1, Name = "Laptop", Price = 999.99M } }; 
}

Registering dependencies in DI container

To use dependency injection, we must register services in Program.cs:

Code block

var builder = WebApplication.CreateBuilder(args); 
builder.Services.AddScoped<IProductRepository, ProductRepository>(); 
builder.Services. AddScoped<IProductService, ProductService>(); 
var app = builder.Build();

In this case, you can replace the implementation of the ProductRepository very easily.

First you need to create a new implementation

Code block

public class AlternateProductRepository : IProductRepository 
{ 
  public List<Product> GetProducts() => new() { new Product { Id = 2, Name = "Smartphone", Price = 799.99M } }; 
} 

Second, replace the registration of old implementation in DI container (Program.cs)

Code block

var builder = WebApplication.CreateBuilder(args); 
  
// Replace the original implementation 
builder.Services.AddScoped<IProductRepository, AlternateProductRepository>(); 
  
builder.Services. AddScoped<IProductService, ProductService>(); 
var app = builder.Build();

Thirdly, use the new implementation: Since we are injecting IProductRepository, the ProductService will automatically receive the new implementation without any changes in the code.

Unit test

Let’s see how to Dependency Injection in Unit Testing: You can use mocking to replace actual implementation of the class with external libraries, like Moq.

Step one: Install Moq

Code block

dotnet add package Moq 

Step two: Create a Unit Test for Products Controller

Code block

using Moq; 
using Xunit; 
using Microsoft.AspNetCore.Mvc; 
using System.Collections.Generic; 
  
public class ProductsControllerTests 
{ 
    [Fact] 
    public void Get_ReturnsProductList() 
    { 
        // Arrange 
        var mockService = new Mock<IProductService>(); 
        mockService.Setup(service => service.GetProducts()).Returns(new List<Product> 
        { 
            new Product { Id = 1, Name = "Laptop", Price = 999.99M }, 
            new Product { Id = 2, Name = "Smartphone", Price = 799.99M } 
        }); 
  
        var controller = new ProductsController(mockService.Object); 
  
        // Act 
        var result = controller.Get(); 
  
        // Assert 
        var okResult = Assert.IsType<ActionResult<List<Product>>>(result); 
        var productList = Assert.IsType<List<Product>>(okResult.Value); 
        Assert.Equal(2, productList.Count); 
    } 
} 

Step three: Mock the Repository Layer (For Testing Service) 

Code block

public class ProductServiceTests 
{ 
    [Fact] 
    public void GetProducts_ReturnsProductList() 
    { 
        // Arrange 
        var mockRepo = new Mock<IProductRepository>(); 
        mockRepo.Setup(repo => repo.GetProducts()).Returns(new List<Product> 
        { 
            new Product { Id = 1, Name = "Tablet", Price = 499.99M } 
        }); 
  
        var service = new ProductService(mockRepo.Object); 
  
        // Act 
        var result = service.GetProducts(); 
  
        // Assert 
        Assert.Single(result); 
        Assert.Equal("Tablet", result[0].Name); 
    } 
} 

You can see Dependency Injection helps: 

  • Easier Testing: Replace real implementation with mock data. 
  • No need actual database call: Fake data can be used 
  • Faster execution: external dependencies are removed.

Conclusion

Using Dependency Injection in .NET Core 9 makes applications more flexible, testable, and maintainable. By comparing both approaches, it’s clear that DI helps in reducing tight coupling, making it a best practice for building scalable applications. 

However, when you want to scale some projects, even using .NET Core 9 can only help so much, then you need to speak to zen8labs. We have the skills to help scale up anything and allowing us to create something awesome together! 

Chien Nguyen, Software Engineer

Related posts

What are the differences between threads in Python and threads in other programming languages? What is the GIL? Is it always more efficient to use multithreading and multiprocessing over single-threading and single-processing? In this article, I will discuss the limitations of threads in Python and provide recommendations on when to use multithreading, multiprocessing, or stick

4 min read

Introduction  In the previous blog, The CSS Happy Learning Path, we explored the fundamentals of CSS, focusing on the cascade, specificity, and box model. However, understanding these principles is only half the battle—applying them effectively in real-world scenarios is where things get tricky. This blog post is dedicated to applying the concepts we previously discussed. 

6 min read

Introduction Odoo is a powerful, open-source business application platform that allows businesses to manage all aspects of their operations, including sales, purchases, inventory, accounting, and more. The core of any Odoo module is its business models and the relationships between them.   These models consist of fields that can be broadly categorized into Basic (or Scalar)

5 min read