Isaac.

Dependency Injection Advanced

Master advanced DI patterns for flexible, testable code.

By EMEPublished: February 20, 2025
dependency injectioniocaspnet coredesign patterns

A Simple Analogy

Advanced DI is like a smart HR department. Instead of employees bringing their own tools, HR provides exactly what each person needs, when they need it, from a central warehouse.


Why Advanced DI?

  • Flexible composition: Change implementations without code changes
  • Testability: Inject mocks for unit tests
  • Decoupling: Components don't depend on implementations
  • Lifecycle control: Manage object lifetimes precisely
  • Factory patterns: Complex object creation

Service Lifetimes

// Transient: New instance every time
services.AddTransient<IUnitOfWork, UnitOfWork>();
// Good for: Stateless services, repositories

// Scoped: One per request (default in web)
services.AddScoped<IOrderService, OrderService>();
// Good for: DbContext, services managing state

// Singleton: One instance forever
services.AddSingleton<ICache, MemoryCache>();
// Good for: Caches, configuration, logging

builder.Services.AddScoped<IOrderService>(provider =>
{
    var db = provider.GetRequiredService<DbContext>();
    var logger = provider.GetRequiredService<ILogger<OrderService>>();
    return new OrderService(db, logger);
});

Factory Patterns

// Factory delegate
services.AddScoped<IOrderService>(provider =>
{
    var environment = provider.GetRequiredService<IHostEnvironment>();
    return environment.IsProduction()
        ? new ProductionOrderService()
        : new DevelopmentOrderService();
});

// Keyed services (named services)
services.AddScoped<IRepository>("orders", _ => new OrderRepository());
services.AddScoped<IRepository>("users", _ => new UserRepository());

var orderRepo = provider.GetKeyedService<IRepository>("orders");

// Generic factory
public class ServiceFactory<T> where T : class
{
    public T Create(IServiceProvider provider)
    {
        // Complex creation logic
        return ActivatorUtilities.CreateInstance<T>(provider);
    }
}

Decorator Pattern

// Original service
public interface IOrderService
{
    Task<Order> GetAsync(int id);
}

public class OrderService : IOrderService
{
    public async Task<Order> GetAsync(int id) => await db.Orders.FindAsync(id);
}

// Decorator adds caching
public class CachedOrderService : IOrderService
{
    private readonly IOrderService _inner;
    private readonly ICache _cache;
    
    public async Task<Order> GetAsync(int id)
    {
        var key = $"order-{id}";
        var cached = _cache.Get<Order>(key);
        if (cached != null) return cached;
        
        var order = await _inner.GetAsync(id);
        _cache.Set(key, order, TimeSpan.FromHours(1));
        return order;
    }
}

// Register
services.AddScoped<OrderService>();
services.AddScoped<IOrderService>(provider =>
    new CachedOrderService(provider.GetRequiredService<OrderService>(), cache));

OpenGenericServices

// Register handler for all types
services.AddTransient(typeof(IHandler<>), typeof(Handler<>));

// Usage
public interface IHandler<T>
{
    Task HandleAsync(T item);
}

public class Handler<T> : IHandler<T>
{
    public async Task HandleAsync(T item) { }
}

// Automatically resolves Handler<Order>, Handler<User>, etc.
var orderHandler = provider.GetRequiredService<IHandler<Order>>();

Module Pattern

public class RepositoryModule : ServiceCollectionExtension
{
    public static IServiceCollection AddRepositories(
        this IServiceCollection services)
    {
        services.AddScoped<IOrderRepository, OrderRepository>();
        services.AddScoped<IUserRepository, UserRepository>();
        services.AddScoped<IProductRepository, ProductRepository>();
        return services;
    }
}

// Usage
builder.Services.AddRepositories();

Best Practices

  1. Register abstractions: Always use interfaces
  2. Manage lifetimes: Choose appropriate scope
  3. Use factories: For complex object creation
  4. Avoid service locator: Always inject dependencies
  5. Consider decorators: Add cross-cutting concerns

Related Concepts

  • Castle Windsor container
  • Autofac container
  • Service locator pattern (avoid)
  • Property injection

Summary

Advanced DI enables flexible, testable architectures. Master factory patterns, decorators, and keyed services to build maintainable, loosely-coupled applications.