skills/core/dependency-injection/SKILL.md
Use when registering services, choosing lifetimes, or implementing DI patterns like decorator or keyed services.
npx skillsauth add faysilalshareef/dotnet-ai-kit dependency-injectionInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
IServiceCollection extension methods// Organize by layer or feature
public static class DependencyInjection
{
public static IServiceCollection AddApplicationServices(
this IServiceCollection services)
{
services.AddMediatR(cfg =>
cfg.RegisterServicesFromAssembly(typeof(DependencyInjection).Assembly));
services.AddValidatorsFromAssembly(
typeof(DependencyInjection).Assembly);
return services;
}
public static IServiceCollection AddInfrastructureServices(
this IServiceCollection services, IConfiguration configuration)
{
// Scoped — one instance per HTTP request
services.AddScoped<IOrderRepository, OrderRepository>();
services.AddScoped<IUnitOfWork, UnitOfWork>();
// Open generics
services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));
// Singleton — one instance for the app lifetime
services.AddSingleton<IDateTimeProvider, DateTimeProvider>();
// Transient — new instance every time
services.AddTransient<IEmailSender, SmtpEmailSender>();
return services;
}
}
// Program.cs
builder.Services
.AddApplicationServices()
.AddInfrastructureServices(builder.Configuration);
Singleton — stateless services, caches, configuration wrappers
Example: IDateTimeProvider, HybridCache, IOptions<T>
Warning: must be thread-safe
Scoped — per-request state, DbContext, UnitOfWork, repositories
Example: AppDbContext, ICurrentUserService
Default choice for most services
Transient — lightweight stateless services, factory output
Example: IEmailSender, validators
Warning: do not hold expensive resources
// Registration
services.AddKeyedSingleton<IPaymentGateway, StripeGateway>("stripe");
services.AddKeyedSingleton<IPaymentGateway, PayPalGateway>("paypal");
services.AddKeyedSingleton<IPaymentGateway, SquareGateway>("square");
// Injection via attribute
public sealed class CheckoutService(
[FromKeyedServices("stripe")] IPaymentGateway gateway)
{
public Task ChargeAsync(decimal amount) => gateway.ChargeAsync(amount);
}
// Resolution from provider
public sealed class PaymentRouter(IServiceProvider provider)
{
public IPaymentGateway GetGateway(string name)
=> provider.GetRequiredKeyedService<IPaymentGateway>(name);
}
// Base service
services.AddScoped<IOrderService, OrderService>();
// Decorate with cross-cutting concerns (outermost last)
services.Decorate<IOrderService, CachedOrderService>();
services.Decorate<IOrderService, LoggingOrderService>();
// Decorator implementation
public sealed class CachedOrderService(
IOrderService inner,
HybridCache cache) : IOrderService
{
public async Task<Order?> GetOrderAsync(Guid id, CancellationToken ct)
{
return await cache.GetOrCreateAsync(
$"orders:{id}",
async token => await inner.GetOrderAsync(id, token),
cancellationToken: ct);
}
}
services.AddScoped<IReportGenerator>(sp =>
{
var config = sp.GetRequiredService<IOptions<ReportOptions>>().Value;
return config.Format switch
{
"pdf" => sp.GetRequiredService<PdfReportGenerator>(),
"csv" => sp.GetRequiredService<CsvReportGenerator>(),
_ => throw new InvalidOperationException(
$"Unknown report format: {config.Format}")
};
});
// Background services are singletons — create scopes for scoped deps
public sealed class OrderProcessor(
IServiceScopeFactory scopeFactory,
ILogger<OrderProcessor> logger) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
using var scope = scopeFactory.CreateScope();
var repository = scope.ServiceProvider
.GetRequiredService<IOrderRepository>();
var unitOfWork = scope.ServiceProvider
.GetRequiredService<IUnitOfWork>();
await ProcessPendingOrdersAsync(repository, unitOfWork, ct);
await Task.Delay(TimeSpan.FromSeconds(30), ct);
}
}
}
// Strongly-typed options with validation
services.AddOptions<DatabaseOptions>()
.BindConfiguration(DatabaseOptions.SectionName)
.ValidateDataAnnotations()
.ValidateOnStart();
services.AddOptions<JwtOptions>()
.BindConfiguration("Jwt")
.ValidateDataAnnotations()
.ValidateOnStart();
// BAD: Service locator — hides dependencies
public class OrderService(IServiceProvider provider)
{
public void Process()
{
var repo = provider.GetRequiredService<IOrderRepository>();
}
}
// BAD: Captive dependency — scoped inside singleton
services.AddSingleton<MySingleton>(); // holds IOrderRepository (scoped)
// BAD: Resolving in constructor
public class OrderService(IServiceProvider provider)
{
private readonly IOrderRepository _repo =
provider.GetRequiredService<IOrderRepository>(); // resolves too early
}
// BAD: new-ing up services manually
public class OrderController
{
private readonly OrderService _service = new OrderService(
new OrderRepository(new AppDbContext())); // defeats DI
}
builder.Services.Add calls in Program.csIServiceCollection extension methods in the codebase[FromKeyedServices] usage (indicates .NET 8+ keyed DI)AddScoped, AddTransient, AddSingleton registration patternsIServiceScopeFactory usage in background servicesScrutor package (provides Decorate<> extension)builder.Services.Add* calls into extension methods per layerValidateOnStart() to all Options registrationsScrutor for decorator support if not already available:
<PackageReference Include="Scrutor" />
| Scenario | Lifetime | Notes | |----------|----------|-------| | DbContext | Scoped | One per request | | Repository | Scoped | Matches DbContext lifetime | | HttpClient handler | Transient | Via IHttpClientFactory | | Cache wrapper | Singleton | Thread-safe required | | Options | Singleton | Via IOptions<T> | | Current user | Scoped | Per-request context | | Background service | Singleton | Use IServiceScopeFactory inside |
data-ai
Use when about to claim work is complete, fixed, passing, or ready — before committing, creating PRs, or moving to the next task. Requires running verification commands and confirming output before making any success claims.
development
Use when encountering any bug, test failure, build error, or unexpected behavior — before proposing fixes or making changes.
development
Use when checkpointing, wrapping up, or handing off an AI-assisted development session.
development
Use when following the Specification-Driven Development lifecycle from plan through ship.