Introduction
Dependency Injection (DI) is built into ASP.NET Core — the framework creates your controllers and injects services like payment gateways, repositories, and loggers. Think of a restaurant: customers order at the counter (controller); the kitchen (services) is staffed by the manager (IoC container), not hired by each customer.
After this article you will
- Explain IoC container and service lifetimes
- Register services with AddTransient, AddScoped, AddSingleton
- Avoid captive dependency bugs
- Use constructor injection and keyed services (.NET 8)
- Build PaymentService with multiple dependencies and unit tests
Prerequisites
- Article 22 — EF Core with SQL Server — Advanced Features
- ShopNest.Web with EF Core from Module 2
Concept deep-dive
Service lifetimes
| Lifetime | Created | ShopNest example |
|---|---|---|
| Transient | Every injection | Email formatter, lightweight helpers |
| Scoped | Per HTTP request | DbContext, PaymentService, UnitOfWork |
| Singleton | Once per app | AppSettings cache, IMemoryCache wrapper |
// Program.cs — Payment module registration
builder.Services.AddScoped<IPaymentGateway, RazorpayGateway>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<IPaymentService, PaymentService>();
// Keyed services (.NET 8) — multiple gateways
builder.Services.AddKeyedScoped<IPaymentGateway, RazorpayGateway>("razorpay");
builder.Services.AddKeyedScoped<IPaymentGateway, StripeGateway>("stripe");
public class PaymentService : IPaymentService
{
private readonly IOrderRepository _orders;
private readonly IPaymentGateway _gateway;
private readonly ILogger<PaymentService> _logger;
public PaymentService(IOrderRepository orders, IPaymentGateway gateway,
ILogger<PaymentService> logger)
{
_orders = orders;
_gateway = gateway;
_logger = logger;
}
}
Captive dependency bug
❌ Injecting Scoped DbContext into a Singleton service — singleton lives forever but holds a dead DbContext. Fix: make the dependent Singleton use IServiceScopeFactory to create scopes, or change lifetimes.
Extension methods: group registration in services.AddShopNestPayments(config) for clean Program.cs.
Hands-on — ShopNest Payment Processing System
- Define IPaymentService, IPaymentGateway, IOrderRepository.
- PaymentService.ProcessAsync: load order, call gateway, update status, log.
- CheckoutController injects IPaymentService only — not gateway directly.
- xUnit test: mock IOrderRepository and IPaymentGateway; verify ProcessAsync.
[HttpPost]
public async Task<IActionResult> Pay(int orderId)
{
var result = await _payments.ProcessAsync(orderId, User);
return result.Success ? RedirectToAction("Receipt", new { orderId })
: View("PaymentFailed", result);
}
Common errors & best practices
- Registering DbContext as Singleton — always Scoped.
- Service locator anti-pattern — avoid HttpContext.RequestServices.GetService in business code.
- Too many constructor parameters — split into facades or use MediatR for complex flows.
Interview questions
Q1: Transient vs Scoped vs Singleton?
A: Transient = new each time; Scoped = one per request; Singleton = one for app lifetime.
Q2: Captive dependency?
A: Long-lived service holding short-lived dependency — causes stale DB connections and bugs.
Q3: Constructor vs property injection?
A: Constructor is standard — required dependencies explicit and immutable.
Q4: What is IoC?
A: Inversion of Control — framework creates objects and wires dependencies instead of new everywhere.
Summary
- DI is first-class in ASP.NET Core via built-in IoC container
- Scoped services match HTTP request + DbContext lifetime
- PaymentService demo shows constructor injection at scale
- Keyed services support multiple payment providers
Previous: EF Core with SQL Server — Advanced Features
Next: Middleware in ASP.NET Core
FAQ
Do I need Autofac?
Built-in DI suffices for most ShopNest apps; Autofac for advanced registration scenarios.
Can I inject IConfiguration directly?
Yes, but prefer IOptions<T> for typed settings (Article 25).