Menu

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

  1. Testability - Easy to mock dependencies for unit testing

  2. Maintainability - Clear dependencies and responsibilities

  3. Flexibility - Easy to swap implementations

  4. Scalability - Better architecture for growing applications

  5. 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!

LecturreAI Assistant

Hello! I'm LecturreAI

What would you like to learn today?