ASP.NET Core Dependency Injection

Mastering the core of building robust and maintainable applications.

Introduction to Dependency Injection

Dependency Injection (DI) is a fundamental design pattern used in ASP.NET Core to manage the creation and consumption of dependencies. It promotes loose coupling, testability, and maintainability by externalizing the responsibility of instantiating objects.

Instead of a component creating its own dependencies, these dependencies are "injected" into the component from an external source, typically a DI container. This makes it easier to swap implementations, mock dependencies for testing, and organize your code.

Core Concepts

Understanding these key terms is crucial for grasping DI in ASP.NET Core:

Working with Services

Services are typically represented by interfaces, promoting abstraction. This allows you to define a contract without specifying a concrete implementation, making your application more flexible.

Example: An ILogger Interface


public interface ILogger
{
    void LogMessage(string message);
}

public class ConsoleLogger : ILogger
{
    public void LogMessage(string message)
    {
        Console.WriteLine($"[LOG]: {message}");
    }
}
            

Service Registration

Before services can be injected, they must be registered with the DI container. This is typically done in the Program.cs (or Startup.cs in older versions) file using the builder.Services collection.

ASP.NET Core provides three primary lifetimes for registering services:

Registration Methods:

Example Registration:


// In Program.cs (or Startup.cs)
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();

// Register ConsoleLogger as a singleton
builder.Services.AddSingleton<ILogger, ConsoleLogger>();

// Register another service as scoped
builder.Services.AddScoped<IScopedService, MyScopedService>();

// Register a transient service
builder.Services.AddTransient<ITransientService, MyTransientService>();

var app = builder.Build();

// ... rest of the application setup
            

Service Resolution

Once registered, services can be resolved and injected into other components, such as controllers, razor pages, or other services.

The most common way to inject dependencies is through constructor injection. The DI container automatically resolves and provides the required services when an instance of the component is created.

Example: Constructor Injection in a Controller


public class HomeController : Controller
{
    private readonly ILogger _logger;
    private readonly IScopedService _scopedService;

    // Constructor injection
    public HomeController(ILogger logger, IScopedService scopedService)
    {
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        _scopedService = scopedService ?? throw new ArgumentNullException(nameof(scopedService));
    }

    public IActionResult Index()
    {
        _logger.LogMessage("HomeController Index action called.");
        _scopedService.PerformAction();
        return View();
    }
}
            

The DI container will look for registrations for ILogger and IScopedService. If found, it will create instances of their registered implementations and pass them to the HomeController constructor.

You can also manually resolve services from the IServiceProvider, though this is less common and generally discouraged in favor of constructor injection.

Best Practices