.NET依赖注入高级应用实践
依赖注入在.NET中初看简单,但随着应用规模扩大,复杂性也随之增加。服务生命周期管理、多实现方案、配置验证等问题逐渐显现。
现代.NET提供了功能完善的内置容器,支持常见生命周期、构造函数注入、开放泛型、键控服务等特性。C# 12的主构造函数语法进一步简化了依赖声明。
services.AddScoped<IOrderProcessor, OrderProcessor>();
容器不应成为架构核心
内置容器设计简洁是优势而非缺陷。关键在于将服务注册视为组合过程,而非设计决策的堆积场。
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddApiDefaults()
.AddOrderModule(builder.Configuration)
.AddPaymentModule(builder.Configuration)
.AddNotificationModule(builder.Configuration);
var app = builder.Build();
app.MapOrderEndpoints();
app.MapPaymentEndpoints();
app.Run();
模块化注册示例:
public static class OrderModuleRegistration
{
public static IServiceCollection AddOrderModule(
this IServiceCollection services,
IConfiguration configuration)
{
services.AddScoped<IOrderStore, SqlOrderStore>();
services.AddScoped<IOrderNumberProvider, OrderNumberProvider>();
services.AddScoped<IOrderCreationHandler, OrderCreationHandler>();
services.AddOptions<OrderSettings>()
.Bind(configuration.GetSection(OrderSettings.SectionName))
.ValidateDataAnnotations()
.ValidateOnStart();
return services;
}
}
主构造函数优化依赖声明
主构造函数减少模板代码,使依赖关系更加清晰:
public sealed class OrderCreationHandler(
IOrderStore orderStore,
IPaymentProcessor paymentProcessor,
ITimeProvider timeProvider,
ILogger<OrderCreationHandler> logger)
{
public async Task ExecuteAsync(
CreateOrderCommand command,
CancellationToken cancellationToken)
{
logger.LogInformation(
"Creating order for customer {CustomerId}",
command.CustomerId);
var order = Order.Create(
command.CustomerId,
command.Items,
timeProvider.UtcNow);
await paymentProcessor.ProcessPaymentAsync(command.Payment, cancellationToken);
await orderStore.SaveAsync(order, cancellationToken);
}
}
生命周期设计决策
生命周期错误是常见问题。基本原则是长生命周期服务不应依赖短生命周期服务。
错误示例:
public sealed class OrderCache(OrderDbContext dbContext)
{
public Task<Order?> RetrieveAsync(
int orderId,
CancellationToken cancellationToken)
{
return dbContext.Orders
.FindAsync([orderId], cancellationToken)
.AsTask();
}
}
// 错误注册
services.AddDbContext<OrderDbContext>(options =>
{
options.UseSqlServer(connectionString);
});
services.AddSingleton<OrderCache>();
正确解决方案:
public sealed class OrderCache(
IMemoryCache cache,
IServiceScopeFactory scopeFactory)
{
public async Task<OrderSummary?> RetrieveAsync(
int orderId,
CancellationToken cancellationToken)
{
var cacheKey = $"orders:summary:{orderId}";
if (cache.TryGetValue(cacheKey, out OrderSummary? cached))
{
return cached;
}
using var scope = scopeFactory.CreateScope();
var dbContext = scope.ServiceProvider
.GetRequiredService<OrderDbContext>();
var order = await dbContext.Orders
.Where(x => x.Id == orderId)
.Select(x => new OrderSummary(
x.Id,
x.OrderNumber,
x.Status,
x.TotalAmount))
.SingleOrDefaultAsync(cancellationToken);
if (order != null)
{
cache.Set(cacheKey, order, TimeSpan.FromMinutes(5));
}
return order;
}
}
配置验证最佳实践
配置验证应在应用启动时完成:
public sealed class PaymentGatewaySettings
{
public const string SectionName = "PaymentGateway";
public required string BaseAddress { get; init; }
public required string ApiKey { get; init; }
public int TimeoutSeconds { get; init; } = 30;
}
// 注册时验证
services.AddOptions<PaymentGatewaySettings>()
.Bind(configuration.GetSection(PaymentGatewaySettings.SectionName))
.Validate(settings => Uri.TryCreate(
settings.BaseAddress,
UriKind.Absolute,
out _),
"PaymentGateway:BaseAddress must be valid absolute URL.")
.Validate(settings => !string.IsNullOrWhiteSpace(settings.ApiKey),
"PaymentGateway:ApiKey is required.")
.Validate(settings => settings.TimeoutSeconds >= 1 && settings.TimeoutSeconds <= 300,
"PaymentGateway:TimeoutSeconds must be between 1 and 300.")
.ValidateOnStart();
HttpClient工厂模式
使用类型化客户端管理HTTP连接:
public sealed class PaymentGatewayClient(HttpClient httpClient)
{
public async Task<PaymentResult> ProcessPaymentAsync(
PaymentRequest request,
CancellationToken cancellationToken)
{
using var response = await httpClient.PostAsJsonAsync(
"payments",
request,
cancellationToken);
response.EnsureSuccessStatusCode();
var result = await response.Content
.ReadFromJsonAsync<PaymentResult>(cancellationToken: cancellationToken);
return result ?? throw new InvalidOperationException(
"Payment gateway returned empty response.");
}
}
// 注册类型化客户端
services.AddHttpClient<PaymentGatewayClient>((sp, client) =>
{
var settings = sp
.GetRequiredService<IOptions<PaymentGatewaySettings>>()
.Value;
client.BaseAddress = new Uri(settings.BaseAddress);
client.Timeout = TimeSpan.FromSeconds(settings.TimeoutSeconds);
client.DefaultRequestHeaders.Add("X-Api-Key", settings.ApiKey);
});
后台服务中的作用域管理
后台服务需要正确处理作用域依赖:
public sealed class InvoiceProcessor(
IServiceScopeFactory scopeFactory,
ILogger<InvoiceProcessor> logger)
: BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
try
{
using var scope = scopeFactory.CreateScope();
var batchProcessor = scope.ServiceProvider
.GetRequiredService<IInvoiceBatchProcessor>();
await batchProcessor.ProcessPendingAsync(cancellationToken);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
break;
}
catch (Exception ex)
{
logger.LogError(ex, "Invoice processing failed");
}
await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken);
}
}
}
装饰器模式应用
使用装饰器实现横切关注点:
public sealed class LoggingCommandHandler<TCommand>(
ICommandHandler<TCommand> inner,
ILogger<LoggingCommandHandler<TCommand>> logger)
: ICommandHandler<TCommand>
{
public async Task ExecuteAsync(
TCommand command,
CancellationToken cancellationToken)
{
var commandName = typeof(TCommand).Name;
logger.LogInformation("Executing command {CommandName}", commandName);
try
{
await inner.ExecuteAsync(command, cancellationToken);
logger.LogInformation("Command {CommandName} completed", commandName);
}
catch (Exception ex)
{
logger.LogError(ex, "Command {CommandName} failed", commandName);
throw;
}
}
}
键控服务合理使用
键控服务适用于基础设施变体:
public interface IFileStorage
{
Task StoreAsync(string path, Stream content, CancellationToken cancellationToken);
}
public sealed class PublicFileStorage : IFileStorage
{
public Task StoreAsync(string path, Stream content, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
public sealed class PrivateFileStorage : IFileStorage
{
public Task StoreAsync(string path, Stream content, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
// 注册键控服务
services.AddKeyedScoped<IFileStorage, PublicFileStorage>("public");
services.AddKeyedScoped<IFileStorage, PrivateFileStorage>("private");
开放泛型注册
开放泛型适用于通用基础设施模式:
public interface IRepository<TEntity> where TEntity : class
{
Task<TEntity?> GetByIdAsync(int id, CancellationToken cancellationToken);
Task AddAsync(TEntity entity, CancellationToken cancellationToken);
}
public sealed class EfRepository<TEntity>(DbContext dbContext)
: IRepository<TEntity> where TEntity : class
{
public async Task<TEntity?> GetByIdAsync(int id, CancellationToken cancellationToken)
{
return await dbContext.Set<TEntity>()
.FindAsync([id], cancellationToken);
}
public async Task AddAsync(TEntity entity, CancellationToken cancellationToken)
{
await dbContext.Set<TEntity>()
.AddAsync(entity, cancellationToken);
}
}
// 泛型注册
services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));
模块边界保护
依赖注入应保护模块边界:
public interface IOrderReader
{
Task<OrderBillingView?> GetBillingViewAsync(int orderId, CancellationToken cancellationToken);
}
internal sealed class OrderReader(OrderDbContext dbContext) : IOrderReader
{
public Task<OrderBillingView?> GetBillingViewAsync(int orderId, CancellationToken cancellationToken)
{
return dbContext.Orders
.Where(x => x.Id == orderId)
.Select(x => new OrderBillingView(
x.Id,
x.CustomerId,
x.TotalAmount,
x.Currency))
.SingleOrDefaultAsync(cancellationToken);
}
}
实践性注册结构
中等规模应用推荐结构:
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddPresentationLayer()
.AddApplicationLayer()
.AddInfrastructureLayer(builder.Configuration)
.AddFeatureModules(builder.Configuration);
var app = builder.Build();
app.UseExceptionHandling();
app.UseAuthentication();
app.UseAuthorization();
app.MapApiEndpoints();
app.Run();
各层职责:
- 表现层:控制器、API端点、认证授权
- 应用层:命令处理器、验证器、领域服务
- 基础设施层:数据库、HTTP客户端、存储提供者
- 功能模块:特性区域组合