C# & ASP.NET Topic
C# Dependency Injection & SOLID Principles
SOLID Principles Foundation
1. Single Responsibility Principle (SRP)
csharp
// VIOLATION: Class doing too much
public class BadUserService
{
public void RegisterUser(string email, string password)
{
// Validate input
if (!email.Contains("@")) throw new ArgumentException("Invalid email");
if (password.Length < 6) throw new ArgumentException("Password too short");
// Save to database
var connection = new SqlConnection("connection_string");
connection.Open();
// ... save user
// Send email
var smtpClient = new SmtpClient();
smtpClient.Send("welcome@company.com", email, "Welcome!", "Welcome message");
// Log activity
File.WriteAllText("log.txt", $"User {email} registered at {DateTime.Now}");
}
}
// CORRECT: Separate responsibilities
public class UserValidator
{
public ValidationResult Validate(string email, string password)
{
var errors = new List<string>();
if (!email.Contains("@")) errors.Add("Invalid email");
if (password.Length < 6) errors.Add("Password too short");
return new ValidationResult(errors);
}
}
public class UserRepository
{
public void Save(User user)
{
using var connection = new SqlConnection("connection_string");
// ... save to database
}
}
public class EmailService
{
public void SendWelcomeEmail(string email)
{
var smtpClient = new SmtpClient();
smtpClient.Send("welcome@company.com", email, "Welcome!", "Welcome message");
}
}
public class Logger
{
public void Log(string message)
{
File.WriteAllText("log.txt", $"{DateTime.Now}: {message}");
}
}
public class UserService
{
private readonly UserValidator _validator;
private readonly UserRepository _repository;
private readonly EmailService _emailService;
private readonly Logger _logger;
public UserService(UserValidator validator, UserRepository repository,
EmailService emailService, Logger logger)
{
_validator = validator;
_repository = repository;
_emailService = emailService;
_logger = logger;
}
public void RegisterUser(string email, string password)
{
var validation = _validator.Validate(email, password);
if (!validation.IsValid)
throw new ArgumentException(string.Join(", ", validation.Errors));
var user = new User { Email = email, Password = password };
_repository.Save(user);
_emailService.SendWelcomeEmail(email);
_logger.Log($"User {email} registered");
}
}
2. Open/Closed Principle (OCP)
csharp
// VIOLATION: Need to modify class for new requirements
public class BadPriceCalculator
{
public decimal Calculate(string customerType, decimal price)
{
return customerType switch
{
"Regular" => price * 0.95m, // 5% discount
"Premium" => price * 0.85m, // 15% discount
"VIP" => price * 0.75m, // 25% discount
_ => price
};
// Adding new customer type requires modifying this method
}
}
// CORRECT: Open for extension, closed for modification
public interface IDiscountStrategy
{
bool CanApply(string customerType);
decimal Apply(decimal price);
}
public class RegularDiscountStrategy : IDiscountStrategy
{
public bool CanApply(string customerType) => customerType == "Regular";
public decimal Apply(decimal price) => price * 0.95m;
}
public class PremiumDiscountStrategy : IDiscountStrategy
{
public bool CanApply(string customerType) => customerType == "Premium";
public decimal Apply(decimal price) => price * 0.85m;
}
public class VipDiscountStrategy : IDiscountStrategy
{
public bool CanApply(string customerType) => customerType == "VIP";
public decimal Apply(decimal price) => price * 0.75m;
}
public class PriceCalculator
{
private readonly IEnumerable<IDiscountStrategy> _discountStrategies;
public PriceCalculator(IEnumerable<IDiscountStrategy> discountStrategies)
{
_discountStrategies = discountStrategies;
}
public decimal Calculate(string customerType, decimal price)
{
var strategy = _discountStrategies.FirstOrDefault(s => s.CanApply(customerType));
return strategy?.Apply(price) ?? price;
}
}
// Adding new discount strategy doesn't require modifying PriceCalculator
public class NewCustomerDiscountStrategy : IDiscountStrategy
{
public bool CanApply(string customerType) => customerType == "New";
public decimal Apply(decimal price) => price * 0.90m; // 10% discount
}
3. Liskov Substitution Principle (LSP)
csharp
// VIOLATION: Derived class changes expected behavior
public class Rectangle
{
public virtual int Width { get; set; }
public virtual int Height { get; set; }
public int Area => Width * Height;
}
public class Square : Rectangle
{
public override int Width
{
set { base.Width = value; base.Height = value; }
}
public override int Height
{
set { base.Height = value; base.Width = value; }
}
}
// This breaks LSP because Square changes the behavior of Width/Height setters
void TestRectangle(Rectangle rect)
{
rect.Width = 5;
rect.Height = 4;
Console.WriteLine(rect.Area); // Expects 20, but if Square gets 16
}
// CORRECT: Separate hierarchy or use composition
public interface IShape
{
int Area { get; }
}
public class Rectangle : IShape
{
public int Width { get; set; }
public int Height { get; set; }
public int Area => Width * Height;
}
public class Square : IShape
{
public int Side { get; set; }
public int Area => Side * Side;
}
// Now both can be used interchangeably through IShape interface
void ProcessShape(IShape shape)
{
Console.WriteLine($"Area: {shape.Area}");
}
4. Interface Segregation Principle (ISP)
csharp
// VIOLATION: Fat interface
public interface IWorker
{
void Work();
void Eat();
void Sleep();
void Code();
void Design();
void Test();
}
public class Developer : IWorker
{
public void Work() { /* ... */ }
public void Eat() { /* ... */ }
public void Sleep() { /* ... */ }
public void Code() { /* ... */ }
public void Design() { /* Not all developers design */ }
public void Test() { /* Not all developers test */ }
}
// CORRECT: Segregated interfaces
public interface IWorkable
{
void Work();
}
public interface IEatable
{
void Eat();
}
public interface ISleepable
{
void Sleep();
}
public interface ICodeable
{
void Code();
}
public interface IDesignable
{
void Design();
}
public interface ITestable
{
void Test();
}
// Classes implement only what they need
public class Developer : IWorkable, IEatable, ISleepable, ICodeable
{
public void Work() => Console.WriteLine("Working...");
public void Eat() => Console.WriteLine("Eating...");
public void Sleep() => Console.WriteLine("Sleeping...");
public void Code() => Console.WriteLine("Coding...");
}
public class Tester : IWorkable, IEatable, ISleepable, ITestable
{
public void Work() => Console.WriteLine("Working...");
public void Eat() => Console.WriteLine("Eating...");
public void Sleep() => Console.WriteLine("Sleeping...");
public void Test() => Console.WriteLine("Testing...");
}
5. Dependency Inversion Principle (DIP)
csharp
// VIOLATION: High-level module depends on low-level modules
public class BusinessLogic
{
private readonly SqlDatabase _database; // Concrete dependency
private readonly FileLogger _logger; // Concrete dependency
public BusinessLogic()
{
_database = new SqlDatabase();
_logger = new FileLogger();
}
public void ProcessData(string data)
{
_database.Save(data);
_logger.Log($"Processed: {data}");
}
}
// CORRECT: Both depend on abstractions
public interface IDataStorage
{
void Save(string data);
}
public interface ILogger
{
void Log(string message);
}
public class BusinessLogic
{
private readonly IDataStorage _storage;
private readonly ILogger _logger;
// Dependencies injected through constructor
public BusinessLogic(IDataStorage storage, ILogger logger)
{
_storage = storage;
_logger = logger;
}
public void ProcessData(string data)
{
_storage.Save(data);
_logger.Log($"Processed: {data}");
}
}
// Low-level modules implement abstractions
public class SqlDatabase : IDataStorage
{
public void Save(string data) { /* SQL implementation */ }
}
public class FileLogger : ILogger
{
public void Log(string message) { /* File logging implementation */ }
}
.NET Dependency Injection Container
Basic Setup and Registration
csharp
// Program.cs (NET 6+)
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = Host.CreateDefaultBuilder(args);
builder.ConfigureServices((context, services) =>
{
// Basic service registration
services.AddScoped<IUserService, UserService>();
services.AddScoped<IEmailService, EmailService>();
services.AddSingleton<ILogger, FileLogger>();
services.AddTransient<IValidator, UserValidator>();
// Register with implementation factory
services.AddScoped<IDatabase>(provider =>
new SqlDatabase(context.Configuration.GetConnectionString("Default")));
// Register multiple implementations
services.AddScoped<IPaymentProcessor, CreditCardProcessor>();
services.AddScoped<IPaymentProcessor, PayPalProcessor>();
services.AddScoped<IPaymentProcessor, BankTransferProcessor>();
});
var host = builder.Build();
Service Lifetimes
csharp
public class ServiceLifetimesDemo
{
public void ConfigureServices(IServiceCollection services)
{
// Transient: New instance every time
services.AddTransient<ITransientService, TransientService>();
// Scoped: One instance per request/scope
services.AddScoped<IScopedService, ScopedService>();
// Singleton: One instance for entire application
services.AddSingleton<ISingletonService, SingletonService>();
// Singleton with instance
var instance = new SingletonService();
services.AddSingleton<ISingletonService>(instance);
}
}
// Example services
public interface ITransientService { Guid Id { get; } }
public interface IScopedService { Guid Id { get; } }
public interface ISingletonService { Guid Id { get; } }
public class TransientService : ITransientService
{
public Guid Id { get; } = Guid.NewGuid();
}
public class ScopedService : IScopedService
{
public Guid Id { get; } = Guid.NewGuid();
}
public class SingletonService : ISingletonService
{
public Guid Id { get; } = Guid.NewGuid();
}
Advanced DI Configuration
csharp
public static class ServiceExtensions
{
public static IServiceCollection AddApplicationServices(this IServiceCollection services, IConfiguration configuration)
{
// Domain services
services.AddScoped<IUserService, UserService>();
services.AddScoped<IOrderService, OrderService>();
services.AddScoped<IPaymentService, PaymentService>();
// Infrastructure
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IOrderRepository, OrderRepository>();
// External services
services.AddHttpClient<IEmailService, EmailService>(client =>
{
client.BaseAddress = new Uri(configuration["EmailService:BaseUrl"]);
client.Timeout = TimeSpan.FromSeconds(30);
});
services.AddHttpClient<IPaymentGateway, StripePaymentGateway>(client =>
{
client.BaseAddress = new Uri("https://api.stripe.com/");
client.DefaultRequestHeaders.Add("Authorization",
$"Bearer {configuration["Stripe:SecretKey"]}");
});
// Options pattern
services.Configure<DatabaseOptions>(configuration.GetSection("Database"));
services.Configure<EmailOptions>(configuration.GetSection("Email"));
// Decorator pattern registration
services.AddScoped<IUserRepository, UserRepository>();
services.Decorate<IUserRepository, CachingUserRepository>();
services.Decorate<IUserRepository, LoggingUserRepository>();
return services;
}
}
// Options classes
public class DatabaseOptions
{
public string ConnectionString { get; set; }
public int TimeoutSeconds { get; set; } = 30;
}
public class EmailOptions
{
public string FromAddress { get; set; }
public string SmtpServer { get; set; }
public int Port { get; set; } = 587;
}
Practical Dependency Injection Examples
E-commerce Application
csharp
// Domain interfaces
public interface IProductRepository
{
Task<Product> GetByIdAsync(int id);
Task<List<Product>> SearchAsync(string searchTerm);
Task AddAsync(Product product);
}
public interface IOrderRepository
{
Task<Order> GetByIdAsync(int id);
Task AddAsync(Order order);
Task UpdateAsync(Order order);
}
public interface IPaymentGateway
{
Task<PaymentResult> ProcessPaymentAsync(PaymentRequest request);
}
public interface IShippingService
{
Task<ShippingLabel> CreateShippingLabelAsync(Order order);
}
public interface INotificationService
{
Task SendOrderConfirmationAsync(Order order);
}
// Domain services
public class OrderService : IOrderService
{
private readonly IOrderRepository _orderRepository;
private readonly IPaymentGateway _paymentGateway;
private readonly IShippingService _shippingService;
private readonly INotificationService _notificationService;
private readonly ILogger<OrderService> _logger;
public OrderService(
IOrderRepository orderRepository,
IPaymentGateway paymentGateway,
IShippingService shippingService,
INotificationService notificationService,
ILogger<OrderService> logger)
{
_orderRepository = orderRepository;
_paymentGateway = paymentGateway;
_shippingService = shippingService;
_notificationService = notificationService;
_logger = logger;
}
public async Task<OrderResult> PlaceOrderAsync(CreateOrderRequest request)
{
_logger.LogInformation("Placing order for customer {CustomerId}", request.CustomerId);
// Create order
var order = new Order(request.CustomerId, request.Items);
await _orderRepository.AddAsync(order);
// Process payment
var paymentRequest = new PaymentRequest(order.TotalAmount, request.PaymentMethod);
var paymentResult = await _paymentGateway.ProcessPaymentAsync(paymentRequest);
if (!paymentResult.Success)
{
order.MarkAsFailed(paymentResult.Error);
await _orderRepository.UpdateAsync(order);
return OrderResult.Failed(paymentResult.Error);
}
// Create shipping
var shippingLabel = await _shippingService.CreateShippingLabelAsync(order);
order.AddShippingLabel(shippingLabel);
// Update order status
order.MarkAsConfirmed();
await _orderRepository.UpdateAsync(order);
// Send notifications
await _notificationService.SendOrderConfirmationAsync(order);
_logger.LogInformation("Order {OrderId} placed successfully", order.Id);
return OrderResult.Success(order);
}
}
// Infrastructure implementations
public class SqlOrderRepository : IOrderRepository
{
private readonly ApplicationDbContext _context;
private readonly ILogger<SqlOrderRepository> _logger;
public SqlOrderRepository(ApplicationDbContext context, ILogger<SqlOrderRepository> logger)
{
_context = context;
_logger = logger;
}
public async Task<Order> GetByIdAsync(int id)
{
return await _context.Orders
.Include(o => o.Items)
.FirstOrDefaultAsync(o => o.Id == id);
}
public async Task AddAsync(Order order)
{
await _context.Orders.AddAsync(order);
await _context.SaveChangesAsync();
}
public async Task UpdateAsync(Order order)
{
_context.Orders.Update(order);
await _context.SaveChangesAsync();
}
}
public class StripePaymentGateway : IPaymentGateway
{
private readonly StripeClient _stripeClient;
private readonly ILogger<StripePaymentGateway> _logger;
public StripePaymentGateway(StripeClient stripeClient, ILogger<StripePaymentGateway> logger)
{
_stripeClient = stripeClient;
_logger = logger;
}
public async Task<PaymentResult> ProcessPaymentAsync(PaymentRequest request)
{
try
{
// Process payment with Stripe
var paymentIntent = await _stripeClient.PaymentIntents.CreateAsync(new PaymentIntentCreateOptions
{
Amount = (long)(request.Amount * 100), // Convert to cents
Currency = "usd",
PaymentMethod = request.PaymentMethodId
});
return PaymentResult.Success(paymentIntent.Id);
}
catch (Exception ex)
{
_logger.LogError(ex, "Payment processing failed for amount {Amount}", request.Amount);
return PaymentResult.Failed("Payment processing failed");
}
}
}
// Registration
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddEcommerceServices(this IServiceCollection services, IConfiguration configuration)
{
// Domain services
services.AddScoped<IOrderService, OrderService>();
services.AddScoped<IProductService, ProductService>();
// Repositories
services.AddScoped<IOrderRepository, SqlOrderRepository>();
services.AddScoped<IProductRepository, SqlProductRepository>();
// External services
services.AddSingleton<StripeClient>(_ => new StripeClient(configuration["Stripe:SecretKey"]));
services.AddScoped<IPaymentGateway, StripePaymentGateway>();
services.AddScoped<IShippingService, FedExShippingService>();
services.AddScoped<INotificationService, EmailNotificationService>();
// HTTP clients
services.AddHttpClient<IShippingService, FedExShippingService>(client =>
{
client.BaseAddress = new Uri(configuration["FedEx:BaseUrl"]);
});
// Options
services.Configure<StripeOptions>(configuration.GetSection("Stripe"));
services.Configure<ShippingOptions>(configuration.GetSection("Shipping"));
return services;
}
}
Decorator Pattern with DI
csharp
// Base service
public interface IDataService
{
Task<string> GetDataAsync(string key);
Task SaveDataAsync(string key, string data);
}
// Core implementation
public class DatabaseDataService : IDataService
{
public async Task<string> GetDataAsync(string key)
{
// Direct database access
await Task.Delay(100);
return $"Data for {key}";
}
public async Task SaveDataAsync(string key, string data)
{
await Task.Delay(100);
Console.WriteLine($"Saved {key}: {data}");
}
}
// Caching decorator
public class CachingDataService : IDataService
{
private readonly IDataService _innerService;
private readonly IMemoryCache _cache;
private readonly ILogger<CachingDataService> _logger;
public CachingDataService(IDataService innerService, IMemoryCache cache, ILogger<CachingDataService> logger)
{
_innerService = innerService;
_cache = cache;
_logger = logger;
}
public async Task<string> GetDataAsync(string key)
{
if (_cache.TryGetValue(key, out string cachedData))
{
_logger.LogInformation("Cache hit for {Key}", key);
return cachedData;
}
_logger.LogInformation("Cache miss for {Key}", key);
var data = await _innerService.GetDataAsync(key);
_cache.Set(key, data, TimeSpan.FromMinutes(5));
return data;
}
public async Task SaveDataAsync(string key, string data)
{
await _innerService.SaveDataAsync(key, data);
_cache.Remove(key); // Invalidate cache on save
}
}
// Logging decorator
public class LoggingDataService : IDataService
{
private readonly IDataService _innerService;
private readonly ILogger<LoggingDataService> _logger;
public LoggingDataService(IDataService innerService, ILogger<LoggingDataService> logger)
{
_innerService = innerService;
_logger = logger;
}
public async Task<string> GetDataAsync(string key)
{
_logger.LogInformation("Getting data for {Key}", key);
var stopwatch = Stopwatch.StartNew();
try
{
var result = await _innerService.GetDataAsync(key);
stopwatch.Stop();
_logger.LogInformation("Retrieved data for {Key} in {ElapsedMs}ms", key, stopwatch.ElapsedMilliseconds);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting data for {Key}", key);
throw;
}
}
public async Task SaveDataAsync(string key, string data)
{
_logger.LogInformation("Saving data for {Key}", key);
await _innerService.SaveDataAsync(key, data);
_logger.LogInformation("Saved data for {Key}", key);
}
}
// Registration with decorators
services.AddScoped<IDataService, DatabaseDataService>();
services.Decorate<IDataService, CachingDataService>();
services.Decorate<IDataService, LoggingDataService>();
Factory Pattern with DI
csharp
public interface IPaymentProcessorFactory
{
IPaymentProcessor Create(string paymentMethod);
}
public class PaymentProcessorFactory : IPaymentProcessorFactory
{
private readonly IServiceProvider _serviceProvider;
public PaymentProcessorFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IPaymentProcessor Create(string paymentMethod)
{
return paymentMethod.ToLower() switch
{
"creditcard" => _serviceProvider.GetRequiredService<CreditCardProcessor>(),
"paypal" => _serviceProvider.GetRequiredService<PayPalProcessor>(),
"banktransfer" => _serviceProvider.GetRequiredService<BankTransferProcessor>(),
_ => throw new ArgumentException($"Unknown payment method: {paymentMethod}")
};
}
}
// Registration
services.AddScoped<IPaymentProcessorFactory, PaymentProcessorFactory>();
services.AddScoped<CreditCardProcessor>();
services.AddScoped<PayPalProcessor>();
services.AddScoped<BankTransferProcessor>();
Testing with Dependency Injection
Unit Testing with Mock Dependencies
csharp
public class OrderServiceTests
{
private readonly Mock<IOrderRepository> _orderRepositoryMock;
private readonly Mock<IPaymentGateway> _paymentGatewayMock;
private readonly Mock<IShippingService> _shippingServiceMock;
private readonly Mock<ILogger<OrderService>> _loggerMock;
private readonly OrderService _orderService;
public OrderServiceTests()
{
_orderRepositoryMock = new Mock<IOrderRepository>();
_paymentGatewayMock = new Mock<IPaymentGateway>();
_shippingServiceMock = new Mock<IShippingService>();
_loggerMock = new Mock<ILogger<OrderService>>();
_orderService = new OrderService(
_orderRepositoryMock.Object,
_paymentGatewayMock.Object,
_shippingServiceMock.Object,
Mock.Of<INotificationService>(), // Simple mock for optional dependency
_loggerMock.Object);
}
[Fact]
public async Task PlaceOrderAsync_WithValidRequest_ShouldSucceed()
{
// Arrange
var request = new CreateOrderRequest
{
CustomerId = 1,
Items = new List<OrderItem> { new OrderItem(1, 2, 25.00m) },
PaymentMethod = "creditcard"
};
_paymentGatewayMock
.Setup(x => x.ProcessPaymentAsync(It.IsAny<PaymentRequest>()))
.ReturnsAsync(PaymentResult.Success("payment_123"));
_shippingServiceMock
.Setup(x => x.CreateShippingLabelAsync(It.IsAny<Order>()))
.ReturnsAsync(new ShippingLabel("label_123"));
// Act
var result = await _orderService.PlaceOrderAsync(request);
// Assert
Assert.True(result.Success);
_orderRepositoryMock.Verify(x => x.AddAsync(It.IsAny<Order>()), Times.Once);
_orderRepositoryMock.Verify(x => x.UpdateAsync(It.IsAny<Order>()), Times.Once);
}
[Fact]
public async Task PlaceOrderAsync_WhenPaymentFails_ShouldReturnFailedResult()
{
// Arrange
var request = new CreateOrderRequest { /* ... */ };
_paymentGatewayMock
.Setup(x => x.ProcessPaymentAsync(It.IsAny<PaymentRequest>()))
.ReturnsAsync(PaymentResult.Failed("Insufficient funds"));
// Act
var result = await _orderService.PlaceOrderAsync(request);
// Assert
Assert.False(result.Success);
Assert.Equal("Insufficient funds", result.Error);
}
}
Integration Testing with Test Containers
csharp
public class OrderServiceIntegrationTests : IAsyncLifetime
{
private readonly TestContainer _databaseContainer;
private readonly IServiceProvider _serviceProvider;
public OrderServiceIntegrationTests()
{
_databaseContainer = new TestContainerBuilder()
.WithImage("mcr.microsoft.com/mssql/server:2019-latest")
.Build();
}
public async Task InitializeAsync()
{
await _databaseContainer.StartAsync();
var services = new ServiceCollection();
services.AddEcommerceServices(GetTestConfiguration());
_serviceProvider = services.BuildServiceProvider();
}
public async Task DisposeAsync()
{
await _databaseContainer.DisposeAsync();
(_serviceProvider as IDisposable)?.Dispose();
}
[Fact]
public async Task PlaceOrderAsync_WithRealDependencies_ShouldWork()
{
// Arrange
var orderService = _serviceProvider.GetRequiredService<IOrderService>();
var request = new CreateOrderRequest { /* ... */ };
// Act & Assert
var result = await orderService.PlaceOrderAsync(request);
Assert.True(result.Success);
}
private IConfiguration GetTestConfiguration()
{
return new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string>
{
["ConnectionStrings:Default"] = _databaseContainer.GetConnectionString(),
["Stripe:SecretKey"] = "sk_test_...",
["Shipping:BaseUrl"] = "https://test.shipping.com/"
})
.Build();
}
}
Best Practices
1. Service Registration Guidelines
csharp
public static class BestPracticeRegistrations
{
public static IServiceCollection AddBestPracticeServices(this IServiceCollection services)
{
// Register interfaces with their implementations
services.AddScoped<IUserService, UserService>();
// Use TryAdd to avoid duplicate registrations
services.TryAddScoped<IEmailService, EmailService>();
// Register multiple implementations for factory pattern
services.AddScoped<CreditCardProcessor>();
services.AddScoped<PayPalProcessor>();
services.AddScoped<IPaymentProcessorFactory, PaymentProcessorFactory>();
// Use options pattern for configuration
services.Configure<DatabaseOptions>(configuration =>
{
configuration.ConnectionString = "Server=...";
configuration.TimeoutSeconds = 30;
});
// Register HttpClient with proper configuration
services.AddHttpClient<IExternalService, ExternalService>(client =>
{
client.BaseAddress = new Uri("https://api.example.com/");
client.DefaultRequestHeaders.Add("User-Agent", "MyApp/1.0");
})
.AddPolicyHandler(GetRetryPolicy())
.AddPolicyHandler(GetCircuitBreakerPolicy());
return services;
}
private static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.WaitAndRetryAsync(3, retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
}
}
2. Dependency Injection Anti-Patterns
csharp
// ANTI-PATTERN: Service locator pattern
public class BadService
{
public void DoWork()
{
using var scope = _serviceProvider.CreateScope();
var dependency = scope.ServiceProvider.GetRequiredService<IDependency>();
// This hides dependencies and makes testing difficult
}
}
// ANTI-PATTERN: Captive dependencies
public class CaptiveDependency
{
private readonly ISingletonService _singleton;
private readonly IScopedService _scoped; // This becomes captive!
public CaptiveDependency(ISingletonService singleton, IScopedService scoped)
{
_singleton = singleton;
_scoped = scoped; // Scoped service captured by singleton - BAD!
}
}
// ANTI-PATTERN: Property injection (when constructor injection is possible)
public class PropertyInjectionService
{
[Inject] // Don't do this when constructor injection works
public IDependency Dependency { get; set; }
}
Key Benefits Summary
-
Testability - Easy to mock dependencies for unit testing
-
Maintainability - Clear dependencies and responsibilities
-
Flexibility - Easy to swap implementations
-
Scalability - Better architecture for growing applications
-
Loose Coupling - Components don't depend on concrete implementations
This comprehensive guide shows how Dependency Injection and SOLID principles work together to create maintainable, testable, and scalable .NET applications!
Comments (0)
No comments yet. Be the first to comment!