Dependency Injection in .NET Core

Dependency Injection (DI) is a design pattern used to implement Inversion of Control (IoC), which is a principle that allows the framework to manage the creation and lifecycle of your application's objects. In .NET Core, DI is a built-in feature, deeply integrated into the framework's architecture.

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

Why Use Dependency Injection?

Core Concepts

1. Services and Consumers

In the context of DI, a service is any object that provides functionality. A consumer is an object that needs or uses a service.

2. DI Container

The DI container (or IoC container) is responsible for:

.NET Core includes a built-in, lightweight DI container.

3. Registration

Before the container can inject services, you need to tell it which services are available and how they should be created. This process is called registration.

You typically register services in the `Program.cs` file (or `Startup.cs` in older .NET Core versions) using the `IServiceCollection` interface.

4. Lifetimes

DI containers manage the lifetime of injected services. Common lifetimes include:

How to Implement DI in .NET Core

Step 1: Define an Interface and a Service Implementation

It's a best practice to program against interfaces.


public interface IMessageService
{
    string GetMessage();
}

public class ConsoleMessageService : IMessageService
{
    public string GetMessage()
    {
        return "Hello from ConsoleMessageService!";
    }
}

public class EmailMessageService : IMessageService
{
    public string GetMessage()
    {
        return "Hello from EmailMessageService!";
    }
}
                

Step 2: Register Services with the DI Container

In your application's entry point (e.g., Program.cs in .NET 6+):


// For .NET 6 and later (using Minimal APIs)
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews(); // Example for web applications

// Registering services with different lifetimes:
builder.Services.AddSingleton<IMessageService, ConsoleMessageService>(); // Singleton
// builder.Services.AddScoped<IMessageService, EmailMessageService>(); // Scoped
// builder.Services.AddTransient<IMessageService, EmailMessageService>(); // Transient

var app = builder.Build();

// ... middleware configuration ...

app.Run();

// For .NET Core 3.1 and earlier (using Startup.cs)
// public void ConfigureServices(IServiceCollection services)
// {
//     services.AddSingleton<IMessageService, ConsoleMessageService>();
// }
                

Step 3: Inject Services into Consumers

Inject services via the constructor of your classes (e.g., controllers, services, etc.).


public class HomeController : Controller
{
    private readonly IMessageService _messageService;

    // Constructor injection
    public HomeController(IMessageService messageService)
    {
        _messageService = messageService ?? throw new ArgumentNullException(nameof(messageService));
    }

    public IActionResult Index()
    {
        ViewData["Message"] = _messageService.GetMessage();
        return View();
    }
}
                

Common DI Scenarios

By leveraging .NET Core's built-in DI, you can build more robust, testable, and maintainable applications.

Mastering Dependency Injection is key to writing modern, scalable .NET Core applications.