dependency-injection — Claude Code skill: what it does, how to install
Guides dependency injection patterns in .NET 10, covering service lifetimes, keyed services, Scrutor decorators, and captive dependency fixes.
npx skills add codewithmukesh/dotnet-claude-kit --skill dependency-injection -a claude-codeWhat dependency-injection does
The skill establishes dependency injection rules for .NET 10 projects. Constructor injection is the default choice. Service locators and property injection are excluded entirely. To select between multiple implementations, it relies on .NET 8+ keyed services via [FromKeyedServices] and GetRequiredKeyedService.
It includes practical templates for the decorator pattern using Scrutor, assembly scanning by convention, and runtime selection using factory delegates. Capturing a scoped service like AppDbContext inside a singleton causes stale data and memory leaks, so the guide enforces IServiceScopeFactory for background operations.
For reviewing existing architectures, the skill suggests the get_di_registrations MCP tool. That tool checks lifetime mismatches and duplicate registrations in a single pass.
What it helps with
- configure keyed services for multiple implementations of an interface
- decorate existing services with logging using Scrutor
- resolve captive dependencies when scoped services are needed in singletons
- register application services automatically via assembly scanning
- bind strongly-typed configuration sections with AddOptions
Example prompts for the agent
- Show me how to register keyed services for INotificationService in .NET 10
- Add a logging decorator to OrderService using Scrutor
- How do I safely resolve AppDbContext inside a singleton cache service?
How to install dependency-injection
1Skills CLI (Vercel)
npx skills add codewithmukesh/dotnet-claude-kit --skill dependency-injection -a claude-code2Manually: put the files in place
~/.claude/skills/dependency-injection/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/dependency-injection/
1Skills CLI (Vercel)
npx skills add codewithmukesh/dotnet-claude-kit --skill dependency-injection -a codex2Manually: put the files in place
.agents/skills/dependency-injection/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add codewithmukesh/dotnet-claude-kit --skill dependency-injection -a cursor2Manually: put the files in place
.cursor/skills/dependency-injection/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install codewithmukesh/dotnet-claude-kit dependency-injection2Manually: put the files in place
.github/skills/dependency-injection/SKILL.md1Skills CLI (Vercel)
npx skills add codewithmukesh/dotnet-claude-kit --skill dependency-injection -a gemini-cli2Platform CLI
gemini skills install https://github.com/codewithmukesh/dotnet-claude-kit --path skills/dependency-injectionManually: put the files in place
.devin/skills/dependency-injection/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add codewithmukesh/dotnet-claude-kit --skill dependency-injection -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about dependency-injection
How to install dependency-injection in Claude Code?
Run npx skills add codewithmukesh/dotnet-claude-kit --skill dependency-injection -a claude-code in your terminal.
What does the dependency-injection skill do?
It instructs the agent on proper service registration in .NET 10, including keyed services, Scrutor decorators, and lifetime management.
How does the skill prevent captive dependencies?
It stops singletons from directly accepting scoped dependencies. Instead, it instructs the class to inject IServiceScopeFactory and create scopes explicitly.
Source text
SKILL.md · MIT
---
name: dependency-injection
description: >
Dependency injection patterns for .NET 10. Covers service lifetimes, keyed
services, the decorator pattern, factory pattern, and common DI pitfalls.
Load this skill when registering services, resolving lifetime issues, designing
service composition, or when the user mentions "DI", "dependency injection",
"service registration", "AddScoped", "AddTransient", "AddSingleton", "keyed
services", "decorator", "Scrutor", "IServiceCollection", or "captive dependency".
---
# Dependency Injection
## Core Principles
1. **Constructor injection is the default** — Inject dependencies through the constructor (primary constructors make this clean). No service locator, no property injection.
2. **Match lifetimes carefully** — A singleton must never depend on a scoped or transient service. This is the most common DI bug.
3. **Register interfaces, resolve interfaces** — Register `services.AddScoped<IOrderService, OrderService>()`, not the concrete type.
4. **Keyed services for strategy pattern** — .NET 8+ keyed services replace manual factory patterns for selecting between implementations.
## Patterns
### Keyed Services (.NET 8+)
Use keyed services to register and resolve multiple implementations of the same interface.
```csharp
// Registration
builder.Services.AddKeyedScoped<INotificationService, EmailNotificationService>("email");
builder.Services.AddKeyedScoped<INotificationService, SmsNotificationService>("sms");
builder.Services.AddKeyedScoped<INotificationService, PushNotificationService>("push");
// Resolution via attribute
public class OrderHandler([FromKeyedServices("email")] INotificationService notifier)
{
public async Task Handle(CreateOrder.Command command, CancellationToken ct)
{
// ... create order
await notifier.SendAsync(notification, ct);
}
}
// Resolution via IServiceProvider
public class NotificationRouter(IServiceProvider provider)
{
public INotificationService GetService(string channel)
{
return provider.GetRequiredKeyedService<INotificationService>(channel);
}
}
```
### Decorator Pattern
```csharp
// Base service
public interface IOrderService
{
Task<Result<Order>> CreateAsync(CreateOrderRequest request, CancellationToken ct);
}
public class OrderService(AppDbContext db, TimeProvider clock) : IOrderService
{
public async Task<Result<Order>> CreateAsync(CreateOrderRequest request, CancellationToken ct)
{
var order = Order.Create(request, clock.GetUtcNow());
db.Orders.Add(order);
await db.SaveChangesAsync(ct);
return Result.Success(order);
}
}
// Decorator — adds logging
public class LoggingOrderService(IOrderService inner, ILogger<LoggingOrderService> logger) : IOrderService
{
public async Task<Result<Order>> CreateAsync(CreateOrderRequest request, CancellationToken ct)
{
logger.LogInformation("Creating order for customer {CustomerId}", request.CustomerId);
var result = await inner.CreateAsync(request, ct);
if (result.IsSuccess)
logger.LogInformation("Order {OrderId} created", result.Value.Id);
return result;
}
}
// Registration with Scrutor
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.Decorate<IOrderService, LoggingOrderService>();
```
### Registration by Convention (Scrutor)
```csharp
// Auto-register all services matching a convention
builder.Services.Scan(scan => scan
.FromAssemblyOf<Program>()
.AddClasses(classes => classes.AssignableTo<ITransientService>())
.AsImplementedInterfaces()
.WithTransientLifetime()
.AddClasses(classes => classes.AssignableTo<IScopedService>())
.AsImplementedInterfaces()
.WithScopedLifetime());
```
### Factory Pattern
When you need runtime logic to select an implementation.
```csharp
builder.Services.AddScoped<IPaymentProcessor>(sp =>
{
var config = sp.GetRequiredService<IOptions<PaymentOptions>>().Value;
return config.Provider switch
{
"stripe" => ActivatorUtilities.CreateInstance<StripeProcessor>(sp),
"paypal" => ActivatorUtilities.CreateInstance<PayPalProcessor>(sp),
_ => throw new InvalidOperationException($"Unknown payment provider: {config.Provider}")
};
});
```
### Options Registration
```csharp
// Bind configuration section to a strongly-typed options class
builder.Services.AddOptions<JwtOptions>()
.BindConfiguration("Jwt")
.ValidateDataAnnotations()
.ValidateOnStart();
// Inject as IOptions<T>
public class TokenService(IOptions<JwtOptions> options)
{
private readonly JwtOptions _jwt = options.Value;
}
```
## Anti-patterns
### Don't Capture Scoped Services in Singletons
```csharp
// BAD — DbContext is scoped, captured by singleton = memory leak + stale data
builder.Services.AddSingleton<OrderCache>(); // depends on AppDbContext
// GOOD — use IServiceScopeFactory in singleton
public class OrderCache(IServiceScopeFactory scopeFactory)
{
public async Task<Order?> GetAsync(Guid id)
{
await using var scope = scopeFactory.CreateAsyncScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return await db.Orders.FindAsync(id);
}
}
```
### Don't Register Everything as Singleton
```csharp
// BAD — making a service singleton when it holds mutable state
builder.Services.AddSingleton<OrderService>(); // has DbContext dependency
// GOOD — match the lifetime to the service's needs
builder.Services.AddScoped<OrderService>();
```
## Decision Guide
| Scenario | Recommendation |
|----------|---------------|
| Stateless service | Scoped (default) or Transient |
| Configuration / cache | Singleton |
| DbContext | Scoped (registered by `AddDbContext`) |
| Multiple implementations | Keyed services (strategy pattern) |
| Cross-cutting behavior | Decorator pattern |
| Convention-based registration | Scrutor |
| Runtime implementation selection | Factory delegate |
| Audit existing registrations | `get_di_registrations` MCP tool — lifetimes, duplicates, captive-dependency risks in one call |
| Strongly-typed config | `AddOptions<T>().BindConfiguration()` |
© codewithmukesh · MIT · codewithmukesh/dotnet-claude-kit