mcpField
D
dependency-injection

dependency-injection

github.com/codewithmukesh开发7913.0k 次安装12 周前更新
代码仓库

关于 dependency-injection

面向 .NET 开发者的依赖注入实践指南,介绍注册、生命周期管理和常见模式。内容还涵盖使用 MCP 工具检查已有注册中的生命周期、重复项和依赖风险。

主要功能

  1. 01构造函数注入与接口注册
  2. 02服务生命周期选择
  3. 03键控服务与工厂模式
  4. 04Scrutor 约定式注册
  5. 05强类型选项配置

适用场景

  1. 01为同一接口选择不同实现
  2. 02按约定批量注册服务
  3. 03配置并验证强类型选项
  4. 04检查生命周期错误与重复注册

MCP 相关

README 提及 `get_di_registrations` MCP 工具,可一次查看依赖注入注册的生命周期、重复项和捕获作用域依赖风险。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/codewithmukesh/dotnet-claude-kit --skill dependency-injection

安装后在 Claude Code 中输入/dependency-injection确认已加载。

详细介绍

依赖注入

核心原则

  1. 默认使用构造函数注入 — 通过构造函数注入依赖项(主构造函数可让这一过程更加简洁)。不要使用服务定位器或属性注入。
  2. 谨慎匹配生命周期 — 单例绝不能依赖作用域或瞬态服务。这是最常见的 DI 错误。
  3. 注册接口,解析接口 — 注册 services.AddScoped<IOrderService, OrderService>(),而不是具体类型。
  4. 使用键控服务实现策略模式 — .NET 8+ 的键控服务取代了用于选择不同实现的手动工厂模式。

模式

键控服务(.NET 8+)

使用键控服务注册和解析同一接口的多个实现。

// 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>();
按约定注册(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());
工厂模式

需要在运行时通过逻辑选择实现时使用。

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>();

决策指南

场景建议
无状态服务作用域(默认)或瞬态
配置 / 缓存单例
DbContext作用域(由 AddDbContext 注册)
多个实现键控服务(策略模式)
横切行为装饰器模式
按约定注册Scrutor
运行时选择实现工厂委托
审查现有注册get_di_registrations MCP 工具 — 一次调用即可检查生命周期、重复项和依赖捕获风险
强类型配置AddOptions<T>().BindConfiguration()

评论

支持 Markdown