Microsoft Extensions Dependency Injection

Set up dependency injection in .NET with Microsoft.Extensions.DependencyInjection

Microsoft Extensions Dependency Injection is a development skill for setting up inversion of control containers in .NET applications, covering service registration, lifetime management, and dependency resolution patterns

What Is This?

Overview

Microsoft.Extensions.DependencyInjection is the official dependency injection (DI) framework for .NET Core and modern .NET applications. It provides a lightweight, built-in container for managing object creation and lifetime, eliminating the need for external DI libraries in most scenarios. The framework integrates seamlessly with ASP.NET Core, making it the standard choice for enterprise applications and microservices.

This skill teaches you how to register services, configure their lifetimes, and resolve dependencies throughout your application. You'll learn to structure code that's testable, maintainable, and follows SOLID principles without tight coupling between components. By using this framework, developers can focus on business logic rather than boilerplate code for object instantiation and dependency management.

Microsoft.Extensions.DependencyInjection supports a wide range of application types, including web APIs, background services, desktop applications, and even cross-platform solutions. Its extensibility allows integration with other Microsoft.Extensions libraries, such as configuration, logging, and options, providing a unified approach to application composition.

Who Should Use This

Backend developers building .NET applications, architects designing scalable systems, and teams migrating to modern .NET who need standardized dependency management practices. It is also valuable for DevOps engineers and QA professionals who require consistent and testable application setups. Anyone aiming to improve code maintainability, enable easier refactoring, or implement automated testing will benefit from mastering this skill.

Why Use It?

Problems It Solves

Manual object creation leads to tight coupling, makes testing difficult, and creates maintenance nightmares as applications grow. Dependency injection solves these problems by centralizing object creation and managing component lifetimes automatically. It enables loose coupling between services, simplifies unit testing through mock injection, and provides a clear configuration point for your entire application architecture.

By using dependency injection, you avoid hard-coded dependencies, making it easier to swap implementations, mock services for tests, and manage complex dependency graphs. This approach also supports the open/closed principle, allowing new features to be added with minimal changes to existing code.

Core Highlights

The container automatically resolves constructor dependencies based on registered types, eliminating manual wiring. Lifetime management through Singleton, Scoped, and Transient patterns controls how long instances persist. Singleton services are created once and shared throughout the application's lifetime, Scoped services are created per request or scope, and Transient services are created each time they are requested.

Service factories and delegates allow complex initialization logic without cluttering your code. Integration with configuration providers enables environment-specific service registration and settings injection. The framework also supports open generics, enabling generic repository or handler patterns.

How to Use It?

Basic Usage

var services = new ServiceCollection();
services.AddSingleton<ILogger, ConsoleLogger>();
services.AddScoped<IUserRepository, UserRepository>();
services.AddTransient<IEmailService, EmailService>();
var provider = services.BuildServiceProvider();
var logger = provider.GetRequiredService<ILogger>();

This example demonstrates registering services with different lifetimes and resolving them from the provider. The AddSingleton, AddScoped, and AddTransient methods specify how long each service instance should live.

Real-World Examples

Example one shows registering multiple implementations with factory patterns for complex initialization:

services.AddScoped<IPaymentProcessor>(provider =>
{
    var config = provider.GetRequiredService<IConfiguration>();
    var apiKey = config["Payment:ApiKey"];
    return new StripePaymentProcessor(apiKey);
});

Example two demonstrates registering multiple implementations of the same interface for factory selection:

services.AddScoped<INotificationService, EmailNotificationService>();
services.AddScoped<INotificationService, SmsNotificationService>();
var notifications = provider.GetServices<INotificationService>();

You can also use extension methods to organize registrations:

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddMyModule(this IServiceCollection services)
    {
        services.AddScoped<IMyService, MyService>();
        // Register related services
        return services;
    }
}

Advanced Tips

Use IServiceCollection extension methods to create reusable registration modules that encapsulate related services, keeping your startup code clean and organized. Leverage IServiceProvider in factory delegates to access already-registered services, enabling complex dependency graphs without circular reference issues. Consider using named options or configuration binding for environment-specific service customization.

When to Use It?

Use Cases

ASP.NET Core web applications benefit from automatic controller dependency injection and middleware integration. Console applications and background services use it to manage worker lifetimes and configuration access. Microservices architectures leverage it for consistent service registration across multiple projects. Unit testing becomes simpler when services are registered as interfaces, allowing mock implementations during test setup. Modular applications and plugin architectures also benefit from DI for extensibility.

Related Topics

This skill complements configuration management with Microsoft.Extensions.Configuration and logging with Microsoft.Extensions.Logging, forming the foundation of modern .NET applications. It also pairs well with options pattern for strongly-typed settings and health checks for monitoring service health.

Important Notes

While Microsoft.Extensions.DependencyInjection simplifies dependency management in .NET, there are practical considerations to ensure correct usage. Understanding prerequisites, following recommended patterns, and being aware of framework limitations will help avoid common pitfalls and maximize maintainability, especially in complex or high-scale applications.

Requirements

  • .NET Core 2.0 or later, or .NET 5+ runtime installed
  • Reference to the Microsoft.Extensions.DependencyInjection NuGet package
  • Access to modify application startup or composition root code
  • Basic familiarity with C# interfaces and object-oriented principles

Usage Recommendations

  • Register services as interfaces rather than concrete types to enable flexibility and testability
  • Use the appropriate lifetime (Singleton, Scoped, Transient) based on service usage and thread safety
  • Keep service registration logic organized using extension methods or modules
  • Avoid resolving scoped services from singleton instances to prevent runtime errors
  • Leverage constructor injection as the primary method for dependency resolution

Limitations

  • Does not support named or keyed service registrations out-of-the-box
  • Lacks advanced features found in some third-party DI containers, such as property injection or child containers
  • Circular dependencies between services will cause runtime exceptions
  • Service registration order can affect resolution if multiple implementations are registered for the same interface