🏗️ Dependency Injection = Clean Architecture Foundation
Modern applications need to be testable and maintainable. Dependency Injection (DI) in .NET Core is the key to building clean, decoupled, and enterprise-ready applications.
❌ Service Locator Antipattern
public class OrderService
{
public void ProcessOrder(Order order)
{
var emailService =
ServiceLocator.GetService<IEmailService>();
emailService.SendConfirmation(order);
}
}
✅ Constructor Injection
public class OrderService
{
private readonly IEmailService _emailService;
private readonly ILogger<OrderService> _logger;
public OrderService(
IEmailService emailService,
ILogger<OrderService> logger)
{
_emailService = emailService;
_logger = logger;
}
public void ProcessOrder(Order order)
{
_emailService.SendConfirmation(order);
}
}
📝 Advanced DI Patterns
// Program.cs - Complete DI Setup
var builder = WebApplication.CreateBuilder(args);
// 1. Core Services (Singleton)
builder.Services.AddSingleton<ICacheService, RedisCacheService>();
// 2. Business Services (Scoped)
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddScoped<IProductService, ProductService>();
// 3. Infrastructure (Transient)
builder.Services.AddTransient<IEmailService, SmtpEmailService>();
builder.Services.AddTransient<ISmsService, TwilioSmsService>();
// 4. Decorator Pattern
builder.Services.AddScoped<IOrderService, CachedOrderService>();
builder.Services.Decorate<IOrderService, LoggingOrderServiceDecorator>();
// 5. Factory Pattern
builder.Services.AddScoped<IPaymentServiceFactory, PaymentServiceFactory>();
builder.Services.AddScoped<Func<PaymentType, IPaymentService>>(
sp => paymentType => paymentType switch
{
PaymentType.CreditCard => sp.GetRequiredService<CreditCardPaymentService>(),
PaymentType.PayPal => sp.GetRequiredService<PayPalPaymentService>(),
PaymentType.BankTransfer => sp.GetRequiredService<BankTransferPaymentService>(),
_ => throw new NotSupportedException()
});
// 6. Options Pattern
builder.Services.Configure<EmailSettings>(
builder.Configuration.GetSection("Email"));
builder.Services.AddScoped(sp =>
sp.GetRequiredService<IOptions<EmailSettings>>().Value);
// 7. Lazy Loading
builder.Services.AddTransient<Lazy<IReportService>>(
sp => new Lazy<IReportService>(
sp.GetRequiredService<IReportService>));
// 8. Keyed Services (.NET 8)
builder.Services.AddKeyedScoped<IPaymentService, CreditCardPaymentService>("credit");
builder.Services.AddKeyedScoped<IPaymentService, PayPalPaymentService>("paypal");
💡 DI Best Practices
- Use Constructor Injection for required dependencies
- Use Property Injection for optional dependencies
- Register services in the correct lifetime (Transient, Scoped, Singleton)
- Use interfaces for abstraction (IOrderService, IEmailService)
- Implement IDisposable for scoped services in long-running operations
“Dependency Injection is the foundation of clean architecture. It makes code testable, maintainable, and promotes the Single Responsibility Principle. Every enterprise .NET application should embrace it.”
