Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
codewithmukesh avatar

Dependency Injection

  • 132 installs
  • 629 repo stars
  • Updated August 3, 2026
  • codewithmukesh/dotnet-claude-kit

Applies .NET dependency injection patterns: correct service lifetimes, keyed services, decorators, and avoiding captive-dependency bugs.

About

Covers dependency injection patterns for .NET: service lifetimes, keyed services, the decorator and factory patterns, and common pitfalls like captive dependencies. A developer uses it when registering services, resolving lifetime issues, or designing service composition.

  • Covers service lifetimes, keyed services, decorator and factory patterns
  • Warns against captive dependencies (singleton depending on scoped/transient)

Dependency Injection by the numbers

  • 132 all-time installs (skills.sh)
  • Ranked #2,716 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/codewithmukesh/dotnet-claude-kit --skill dependency-injection

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs132
repo stars629
Last updatedAugust 3, 2026
Repositorycodewithmukesh/dotnet-claude-kit

What it does

Applies .NET dependency injection patterns: correct service lifetimes, keyed services, decorators, and avoiding captive-dependency bugs.

Files

SKILL.mdMarkdownGitHub ↗

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.

// 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

ScenarioRecommendation
Stateless serviceScoped (default) or Transient
Configuration / cacheSingleton
DbContextScoped (registered by AddDbContext)
Multiple implementationsKeyed services (strategy pattern)
Cross-cutting behaviorDecorator pattern
Convention-based registrationScrutor
Runtime implementation selectionFactory delegate
Strongly-typed configAddOptions<T>().BindConfiguration()

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.