Isaac.

architecture

Dependency Injection Advanced

Master advanced DI patterns for flexible, testable code.

By Emem IsaacMay 25, 20223 min read
#dependency injection#ioc#aspnet core#design patterns
Share:

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.

Share:

Written by Emem Isaac

Expert Software Engineer with 15+ years of experience building scalable enterprise applications. Specialized in ASP.NET Core, Azure, Docker, and modern web development. Passionate about sharing knowledge and helping developers grow.

Ready to Build Something Amazing?

Let's discuss your project and explore how my expertise can help you achieve your goals. Free consultation available.

💼 Trusted by 50+ companies worldwide | ⚡ Average response time: 24 hours