dotnet-csharp-dependency-injection
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesedotnet-csharp-dependency-injection
dotnet-csharp-dependency-injection
Advanced Microsoft.Extensions.DependencyInjection patterns for .NET applications. Covers service lifetimes, keyed services (net8.0+), decoration, factory delegates, scope validation, and hosted service registration.
Cross-references: [skill:dotnet-csharp-async-patterns] for async patterns, [skill:dotnet-csharp-configuration] for binding.
BackgroundServiceIOptions<T>适用于.NET应用的高级Microsoft.Extensions.DependencyInjection模式。涵盖服务生命周期、键控服务(net8.0+)、装饰器、工厂委托、作用域验证和托管服务注册。
交叉参考:[skill:dotnet-csharp-async-patterns] 了解异步模式,[skill:dotnet-csharp-configuration] 了解绑定。
BackgroundServiceIOptions<T>Service Lifetimes
服务生命周期
| Lifetime | Registration | When to Use |
|---|---|---|
| Transient | | Lightweight, stateless services. New instance per injection. |
| Scoped | | Per-request state (EF Core |
| Singleton | | Thread-safe, stateless, or shared state (caches, config). |
| 生命周期 | 注册方式 | 使用场景 |
|---|---|---|
| Transient | | 轻量级、无状态服务。每次注入都会创建新实例。 |
| Scoped | | 每个请求的状态(如EF Core |
| Singleton | | 线程安全、无状态或共享状态(如缓存、配置)。 |
Lifetime Mismatches (Captive Dependencies)
生命周期不匹配(捕获依赖)
Never inject a shorter-lived service into a longer-lived one:
csharp
// WRONG -- scoped DbContext captured in singleton = same context for all requests
builder.Services.AddSingleton<OrderService>(); // singleton
builder.Services.AddScoped<AppDbContext>(); // scoped -- CAPTIVE!
// CORRECT -- use IServiceScopeFactory in singletons
public sealed class OrderService(IServiceScopeFactory scopeFactory)
{
public async Task ProcessAsync(CancellationToken ct = default)
{
using var scope = scopeFactory.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await db.Orders.Where(o => o.IsPending).ToListAsync(ct);
}
}切勿将短生命周期服务注入长生命周期服务:
csharp
// 错误 -- 单例中捕获作用域DbContext = 所有请求使用同一个上下文
builder.Services.AddSingleton<OrderService>(); // 单例
builder.Services.AddScoped<AppDbContext>(); // 作用域 -- 被捕获!
// 正确 -- 在单例中使用IServiceScopeFactory
public sealed class OrderService(IServiceScopeFactory scopeFactory)
{
public async Task ProcessAsync(CancellationToken ct = default)
{
using var scope = scopeFactory.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await db.Orders.Where(o => o.IsPending).ToListAsync(ct);
}
}Enable Scope Validation (Development)
启用作用域验证(开发环境)
csharp
var builder = WebApplication.CreateBuilder(args);
// In Development, ValidateScopes is already true by default.
// For non-web hosts:
var host = Host.CreateDefaultBuilder(args)
.UseDefaultServiceProvider(options =>
{
options.ValidateScopes = true;
options.ValidateOnBuild = true; // Validates all registrations at startup
})
.Build();csharp
var builder = WebApplication.CreateBuilder(args);
// 在开发环境中,ValidateScopes默认已设为true。
// 对于非Web主机:
var host = Host.CreateDefaultBuilder(args)
.UseDefaultServiceProvider(options =>
{
options.ValidateScopes = true;
options.ValidateOnBuild = true; // 在启动时验证所有注册
})
.Build();Registration Patterns
注册模式
Interface-Implementation Pair
接口-实现配对
csharp
builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();csharp
builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();Multiple Implementations
多实现注册
csharp
// Register multiple implementations
builder.Services.AddScoped<INotifier, EmailNotifier>();
builder.Services.AddScoped<INotifier, SmsNotifier>();
builder.Services.AddScoped<INotifier, PushNotifier>();
// Inject all -- order matches registration order
public sealed class CompositeNotifier(IEnumerable<INotifier> notifiers)
{
public async Task NotifyAsync(string message, CancellationToken ct = default)
{
foreach (var notifier in notifiers)
{
await notifier.NotifyAsync(message, ct);
}
}
}csharp
// 注册多个实现
builder.Services.AddScoped<INotifier, EmailNotifier>();
builder.Services.AddScoped<INotifier, SmsNotifier>();
builder.Services.AddScoped<INotifier, PushNotifier>();
// 注入所有实现 -- 顺序与注册顺序一致
public sealed class CompositeNotifier(IEnumerable<INotifier> notifiers)
{
public async Task NotifyAsync(string message, CancellationToken ct = default)
{
foreach (var notifier in notifiers)
{
await notifier.NotifyAsync(message, ct);
}
}
}Factory Delegates
工厂委托
csharp
builder.Services.AddScoped<IOrderService>(sp =>
{
var repo = sp.GetRequiredService<IOrderRepository>();
var logger = sp.GetRequiredService<ILogger<OrderService>>();
var options = sp.GetRequiredService<IOptions<OrderOptions>>();
return new OrderService(repo, logger, options.Value.MaxRetries);
});csharp
builder.Services.AddScoped<IOrderService>(sp =>
{
var repo = sp.GetRequiredService<IOrderRepository>();
var logger = sp.GetRequiredService<ILogger<OrderService>>();
var options = sp.GetRequiredService<IOptions<OrderOptions>>();
return new OrderService(repo, logger, options.Value.MaxRetries);
});TryAdd
for Library Registrations
TryAdd库注册使用TryAdd
TryAddLibraries should use so applications can override:
TryAddcsharp
// Library code -- won't overwrite app registrations
builder.Services.TryAddScoped<IOrderRepository, DefaultOrderRepository>();
// Application code -- takes precedence if registered first
builder.Services.AddScoped<IOrderRepository, CustomOrderRepository>();库应使用,以便应用可以覆盖注册:
TryAddcsharp
// 库代码 -- 不会覆盖应用的注册
builder.Services.TryAddScoped<IOrderRepository, DefaultOrderRepository>();
// 应用代码 -- 如果先注册则优先生效
builder.Services.AddScoped<IOrderRepository, CustomOrderRepository>();Keyed Services (net8.0+)
键控服务(net8.0+)
Register and resolve services by a key, replacing the need for named service patterns.
csharp
// Registration
builder.Services.AddKeyedScoped<ICache, RedisCache>("distributed");
builder.Services.AddKeyedScoped<ICache, MemoryCache>("local");
// Injection via attribute
public sealed class OrderService(
[FromKeyedServices("distributed")] ICache distributedCache,
[FromKeyedServices("local")] ICache localCache)
{
public async Task<Order?> GetAsync(int id, CancellationToken ct = default)
{
// Check local cache first, then distributed
return await localCache.GetAsync<Order>(id.ToString(), ct)
?? await distributedCache.GetAsync<Order>(id.ToString(), ct);
}
}
// Manual resolution
var cache = sp.GetRequiredKeyedService<ICache>("distributed");net8.0+ only. On earlier TFMs, use factory patterns or a dictionary-based approach.
通过键注册和解析服务,替代命名服务模式。
csharp
// 注册
builder.Services.AddKeyedScoped<ICache, RedisCache>("distributed");
builder.Services.AddKeyedScoped<ICache, MemoryCache>("local");
// 通过特性注入
public sealed class OrderService(
[FromKeyedServices("distributed")] ICache distributedCache,
[FromKeyedServices("local")] ICache localCache)
{
public async Task<Order?> GetAsync(int id, CancellationToken ct = default)
{
// 先检查本地缓存,再检查分布式缓存
return await localCache.GetAsync<Order>(id.ToString(), ct)
?? await distributedCache.GetAsync<Order>(id.ToString(), ct);
}
}
// 手动解析
var cache = sp.GetRequiredKeyedService<ICache>("distributed");仅支持net8.0+。在更早的TFM中,使用工厂模式或基于字典的方法。
Decoration Pattern
装饰器模式
The built-in container does not natively support decoration. Use one of these approaches:
内置容器本身不支持装饰器。可使用以下方法之一:
Manual Decoration
手动装饰
csharp
builder.Services.AddScoped<SqlOrderRepository>();
builder.Services.AddScoped<IOrderRepository>(sp =>
{
var inner = sp.GetRequiredService<SqlOrderRepository>();
var logger = sp.GetRequiredService<ILogger<LoggingOrderRepository>>();
return new LoggingOrderRepository(inner, logger);
});
public sealed class LoggingOrderRepository(
IOrderRepository inner,
ILogger<LoggingOrderRepository> logger) : IOrderRepository
{
public async Task<Order?> GetByIdAsync(int id, CancellationToken ct = default)
{
logger.LogInformation("Getting order {OrderId}", id);
return await inner.GetByIdAsync(id, ct);
}
}csharp
builder.Services.AddScoped<SqlOrderRepository>();
builder.Services.AddScoped<IOrderRepository>(sp =>
{
var inner = sp.GetRequiredService<SqlOrderRepository>();
var logger = sp.GetRequiredService<ILogger<LoggingOrderRepository>>();
return new LoggingOrderRepository(inner, logger);
});
public sealed class LoggingOrderRepository(
IOrderRepository inner,
ILogger<LoggingOrderRepository> logger) : IOrderRepository
{
public async Task<Order?> GetByIdAsync(int id, CancellationToken ct = default)
{
logger.LogInformation("获取订单 {OrderId}", id);
return await inner.GetByIdAsync(id, ct);
}
}Scrutor Library (Popular Alternative)
Scrutor库(常用替代方案)
csharp
builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();
builder.Services.Decorate<IOrderRepository, LoggingOrderRepository>();
builder.Services.Decorate<IOrderRepository, CachingOrderRepository>();
// Outer -> CachingOrderRepository -> LoggingOrderRepository -> SqlOrderRepositorycsharp
builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();
builder.Services.Decorate<IOrderRepository, LoggingOrderRepository>();
builder.Services.Decorate<IOrderRepository, CachingOrderRepository>();
// 外层 -> CachingOrderRepository -> LoggingOrderRepository -> SqlOrderRepositoryHosted Services and Background Workers
托管服务与后台工作者
BackgroundService
(Preferred)
BackgroundServiceBackgroundService
(推荐)
BackgroundServicecsharp
public sealed class QueueProcessorWorker(
IServiceScopeFactory scopeFactory,
ILogger<QueueProcessorWorker> logger) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
logger.LogInformation("Queue processor starting");
while (!stoppingToken.IsCancellationRequested)
{
try
{
using var scope = scopeFactory.CreateScope();
var processor = scope.ServiceProvider
.GetRequiredService<IQueueProcessor>();
await processor.ProcessNextBatchAsync(stoppingToken);
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
logger.LogError(ex, "Error processing queue batch");
}
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
}
}
// Registration
builder.Services.AddHostedService<QueueProcessorWorker>();csharp
public sealed class QueueProcessorWorker(
IServiceScopeFactory scopeFactory,
ILogger<QueueProcessorWorker> logger) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
logger.LogInformation("队列处理器启动");
while (!stoppingToken.IsCancellationRequested)
{
try
{
using var scope = scopeFactory.CreateScope();
var processor = scope.ServiceProvider
.GetRequiredService<IQueueProcessor>();
await processor.ProcessNextBatchAsync(stoppingToken);
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
logger.LogError(ex, "处理队列批次时出错");
}
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
}
}
// 注册
builder.Services.AddHostedService<QueueProcessorWorker>();IHostedService
(Startup/Shutdown Hooks)
IHostedServiceIHostedService
(启动/关闭钩子)
IHostedServicecsharp
public sealed class DatabaseMigrationService(
IServiceScopeFactory scopeFactory,
ILogger<DatabaseMigrationService> logger) : IHostedService
{
public async Task StartAsync(CancellationToken cancellationToken)
{
using var scope = scopeFactory.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await db.Database.MigrateAsync(cancellationToken);
logger.LogInformation("Database migration completed");
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
builder.Services.AddHostedService<DatabaseMigrationService>();csharp
public sealed class DatabaseMigrationService(
IServiceScopeFactory scopeFactory,
ILogger<DatabaseMigrationService> logger) : IHostedService
{
public async Task StartAsync(CancellationToken cancellationToken)
{
using var scope = scopeFactory.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await db.Database.MigrateAsync(cancellationToken);
logger.LogInformation("数据库迁移完成");
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
builder.Services.AddHostedService<DatabaseMigrationService>();Key Rules for Hosted Services
托管服务关键规则
- Always use to create scopes -- hosted services are singletons
IServiceScopeFactory - Never inject scoped services directly into hosted service constructors
- Handle exceptions inside -- unhandled exceptions stop the host (net8.0+)
ExecuteAsync - See [skill:dotnet-csharp-async-patterns] for async patterns in background workers
- 始终使用创建作用域 -- 托管服务是单例
IServiceScopeFactory - 切勿直接将作用域服务注入托管服务构造函数
- 在内处理异常 -- 未处理的异常会停止主机(net8.0+)
ExecuteAsync - 查看[skill:dotnet-csharp-async-patterns]了解后台工作者的异步模式
Organizing Registrations
组织注册
Group related registrations into extension methods for clean :
Program.cscsharp
// ServiceCollectionExtensions.cs
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddOrderServices(this IServiceCollection services)
{
services.AddScoped<IOrderRepository, SqlOrderRepository>();
services.AddScoped<IOrderService, OrderService>();
services.AddHostedService<OrderProcessorWorker>();
return services;
}
public static IServiceCollection AddNotificationServices(this IServiceCollection services)
{
services.AddScoped<INotifier, EmailNotifier>();
services.AddScoped<INotifier, SmsNotifier>();
return services;
}
}
// Program.cs
builder.Services.AddOrderServices();
builder.Services.AddNotificationServices();将相关注册分组到扩展方法中,使更简洁:
Program.cscsharp
// ServiceCollectionExtensions.cs
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddOrderServices(this IServiceCollection services)
{
services.AddScoped<IOrderRepository, SqlOrderRepository>();
services.AddScoped<IOrderService, OrderService>();
services.AddHostedService<OrderProcessorWorker>();
return services;
}
public static IServiceCollection AddNotificationServices(this IServiceCollection services)
{
services.AddScoped<INotifier, EmailNotifier>();
services.AddScoped<INotifier, SmsNotifier>();
return services;
}
}
// Program.cs
builder.Services.AddOrderServices();
builder.Services.AddNotificationServices();Testing with DI
使用DI进行测试
csharp
[Fact]
public async Task OrderService_UsesRepository()
{
// Arrange -- build a service provider for integration tests
var services = new ServiceCollection();
services.AddScoped<IOrderRepository, InMemoryOrderRepository>();
services.AddScoped<IOrderService, OrderService>();
services.AddLogging();
using var provider = services.BuildServiceProvider();
using var scope = provider.CreateScope();
var service = scope.ServiceProvider.GetRequiredService<IOrderService>();
// Act
var order = await service.GetByIdAsync(1);
// Assert
Assert.NotNull(order);
}For unit tests, prefer direct constructor injection with mocks rather than building a full container.
csharp
[Fact]
public async Task OrderService_UsesRepository()
{
// 准备 -- 为集成测试构建服务提供器
var services = new ServiceCollection();
services.AddScoped<IOrderRepository, InMemoryOrderRepository>();
services.AddScoped<IOrderService, OrderService>();
services.AddLogging();
using var provider = services.BuildServiceProvider();
using var scope = provider.CreateScope();
var service = scope.ServiceProvider.GetRequiredService<IOrderService>();
// 执行
var order = await service.GetByIdAsync(1);
// 断言
Assert.NotNull(order);
}对于单元测试,优先使用直接构造函数注入模拟对象,而非构建完整容器。