关于 dependency-injection
面向 .NET 开发者的依赖注入实践指南,介绍注册、生命周期管理和常见模式。内容还涵盖使用 MCP 工具检查已有注册中的生命周期、重复项和依赖风险。
主要功能
- 01构造函数注入与接口注册
- 02服务生命周期选择
- 03键控服务与工厂模式
- 04Scrutor 约定式注册
- 05强类型选项配置
适用场景
- 01为同一接口选择不同实现
- 02按约定批量注册服务
- 03配置并验证强类型选项
- 04检查生命周期错误与重复注册
MCP 相关
README 提及 `get_di_registrations` MCP 工具,可一次查看依赖注入注册的生命周期、重复项和捕获作用域依赖风险。
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ codewithmukesh/ dotnet-claude-kit --skill dependency-injection安装后在 Claude Code 中输入/dependency-injection确认已加载。
详细介绍
依赖注入
核心原则
- 默认使用构造函数注入 — 通过构造函数注入依赖项(主构造函数可让这一过程更加简洁)。不要使用服务定位器或属性注入。
- 谨慎匹配生命周期 — 单例绝不能依赖作用域或瞬态服务。这是最常见的 DI 错误。
- 注册接口,解析接口 — 注册
services.AddScoped<IOrderService, OrderService>(),而不是具体类型。 - 使用键控服务实现策略模式 — .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