Dependency Injection, Service Lifetimes & Options Pattern
Build maintainable, testable software using .NET's built-in Dependency Injection container (IServiceCollection), service lifetimes (Transient, Scoped, Singleton), and strongly typed Options.
What You Will Learn in This Lesson
- The Inversion of Control (IoC) principle and Dependency Injection in .NET
- Service Lifetimes: `Transient` (every request), `Scoped` (per HTTP request), `Singleton` (application lifetime)
- The Captive Dependency anti-pattern and how to avoid it
- Strongly typed application configuration with `IOptions<T>` and `IOptionsSnapshot<T>`
Introduction & Core Concept
Hardcoding dependencies with 'new Service()' creates tightly coupled systems that cannot be unit-tested. Dependency Injection decouples implementations from interfaces, enabling modular architecture and seamless mocking.
Syntax & Structure
builder.Services.AddScoped<IUserRepository, SqlUserRepository>();builder.Services.Configure<DatabaseOptions>(builder.Configuration.GetSection("Database"));Configuring DI Container and Constructor Injection in .NET
csharp12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061// Dependency Injection and Options Pattern in .NET 9using System;// 1. Strongly-typed configuration optionspublic class PaymentGatewayOptions{public string ApiKey { get; set; } = "sk_live_default_key";public int TimeoutSeconds { get; set; } = 30;}// 2. Service interface and implementationpublic interface IPaymentService{void ProcessPayment(decimal amount);}public class StripePaymentService : IPaymentService{private readonly PaymentGatewayOptions _options;// Constructor Injection of optionspublic StripePaymentService(PaymentGatewayOptions options){_options = options;}public void ProcessPayment(decimal amount){Console.WriteLine($"[Stripe] Processed payment of USD {amount:N2} using API Key ending in '...{_options.ApiKey[^4..]}'");}}// 3. Controller consuming injected servicepublic class CheckoutController{private readonly IPaymentService _paymentService;public CheckoutController(IPaymentService paymentService){_paymentService = paymentService;}public void ExecuteCheckout(decimal cartTotal){Console.WriteLine("Executing checkout transaction...");_paymentService.ProcessPayment(cartTotal);}}public class Program{public static void Main(){// Demonstration of resolving configured dependency hierarchyvar config = new PaymentGatewayOptions { ApiKey = "sk_live_kwas_academy_secret_9981" };IPaymentService paymentService = new StripePaymentService(config);var controller = new CheckoutController(paymentService);controller.ExecuteCheckout(249.99m);}}
Line-by-Line Technical Breakdown
Try It Yourself (Interactive Editor)
Modify the code in real-time and click Run to test live browser output and console logs.
Common Mistakes & How to Avoid Them
#1: Registering Entity Framework DbContext as a Singleton service.
DbContext is not thread-safe. Registering it as a Singleton causes multiple concurrent HTTP requests to share the same database connection, causing fatal runtime concurrency crashes.
builder.Services.AddSingleton<AppDbContext>();builder.Services.AddDbContext<AppDbContext>(); // Defaults to ScopedIndustry Best Practices & Professional Standards
- Register DbContexts and unit-of-work repositories as `Scoped` services.
- Register lightweight, stateless utility services as `Transient`.
- Register thread-safe caches and telemetry clients as `Singleton`.
Lesson Summary & Core Takeaways
- .NET features a built-in IoC container managing service lifetimes.
- `Transient`, `Scoped`, and `Singleton` control instance allocation.
- The Options pattern binds `appsettings.json` sections to strongly typed classes.