# Dependency Injection Lifetimes in .NET

## 1. The Built-in Container

ASP.NET Core ships with `Microsoft.Extensions.DependencyInjection`. You register services on `builder.Services` and the framework resolves them for controllers, minimal API handlers, middleware, hosted services and other services:

```csharp
builder.Services.AddSingleton<IClock, SystemClock>();
builder.Services.AddScoped<IOrderRepository, EfOrderRepository>();
builder.Services.AddTransient<IEmailFormatter, EmailFormatter>();
builder.Services.AddDbContext<ShopDb>(o => o.UseNpgsql(cs));   // scoped by default
builder.Services.AddHttpClient<PaymentsClient>();               // typed client via IHttpClientFactory
```

## 2. The Three Lifetimes

| Lifetime | One instance per… | Typical use |
|---|---|---|
| **Singleton** | Application | Stateless services, caches, configuration, `HttpClient` factories. Must be **thread-safe**. |
| **Scoped** | Scope (an HTTP request in ASP.NET Core) | `DbContext`, unit of work, per-request user context |
| **Transient** | Resolution (every injection) | Lightweight stateless helpers |

Disposable services are disposed by the container at the end of their lifetime: scoped ones when the request ends, singletons at shutdown. Transient disposables resolved from the root provider live until shutdown, a subtle leak.

## 3. Captive Dependencies

A service must not depend on a service with a **shorter** lifetime. A singleton that receives a scoped `DbContext` captures it for the life of the app: the context is shared across requests and threads (not thread-safe) and never disposed.

```text
Singleton → Scoped     ✗ captive dependency
Singleton → Transient  ⚠ the transient becomes effectively a singleton
Scoped    → Singleton  ✓
```

In Development, ASP.NET Core enables **scope validation** (`ValidateScopes`, `ValidateOnBuild`) and throws when a singleton resolves a scoped service. When a singleton (like a `BackgroundService`) genuinely needs scoped services, create a scope per unit of work:

```csharp
using var scope = _scopeFactory.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<ShopDb>();
```

## 4. Advanced Registration

- **Multiple implementations**: register several `INotifier`s and inject `IEnumerable<INotifier>`; the last registration wins for a single `INotifier`.
- **Keyed services** (.NET 8): `AddKeyedSingleton<IStorage, S3Storage>("s3")` and `[FromKeyedServices("s3")]`.
- **Factories**: `AddScoped<IRepo>(sp => new Repo(sp.GetRequiredService<ShopDb>(), "eu"))`.
- **Options pattern**: `services.Configure<SmtpOptions>(config.GetSection("Smtp"))` and inject `IOptions<T>` (singleton snapshot), `IOptionsSnapshot<T>` (per request) or `IOptionsMonitor<T>` (change notifications).
- **Decorators**: wrap implementations for caching or logging (Scrutor helps with assembly scanning and decoration).

## 5. Testing

Override registrations in tests with `WebApplicationFactory<Program>.WithWebHostBuilder(b => b.ConfigureTestServices(s => s.AddSingleton<IClock>(fakeClock)))`. Constructor injection makes unit tests simple: pass fakes directly.

## Common Pitfalls

1. **Captive dependencies** (singleton → scoped DbContext).
2. **Non-thread-safe singletons** with mutable state.
3. **Service locator** (`IServiceProvider` injected everywhere) hiding dependencies.
4. **Resolving scoped services from the root provider** in background services.
5. **Huge constructors**: a sign the class does too much.

## Quick Reference

```text
Singleton (app, thread-safe) · Scoped (per request: DbContext) · Transient (per resolution)
never depend on a shorter lifetime · ValidateScopes/ValidateOnBuild in dev
background services: IServiceScopeFactory.CreateScope() per unit of work
IEnumerable<T> for many impls · keyed services · IOptions / IOptionsSnapshot / IOptionsMonitor
```
