dependency-injection
Skill229 starsChanged 3 months agoApache-2.0View on GitHub
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".
We split this skill into the rules it gives an agent
2 rules. 0 are common, 2 are distinctive, and 0 are very specific to this project.
0
Common
Most of the 10 edited versions we read keep these.
2
Distinctive
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 25
Use keyed services to register and resolve multiple implementations of the same interface.
- Distinctive: line 107
When you need runtime logic to select an implementation.
“Common” is measured: kept by at least 6 in 10 of the 10 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Dependency Injection
Core Principles
- Constructor injection is the default — Inject dependencies through the constructor (primary constructors make this clean). No service locator, no property injection.
- Match lifetimes carefully — A singleton must never depend on a scoped or transient service. This is the most common DI bug.
- Register interfaces, resolve interfaces — Register
services.AddScoped<IOrderService, OrderService>(), not the concrete type. - 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.
// 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
// 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)
// 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.
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
// 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
// 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
// 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 |
| Strongly-typed config | AddOptions<T>().BindConfiguration() |
Other versions11 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 11
- trezor/trezor-suite★ 1.1ksame name
- jame581/GodotPrompter★ 870same name
- GoBetterAuth/go-better-auth★ 273same name
- garrettw/php-arch-skills★ 13same name
- HamzaSheikh768/Agents-Skills-Lap★ 3same name
- ffsshhttiikk/opencode-agents-skills★ 2same name
- kimtth/agent-skill-100-lines-or-less★ 2same name
- andr-ca/agentharness★ 1same name
- aubynsamuel/android-dev-skills★ 1same name
- gerivdb/SKILLS★ 1same name
1 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository96 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- .github/copilot-instructions.mdCopilot instructions
- 80-20-reviewSkill
- api-versioningSkill
- architecture-advisorSkill
- aspireSkill
- authenticationSkill
- autonomous-loopsSkill
- cachingSkill
- ci-cdSkill
- clean-architectureSkill
- code-review-workflowSkill
- configurationSkill
- container-publishSkill
- context-disciplineSkill
- convention-learnerSkill
- dddSkill
- de-sloppifySkill
- dockerSkill
- ef-coreSkill
- error-handlingSkill
- health-checkSkill
- httpclient-factorySkill
- instinct-systemSkill
- learning-logSkill
- loggingSkill
- messagingSkill
- migration-workflowSkill
- minimal-apiSkill
- model-selectionSkill
- modern-csharpSkill
- openapiSkill
- opentelemetrySkill
- project-setupSkill
- project-structureSkill
- resilienceSkill
- scaffoldingSkill
- scalarSkill
- security-scanSkill
- self-correction-loopSkill
- serilogSkill
- session-managementSkill
- split-memorySkill
- testingSkill
- verification-loopSkill
- vertical-sliceSkill
- workflow-masterySkill
- wrap-up-ritualSkill
- 80-20-reviewSkill
- api-versioningSkill
- architecture-advisorSkill
- aspireSkill
- authenticationSkill
- autonomous-loopsSkill
- cachingSkill
- ci-cdSkill
- clean-architectureSkill
- code-review-workflowSkill
- configurationSkill
- The first 60 shown.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

