Dependency Injection in ASP.NET Core

On this page

Dependency Injection (DI) is a design pattern that implements the Inversion of Control (IoC) principle. In ASP.NET Core, DI is a fundamental part of the framework, enabling loose coupling between components and making applications more modular, testable, and maintainable.

What is Dependency Injection?

Instead of a component creating its own dependencies (objects it needs to function), those dependencies are "injected" into the component from an external source, typically a DI container.

Consider a service, EmailService, that needs an ISmtpClient. Without DI, EmailService might create its own SmtpClient instance:

public class EmailService
{
    private readonly SmtpClient _client;

    public EmailService()
    {
        _client = new SmtpClient("smtp.example.com"); // Tightly coupled
    }

    public void SendEmail(string to, string subject, string body)
    {
        // ... implementation using _client ...
    }
}

With DI, the ISmtpClient is injected:

public interface ISmtpClient { /* ... */ }
public class SmtpClient : ISmtpClient { /* ... */ }

public class EmailService
{
    private readonly ISmtpClient _client;

    // Dependency is injected via the constructor
    public EmailService(ISmtpClient client)
    {
        _client = client;
    }

    public void SendEmail(string to, string subject, string body)
    {
        // ... implementation using _client ...
    }
}

Benefits of DI

Service Lifecycles

The DI container manages the lifetime of registered services. The most common lifecycles are:

Registering Services

Services are registered with the DI container in the Program.cs (or Startup.cs in older versions) file using the WebApplicationBuilder.

Example:

// Program.cs

using Microsoft.Extensions.DependencyInjection;

var builder = WebApplication.CreateBuilder(args);

// Register a service as a singleton
builder.Services.AddSingleton();

// Register a service as scoped
builder.Services.AddScoped();

// Register a service as transient
builder.Services.AddTransient();

var app = builder.Build();

// ... rest of the application setup ...

app.Run();

The most common methods are:

Tip: You can also register services without an interface using AddSingleton<MyConcreteService>(), but it's generally recommended to program against abstractions.

Consuming Services

Registered services can be consumed by:

Example: Consuming a service in an MVC Controller

public class HomeController : Controller
{
    private readonly IMyScopedService _scopedService;
    private readonly IMyTransientService _transientService;

    public HomeController(IMyScopedService scopedService, IMyTransientService transientService)
    {
        _scopedService = scopedService;
        _transientService = transientService;
    }

    public IActionResult Index()
    {
        ViewData["ScopedValue"] = _scopedService.GetId();
        ViewData["TransientValue"] = _transientService.GetId();
        return View();
    }
}
Tip: When consuming services in constructors, the DI container automatically resolves all dependencies and injects them.

Built-in Services

ASP.NET Core registers many useful services by default, such as:

You can inject these services directly into your components without explicit registration.

Understanding and leveraging Dependency Injection is key to building robust and scalable applications with ASP.NET Core.