.NET 相依性注入進階規範:Generic Host、Worker Service、Keyed Services 與 Decorator 模式。
當使用者撰寫涉及 DI 容器進階配置的程式碼(如 Worker Service、多實作切換、Decorator 模式)時,請自動套用以下規範。基礎 DI Lifetime 規範參閱 csharp-aspnetcore skill。
Host.CreateDefaultBuilder() 或 Host.CreateApplicationBuilder() 建立宿主,統一 DI、Configuration、Logging 基礎設施。ServiceCollection 再 BuildServiceProvider(),除非是單元測試場景。// ✅ 正確
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<MyWorker>();
IHost host = builder.Build();
await host.RunAsync().ConfigureAwait(false);
// ❌ 錯誤:手動建立容器
ServiceCollection services = new();
services.AddSingleton<IMyService, MyService>();
ServiceProvider provider = services.BuildServiceProvider();
BackgroundService 並覆寫 ExecuteAsync()。ExecuteAsync() 內的迴圈必須檢查 CancellationToken,確保 Graceful Shutdown。IServiceScopeFactory 在迴圈內建立 Scope:public class OrderProcessorWorker(
IServiceScopeFactory scopeFactory,
ILogger<OrderProcessorWorker> logger
) : BackgroundService {
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
while (!stoppingToken.IsCancellationRequested) {
await using AsyncServiceScope scope = scopeFactory.CreateAsyncScope();
IOrderService orderService = scope.ServiceProvider
.GetRequiredService<IOrderService>();
await orderService.ProcessPendingOrdersAsync(stoppingToken)
.ConfigureAwait(false);
await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken)
.ConfigureAwait(false);
}
}
}
BackgroundService)。// ✅ 正確
builder.Services.AddScoped<IOrderService, OrderService>();
// ❌ 錯誤
builder.Services.AddScoped<OrderService>();
builder.Services.AddKeyedScoped<INotificationService, EmailNotificationService>("email");
builder.Services.AddKeyedScoped<INotificationService, SmsNotificationService>("sms");
// 消費端
public class OrderController(
[FromKeyedServices("email")] INotificationService emailNotifier
) { }
IEnumerable<T> 接收:builder.Services.AddScoped<IValidator, NameValidator>();
builder.Services.AddScoped<IValidator, EmailValidator>();
// 消費端:注入所有 IValidator 實作
public class ValidationService(IEnumerable<IValidator> validators) { }
builder.Services.AddScoped<IProductRepository, ProductRepository>();
builder.Services.Decorate<IProductRepository, CachedProductRepository>();
builder.Services.AddScoped<ProductRepository>();
builder.Services.AddScoped<IProductRepository>(sp => {
ProductRepository inner = sp.GetRequiredService<ProductRepository>();
return new CachedProductRepository(inner, sp.GetRequiredService<IMemoryCache>());
});
ValidateScopes 與 ValidateOnBuild,及早發現 Captive Dependency 與遺漏註冊:HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
if (builder.Environment.IsDevelopment()) {
builder.Services.BuildServiceProvider(new ServiceProviderOptions {
ValidateScopes = true,
ValidateOnBuild = true
});
}
IServiceProvider.GetService<T>()。服務應透過建構函式注入,而非在方法內自行解析。唯一允許的例外是工廠方法與 BackgroundService 內的 Scope 建立。IServiceProvider 存放於靜態欄位供全域存取(如 ServiceLocator.Instance)。