From dotnet-claude-kit
Provides .NET dependency injection patterns including service lifetimes, keyed services, decorators, factories, and pitfalls. Useful for service registration, lifetime resolution, and composition design.
npx claudepluginhub codewithmukesh/dotnet-claude-kit --plugin dotnet-claude-kitThis skill uses the workspace's default tool permissions.
1. **Constructor injection is the default** — Inject dependencies through the constructor (primary constructors make this clean). No service locator, no property injection.
Dependency injection patterns and best practices using Microsoft.Extensions.DependencyInjection for .NET applications. Use when configuring DI containers in .NET, choosing between service lifetimes (Singleton, Scoped, Transient), or implementing decorator patterns and service interception.
Provides C#/.NET patterns for production-grade APIs, MCP servers, and enterprise backends with DI, async, caching, EF Core/Dapper optimization, resilience, and testing.
Provides .NET backend patterns for APIs, MCP servers, and enterprise apps covering async/await, DI, EF Core, Dapper, configuration, Redis caching, and xUnit testing. Use for .NET development, code review, API architecture.
Share bugs, ideas, or general feedback.
services.AddScoped<IOrderService, OrderService>(), not the concrete type.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);
}
}
// 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>();
// 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());
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}")
};
});
// 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;
}
// 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);
}
}
// 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>();
| 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() |