Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Advanced Microsoft.Extensions.DependencyInjection patterns for .NET applications. Covers service lifetimes, keyed
services (net8.0+), decoration, factory delegates, scope validation, and hosted service registration.
Scope
Service lifetimes (transient, scoped, singleton) and captive dependency detection
Keyed services (.NET 8+) and factory delegates
Decorator pattern and scope validation
Hosted service registration
Out of scope
Async/await patterns in BackgroundService -- see [skill:dotnet-csharp-async-patterns]
Options pattern binding and IOptions -- see [skill:dotnet-csharp-configuration]
SOLID/DRY design principles -- see [skill:dotnet-solid-principles]
Cross-references: [skill:dotnet-csharp-async-patterns] for BackgroundService async patterns,
[skill:dotnet-csharp-configuration] for IOptions<T> binding.
Service Lifetimes
Lifetime
Registration
When to Use
Transient
AddTransient<T>()
Lightweight, stateless services. New instance per injection.
Scoped
AddScoped<T>()
Per-request state (EF Core DbContext, unit of work).
Singleton
AddSingleton<T>()
Thread-safe, stateless, or shared state (caches, config).
Lifetime Mismatches (Captive Dependencies)
Never inject a shorter-lived service into a longer-lived one:
// WRONG -- scoped DbContext captured in singleton = same context for all requests
builder.Services.AddSingleton<OrderService>(); // singleton
builder.Services.AddScoped<AppDbContext>(); // scoped -- CAPTIVE!// CORRECT -- use IServiceScopeFactory in singletonspublicsealedclassOrderService(IServiceScopeFactory scopeFactory)
{
publicasync Task ()
{
scope = scopeFactory.CreateScope();
db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
db.Orders.Where(o => o.IsPending).ToListAsync(ct);
}
}
```text
```csharp
builder = WebApplication.CreateBuilder();
host = Host.CreateDefaultBuilder()
.UseDefaultServiceProvider(options =>
{
options.ValidateScopes = ;
options.ValidateOnBuild = ;
})
.Build();
```text
---
```csharp
builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();
```csharp
```csharp
builder.Services.AddScoped<INotifier, EmailNotifier>();
builder.Services.AddScoped<INotifier, SmsNotifier>();
builder.Services.AddScoped<INotifier, PushNotifier>();
{
{
( notifier notifiers)
{
notifier.NotifyAsync(message, ct);
}
}
}
```text
```csharp
builder.Services.AddScoped<IOrderService>(sp =>
{
repo = sp.GetRequiredService<IOrderRepository>();
logger = sp.GetRequiredService<ILogger<OrderService>>();
options = sp.GetRequiredService<IOptions<OrderOptions>>();
OrderService(repo, logger, options.Value.MaxRetries);
});
```text
Libraries should use `TryAdd` so applications can :
```csharp
builder.Services.TryAddScoped<IOrderRepository, DefaultOrderRepository>();
builder.Services.AddScoped<IOrderRepository, CustomOrderRepository>();
```text
---
Register resolve services a key, replacing the need named service patterns.
```csharp
builder.Services.AddKeyedScoped<ICache, RedisCache>();
builder.Services.AddKeyedScoped<ICache, MemoryCache>();
{
Task<Order?> GetAsync( id, CancellationToken ct = )
{
localCache.GetAsync<Order>(id.ToString(), ct)
?? distributedCache.GetAsync<Order>(id.ToString(), ct);
}
}
cache = sp.GetRequiredKeyedService<ICache>();
```text
> **net8+ only.** On earlier TFMs, use factory patterns a dictionary-based approach.
---
The built- container does natively support decoration. Use one of these approaches:
```csharp
builder.Services.AddScoped<SqlOrderRepository>();
builder.Services.AddScoped<IOrderRepository>(sp =>
{
inner = sp.GetRequiredService<SqlOrderRepository>();
logger = sp.GetRequiredService<ILogger<LoggingOrderRepository>>();
LoggingOrderRepository(inner, logger);
});
{
Task<Order?> GetByIdAsync( id, CancellationToken ct = )
{
logger.LogInformation(, id);
inner.GetByIdAsync(id, ct);
}
}
```text
```csharp
builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();
builder.Services.Decorate<IOrderRepository, LoggingOrderRepository>();
builder.Services.Decorate<IOrderRepository, CachingOrderRepository>();
```text
---
```
{
{
logger.LogInformation();
(!stoppingToken.IsCancellationRequested)
{
{
scope = scopeFactory.CreateScope();
processor = scope.ServiceProvider
.GetRequiredService<IQueueProcessor>();
processor.ProcessNextBatchAsync(stoppingToken);
}
(Exception ex) (ex OperationCanceledException)
{
logger.LogError(ex, );
}
Task.Delay(TimeSpan.FromSeconds(), stoppingToken);
}
}
}
builder.Services.AddHostedService<QueueProcessorWorker>();
```text
```
{
{
scope = scopeFactory.CreateScope();
db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
db.Database.MigrateAsync(cancellationToken);
logger.LogInformation();
}
=> Task.CompletedTask;
}
builder.Services.AddHostedService<DatabaseMigrationService>();
```text
- Always use `IServiceScopeFactory` to create scopes -- hosted services are singletons
- Never inject services directly hosted service constructors
- Handle exceptions inside `ExecuteAsync` --
{
{
services.AddScoped<IOrderRepository, SqlOrderRepository>();
services.AddScoped<IOrderService, OrderService>();
services.AddHostedService<OrderProcessorWorker>();
services;
}
{
services.AddScoped<INotifier, EmailNotifier>();
services.AddScoped<INotifier, SmsNotifier>();
services;
}
}
builder.Services.AddOrderServices();
builder.Services.AddNotificationServices();
```csharp
---
```csharp
[]
{
services = ServiceCollection();
services.AddScoped<IOrderRepository, InMemoryOrderRepository>();
services.AddScoped<IOrderService, OrderService>();
services.AddLogging();
provider = services.BuildServiceProvider();
scope = provider.CreateScope();
service = scope.ServiceProvider.GetRequiredService<IOrderService>();
order = service.GetByIdAsync();
Assert.NotNull(order);
}
```text
For unit tests, prefer direct constructor injection mocks rather than building a full container.
---
**Primary approach:** Use Serena symbol operations efficient code navigation:
**Find definitions**: `serena_find_symbol` instead of text search
**Understand structure**: `serena_get_symbols_overview` organization
**Track references**: `serena_find_referencing_symbols` impact analysis
**Precise edits**: `serena_replace_symbol_body` clean modifications
**When to use Serena vs traditional tools:**
- ✅ **Use Serena**: Navigation, refactoring, dependency analysis, precise edits
- ✅ **Use Read/Grep**: Reading full files, pattern matching, simple text operations
- ✅ **Fallback**: If Serena unavailable, traditional tools work fine
**Example workflow:**
```text
Read: src/Services/OrderService.cs
Grep:
serena_find_symbol:
serena_get_symbols_overview:
```
- [Dependency injection .NET](https:
- [Keyed services .NET ](https:
- [Background tasks hosted services](https:
- [Service lifetimes](https:
- [.NET Framework Design Guidelines](https:
ProcessAsync
CancellationToken ct = default
using
var
var
await
### Enable Scope Validation (Development)
var
args
// In Development, ValidateScopes is already true by default.
public Task StopAsync(CancellationToken cancellationToken)
### Key Rules for Hosted Services
scoped
into
unhandled exceptions stop the host (net8.0+)
- See [skill:dotnet-csharp-async-patterns] forasync patterns in background workers
---
## Organizing Registrations
Group related registrations into extension methods for clean `Program.cs`:
```csharp
// ServiceCollectionExtensions.cspublicstaticclass ServiceCollectionExtensions