microsoft-extensions-dependency-injection
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDependency Injection Patterns
依赖注入模式
When to Use This Skill
何时使用该技能
Use this skill when:
- Organizing service registrations in ASP.NET Core applications
- Avoiding massive Program.cs/Startup.cs files with hundreds of registrations
- Making service configuration reusable between production and tests
- Designing libraries that integrate with Microsoft.Extensions.DependencyInjection
当你需要处理以下场景时可以使用该技能:
- 整理ASP.NET Core应用中的服务注册逻辑
- 避免Program.cs/Startup.cs文件中堆积数百行注册代码导致过于臃肿
- 让生产环境和测试环境的服务配置可以复用
- 设计需要集成Microsoft.Extensions.DependencyInjection的类库
The Problem
存在的问题
Without organization, Program.cs becomes unmanageable:
csharp
// BAD: 200+ lines of unorganized registrations
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<IProductRepository, ProductRepository>();
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddScoped<IEmailSender, SmtpEmailSender>();
builder.Services.AddScoped<IEmailComposer, MjmlEmailComposer>();
builder.Services.AddSingleton<IEmailLinkGenerator, EmailLinkGenerator>();
builder.Services.AddScoped<IPaymentProcessor, StripePaymentProcessor>();
builder.Services.AddScoped<IInvoiceGenerator, InvoiceGenerator>();
// ... 150 more lines ...Problems:
- Hard to find related registrations
- No clear boundaries between subsystems
- Can't reuse configuration in tests
- Merge conflicts in team settings
- No encapsulation of internal dependencies
如果不做整理,Program.cs会变得难以维护:
csharp
// BAD: 200+行毫无组织的注册代码
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<IProductRepository, ProductRepository>();
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddScoped<IEmailSender, SmtpEmailSender>();
builder.Services.AddScoped<IEmailComposer, MjmlEmailComposer>();
builder.Services.AddSingleton<IEmailLinkGenerator, EmailLinkGenerator>();
builder.Services.AddScoped<IPaymentProcessor, StripePaymentProcessor>();
builder.Services.AddScoped<IInvoiceGenerator, InvoiceGenerator>();
// ... 还有150多行代码 ...存在的问题:
- 难以找到相关的注册代码
- 子系统之间没有明确的边界
- 测试环境中无法复用配置
- 团队协作时容易出现合并冲突
- 内部依赖没有封装
The Solution: Extension Method Composition
解决方案:扩展方法组合
Group related registrations into extension methods:
csharp
// GOOD: Clean, composable Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddUserServices()
.AddOrderServices()
.AddEmailServices()
.AddPaymentServices()
.AddValidators();
var app = builder.Build();Each method encapsulates a cohesive set of registrations.
Add*将相关的注册逻辑分组到扩展方法中:
csharp
// GOOD: 简洁、可组合的Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddUserServices()
.AddOrderServices()
.AddEmailServices()
.AddPaymentServices()
.AddValidators();
var app = builder.Build();每个方法都封装了一组内聚的注册逻辑。
Add*Extension Method Pattern
扩展方法模式
Basic Structure
基础结构
csharp
namespace MyApp.Users;
public static class UserServiceCollectionExtensions
{
public static IServiceCollection AddUserServices(this IServiceCollection services)
{
// Repositories
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IUserReadStore, UserReadStore>();
services.AddScoped<IUserWriteStore, UserWriteStore>();
// Services
services.AddScoped<IUserService, UserService>();
services.AddScoped<IUserValidationService, UserValidationService>();
// Return for chaining
return services;
}
}csharp
namespace MyApp.Users;
public static class UserServiceCollectionExtensions
{
public static IServiceCollection AddUserServices(this IServiceCollection services)
{
// 仓储层
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IUserReadStore, UserReadStore>();
services.AddScoped<IUserWriteStore, UserWriteStore>();
// 服务层
services.AddScoped<IUserService, UserService>();
services.AddScoped<IUserValidationService, UserValidationService>();
// 返回实例支持链式调用
return services;
}
}With Configuration
带配置的扩展方法
csharp
namespace MyApp.Email;
public static class EmailServiceCollectionExtensions
{
public static IServiceCollection AddEmailServices(
this IServiceCollection services,
string configSectionName = "EmailSettings")
{
// Bind configuration
services.AddOptions<EmailOptions>()
.BindConfiguration(configSectionName)
.ValidateDataAnnotations()
.ValidateOnStart();
// Register services
services.AddSingleton<IMjmlTemplateRenderer, MjmlTemplateRenderer>();
services.AddSingleton<IEmailLinkGenerator, EmailLinkGenerator>();
services.AddScoped<IUserEmailComposer, UserEmailComposer>();
services.AddScoped<IOrderEmailComposer, OrderEmailComposer>();
// SMTP client depends on environment
services.AddScoped<IEmailSender, SmtpEmailSender>();
return services;
}
}csharp
namespace MyApp.Email;
public static class EmailServiceCollectionExtensions
{
public static IServiceCollection AddEmailServices(
this IServiceCollection services,
string configSectionName = "EmailSettings")
{
// 绑定配置
services.AddOptions<EmailOptions>()
.BindConfiguration(configSectionName)
.ValidateDataAnnotations()
.ValidateOnStart();
// 注册服务
services.AddSingleton<IMjmlTemplateRenderer, MjmlTemplateRenderer>();
services.AddSingleton<IEmailLinkGenerator, EmailLinkGenerator>();
services.AddScoped<IUserEmailComposer, UserEmailComposer>();
services.AddScoped<IOrderEmailComposer, OrderEmailComposer>();
// SMTP客户端实现根据环境切换
services.AddScoped<IEmailSender, SmtpEmailSender>();
return services;
}
}With Dependencies on Other Extensions
依赖其他扩展的扩展方法
csharp
namespace MyApp.Orders;
public static class OrderServiceCollectionExtensions
{
public static IServiceCollection AddOrderServices(this IServiceCollection services)
{
// This subsystem depends on email services
// Caller is responsible for calling AddEmailServices() first
// Or we can call it here if it's idempotent
services.AddScoped<IOrderRepository, OrderRepository>();
services.AddScoped<IOrderService, OrderService>();
services.AddScoped<IOrderEmailNotifier, OrderEmailNotifier>();
return services;
}
}csharp
namespace MyApp.Orders;
public static class OrderServiceCollectionExtensions
{
public static IServiceCollection AddOrderServices(this IServiceCollection services)
{
// 该子系统依赖邮件服务
// 调用方需要先调用AddEmailServices()
// 如果方法是幂等的,也可以在这里直接调用
services.AddScoped<IOrderRepository, OrderRepository>();
services.AddScoped<IOrderService, OrderService>();
services.AddScoped<IOrderEmailNotifier, OrderEmailNotifier>();
return services;
}
}File Organization
文件组织方式
Place extension methods near the services they register:
src/
MyApp.Api/
Program.cs # Composes all Add* methods
MyApp.Users/
Services/
UserService.cs
IUserService.cs
Repositories/
UserRepository.cs
UserServiceCollectionExtensions.cs # AddUserServices()
MyApp.Orders/
Services/
OrderService.cs
OrderServiceCollectionExtensions.cs # AddOrderServices()
MyApp.Email/
Composers/
UserEmailComposer.cs
EmailServiceCollectionExtensions.cs # AddEmailServices()Convention: next to the feature's services.
{Feature}ServiceCollectionExtensions.cs将扩展方法放在它们所注册的服务附近:
src/
MyApp.Api/
Program.cs # 组合所有Add*方法
MyApp.Users/
Services/
UserService.cs
IUserService.cs
Repositories/
UserRepository.cs
UserServiceCollectionExtensions.cs # AddUserServices()实现
MyApp.Orders/
Services/
OrderService.cs
OrderServiceCollectionExtensions.cs # AddOrderServices()实现
MyApp.Email/
Composers/
UserEmailComposer.cs
EmailServiceCollectionExtensions.cs # AddEmailServices()实现约定:文件放在对应功能模块的服务同级目录下。
{功能模块}ServiceCollectionExtensions.csNaming Conventions
命名约定
| Pattern | Use For |
|---|---|
| General feature registration |
| Short form when unambiguous |
| When primarily setting options |
| Middleware (on IApplicationBuilder) |
csharp
// Feature services
services.AddUserServices();
services.AddEmailServices();
services.AddPaymentServices();
// Third-party integrations
services.AddStripePayments();
services.AddSendGridEmail();
// Configuration-heavy
services.ConfigureAuthentication();
services.ConfigureAuthorization();| 模式 | 适用场景 |
|---|---|
| 通用功能模块注册 |
| 不会产生歧义的场景下的简写形式 |
| 主要用于设置选项的场景 |
| 中间件注册(作用于IApplicationBuilder) |
csharp
// 功能模块服务注册
services.AddUserServices();
services.AddEmailServices();
services.AddPaymentServices();
// 第三方集成
services.AddStripePayments();
services.AddSendGridEmail();
// 重配置为主的场景
services.ConfigureAuthentication();
services.ConfigureAuthorization();Testing Benefits
测试优势
The main advantage: reuse production configuration in tests.
最主要的优势:可以在测试中复用生产环境的配置。
WebApplicationFactory
WebApplicationFactory示例
csharp
public class ApiTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
public ApiTests(WebApplicationFactory<Program> factory)
{
_factory = factory.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
// Production services already registered via Add* methods
// Only override what's different for testing
// Replace email sender with test double
services.RemoveAll<IEmailSender>();
services.AddSingleton<IEmailSender, TestEmailSender>();
// Replace external payment processor
services.RemoveAll<IPaymentProcessor>();
services.AddSingleton<IPaymentProcessor, FakePaymentProcessor>();
});
});
}
[Fact]
public async Task CreateOrder_SendsConfirmationEmail()
{
var client = _factory.CreateClient();
var emailSender = _factory.Services.GetRequiredService<IEmailSender>() as TestEmailSender;
await client.PostAsJsonAsync("/api/orders", new CreateOrderRequest(...));
Assert.Single(emailSender!.SentEmails);
}
}csharp
public class ApiTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
public ApiTests(WebApplicationFactory<Program> factory)
{
_factory = factory.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
// 生产环境服务已经通过Add*方法完成注册
// 只需要覆写测试环境需要修改的部分
// 将邮件发送器替换为测试替身
services.RemoveAll<IEmailSender>();
services.AddSingleton<IEmailSender, TestEmailSender>();
// 替换外部支付处理器
services.RemoveAll<IPaymentProcessor>();
services.AddSingleton<IPaymentProcessor, FakePaymentProcessor>();
});
});
}
[Fact]
public async Task CreateOrder_SendsConfirmationEmail()
{
var client = _factory.CreateClient();
var emailSender = _factory.Services.GetRequiredService<IEmailSender>() as TestEmailSender;
await client.PostAsJsonAsync("/api/orders", new CreateOrderRequest(...));
Assert.Single(emailSender!.SentEmails);
}
}Akka.Hosting.TestKit
Akka.Hosting.TestKit示例
csharp
public class OrderActorSpecs : Akka.Hosting.TestKit.TestKit
{
protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider)
{
// Reuse production Akka configuration
builder.AddOrderActors();
}
protected override void ConfigureServices(IServiceCollection services)
{
// Reuse production service configuration
services.AddOrderServices();
// Override only external dependencies
services.RemoveAll<IPaymentProcessor>();
services.AddSingleton<IPaymentProcessor, FakePaymentProcessor>();
}
[Fact]
public async Task OrderActor_ProcessesPayment()
{
var orderActor = ActorRegistry.Get<OrderActor>();
orderActor.Tell(new ProcessOrder(orderId));
ExpectMsg<OrderProcessed>();
}
}csharp
public class OrderActorSpecs : Akka.Hosting.TestKit.TestKit
{
protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider)
{
// 复用生产环境的Akka配置
builder.AddOrderActors();
}
protected override void ConfigureServices(IServiceCollection services)
{
// 复用生产环境的服务配置
services.AddOrderServices();
// 只覆写外部依赖
services.RemoveAll<IPaymentProcessor>();
services.AddSingleton<IPaymentProcessor, FakePaymentProcessor>();
}
[Fact]
public async Task OrderActor_ProcessesPayment()
{
var orderActor = ActorRegistry.Get<OrderActor>();
orderActor.Tell(new ProcessOrder(orderId));
ExpectMsg<OrderProcessed>();
}
}Standalone Unit Tests
独立单元测试示例
csharp
public class UserServiceTests
{
private readonly ServiceProvider _provider;
public UserServiceTests()
{
var services = new ServiceCollection();
// Reuse production registrations
services.AddUserServices();
// Add test infrastructure
services.AddSingleton<IUserRepository, InMemoryUserRepository>();
_provider = services.BuildServiceProvider();
}
[Fact]
public async Task CreateUser_ValidData_Succeeds()
{
var service = _provider.GetRequiredService<IUserService>();
var result = await service.CreateUserAsync(new CreateUserRequest(...));
Assert.True(result.IsSuccess);
}
}csharp
public class UserServiceTests
{
private readonly ServiceProvider _provider;
public UserServiceTests()
{
var services = new ServiceCollection();
// 复用生产环境的注册逻辑
services.AddUserServices();
// 添加测试基础设施
services.AddSingleton<IUserRepository, InMemoryUserRepository>();
_provider = services.BuildServiceProvider();
}
[Fact]
public async Task CreateUser_ValidData_Succeeds()
{
var service = _provider.GetRequiredService<IUserService>();
var result = await service.CreateUserAsync(new CreateUserRequest(...));
Assert.True(result.IsSuccess);
}
}Layered Extensions
分层扩展
For larger applications, compose extensions hierarchically:
csharp
// Top-level: Everything the app needs
public static class AppServiceCollectionExtensions
{
public static IServiceCollection AddAppServices(this IServiceCollection services)
{
return services
.AddDomainServices()
.AddInfrastructureServices()
.AddApiServices();
}
}
// Domain layer
public static class DomainServiceCollectionExtensions
{
public static IServiceCollection AddDomainServices(this IServiceCollection services)
{
return services
.AddUserServices()
.AddOrderServices()
.AddProductServices();
}
}
// Infrastructure layer
public static class InfrastructureServiceCollectionExtensions
{
public static IServiceCollection AddInfrastructureServices(this IServiceCollection services)
{
return services
.AddEmailServices()
.AddPaymentServices()
.AddStorageServices();
}
}对于大型应用,可以分层组合扩展方法:
csharp
// 顶层:应用需要的所有服务
public static class AppServiceCollectionExtensions
{
public static IServiceCollection AddAppServices(this IServiceCollection services)
{
return services
.AddDomainServices()
.AddInfrastructureServices()
.AddApiServices();
}
}
// 领域层
public static class DomainServiceCollectionExtensions
{
public static IServiceCollection AddDomainServices(this IServiceCollection services)
{
return services
.AddUserServices()
.AddOrderServices()
.AddProductServices();
}
}
// 基础设施层
public static class InfrastructureServiceCollectionExtensions
{
public static IServiceCollection AddInfrastructureServices(this IServiceCollection services)
{
return services
.AddEmailServices()
.AddPaymentServices()
.AddStorageServices();
}
}Akka.Hosting Integration
Akka.Hosting集成
The same pattern works for Akka.NET actor configuration:
csharp
public static class OrderActorExtensions
{
public static AkkaConfigurationBuilder AddOrderActors(
this AkkaConfigurationBuilder builder)
{
return builder
.WithActors((system, registry, resolver) =>
{
var orderProps = resolver.Props<OrderActor>();
var orderRef = system.ActorOf(orderProps, "orders");
registry.Register<OrderActor>(orderRef);
})
.WithShardRegion<OrderShardActor>(
typeName: "order-shard",
(system, registry, resolver) =>
entityId => resolver.Props<OrderShardActor>(entityId),
new OrderMessageExtractor(),
ShardOptions.Create());
}
}
// Usage in Program.cs
builder.Services.AddAkka("MySystem", (builder, sp) =>
{
builder
.AddOrderActors()
.AddInventoryActors()
.AddNotificationActors();
});See skill for complete Akka.Hosting patterns.
akka/hosting-actor-patterns同样的模式也适用于Akka.NET Actor配置:
csharp
public static class OrderActorExtensions
{
public static AkkaConfigurationBuilder AddOrderActors(
this AkkaConfigurationBuilder builder)
{
return builder
.WithActors((system, registry, resolver) =>
{
var orderProps = resolver.Props<OrderActor>();
var orderRef = system.ActorOf(orderProps, "orders");
registry.Register<OrderActor>(orderRef);
})
.WithShardRegion<OrderShardActor>(
typeName: "order-shard",
(system, registry, resolver) =>
entityId => resolver.Props<OrderShardActor>(entityId),
new OrderMessageExtractor(),
ShardOptions.Create());
}
}
// 在Program.cs中使用
builder.Services.AddAkka("MySystem", (builder, sp) =>
{
builder
.AddOrderActors()
.AddInventoryActors()
.AddNotificationActors();
});完整的Akka.Hosting模式可参考技能。
akka/hosting-actor-patternsCommon Patterns
常用模式
Conditional Registration
条件注册
csharp
public static IServiceCollection AddEmailServices(
this IServiceCollection services,
IHostEnvironment environment)
{
services.AddSingleton<IEmailComposer, MjmlEmailComposer>();
if (environment.IsDevelopment())
{
// Use Mailpit in development
services.AddSingleton<IEmailSender, MailpitEmailSender>();
}
else
{
// Use real SMTP in production
services.AddSingleton<IEmailSender, SmtpEmailSender>();
}
return services;
}csharp
public static IServiceCollection AddEmailServices(
this IServiceCollection services,
IHostEnvironment environment)
{
services.AddSingleton<IEmailComposer, MjmlEmailComposer>();
if (environment.IsDevelopment())
{
// 开发环境使用Mailpit
services.AddSingleton<IEmailSender, MailpitEmailSender>();
}
else
{
// 生产环境使用真实SMTP服务
services.AddSingleton<IEmailSender, SmtpEmailSender>();
}
return services;
}Factory-Based Registration
基于工厂的注册
csharp
public static IServiceCollection AddPaymentServices(
this IServiceCollection services,
string configSection = "Stripe")
{
services.AddOptions<StripeOptions>()
.BindConfiguration(configSection)
.ValidateOnStart();
// Factory for complex initialization
services.AddSingleton<IPaymentProcessor>(sp =>
{
var options = sp.GetRequiredService<IOptions<StripeOptions>>().Value;
var logger = sp.GetRequiredService<ILogger<StripePaymentProcessor>>();
return new StripePaymentProcessor(options.ApiKey, options.WebhookSecret, logger);
});
return services;
}csharp
public static IServiceCollection AddPaymentServices(
this IServiceCollection services,
string configSection = "Stripe")
{
services.AddOptions<StripeOptions>()
.BindConfiguration(configSection)
.ValidateOnStart();
// 工厂方法用于复杂初始化
services.AddSingleton<IPaymentProcessor>(sp =>
{
var options = sp.GetRequiredService<IOptions<StripeOptions>>().Value;
var logger = sp.GetRequiredService<ILogger<StripePaymentProcessor>>();
return new StripePaymentProcessor(options.ApiKey, options.WebhookSecret, logger);
});
return services;
}Keyed Services (.NET 8+)
键控服务(.NET 8+)
csharp
public static IServiceCollection AddNotificationServices(this IServiceCollection services)
{
// Register multiple implementations with keys
services.AddKeyedSingleton<INotificationSender, EmailNotificationSender>("email");
services.AddKeyedSingleton<INotificationSender, SmsNotificationSender>("sms");
services.AddKeyedSingleton<INotificationSender, PushNotificationSender>("push");
// Resolver that picks the right one
services.AddScoped<INotificationDispatcher, NotificationDispatcher>();
return services;
}csharp
public static IServiceCollection AddNotificationServices(this IServiceCollection services)
{
// 注册带键的多个实现
services.AddKeyedSingleton<INotificationSender, EmailNotificationSender>("email");
services.AddKeyedSingleton<INotificationSender, SmsNotificationSender>("sms");
services.AddKeyedSingleton<INotificationSender, PushNotificationSender>("push");
// 负责选择对应实现的分发器
services.AddScoped<INotificationDispatcher, NotificationDispatcher>();
return services;
}Anti-Patterns
反模式
Don't: Register Everything in Program.cs
不要:把所有注册逻辑都放在Program.cs里
csharp
// BAD: Massive Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
// ... 200 more lines ...csharp
// BAD: 过于臃肿的Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
// ... 还有200多行代码 ...Don't: Create Overly Generic Extensions
不要:创建过于通用的扩展方法
csharp
// BAD: Too vague, doesn't communicate what's registered
public static IServiceCollection AddServices(this IServiceCollection services)
{
// Registers 50 random things
}csharp
// BAD: 过于模糊,无法体现注册的内容
public static IServiceCollection AddServices(this IServiceCollection services)
{
// 注册了50个毫无关联的服务
}Don't: Hide Important Configuration
不要:隐藏重要配置
csharp
// BAD: Buried important settings
public static IServiceCollection AddDatabase(this IServiceCollection services)
{
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer("hardcoded-connection-string")); // Hidden!
}
// GOOD: Accept configuration explicitly
public static IServiceCollection AddDatabase(
this IServiceCollection services,
string connectionString)
{
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString));
}csharp
// BAD: 重要配置被隐藏
public static IServiceCollection AddDatabase(this IServiceCollection services)
{
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer("hardcoded-connection-string")); // 硬编码隐藏在方法内部!
}
// GOOD: 显式接收配置参数
public static IServiceCollection AddDatabase(
this IServiceCollection services,
string connectionString)
{
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString));
}Best Practices Summary
最佳实践总结
| Practice | Benefit |
|---|---|
Group related services into | Clean Program.cs, clear boundaries |
| Place extensions near the services they register | Easy to find and maintain |
Return | Fluent API |
| Accept configuration parameters | Flexibility |
Use consistent naming ( | Discoverability |
| Test by reusing production extensions | Confidence, less duplication |
| 实践 | 收益 |
|---|---|
将相关服务分组到 | 简洁的Program.cs,清晰的边界 |
| 将扩展方法放在所注册服务的附近 | 便于查找和维护 |
返回 | 流式API体验 |
| 接收配置参数 | 更高的灵活性 |
使用统一的命名规范( | 更好的可发现性 |
| 测试中复用生产环境的扩展方法 | 更高的可信度,减少重复代码 |
Lifetime Management
生命周期管理
Choose the right lifetime based on state:
| Lifetime | Use When | Examples |
|---|---|---|
| Singleton | Stateless, thread-safe, expensive to create | Configuration, HttpClient factories, caches |
| Scoped | Stateful per-request, database contexts | DbContext, repositories, user context |
| Transient | Lightweight, stateful, cheap to create | Validators, short-lived helpers |
根据服务的状态选择合适的生命周期:
| 生命周期 | 适用场景 | 示例 |
|---|---|---|
| Singleton | 无状态、线程安全、创建成本高的服务 | 配置、HttpClient工厂、缓存 |
| Scoped | 每个请求有独立状态、数据库上下文 | DbContext、仓储、用户上下文 |
| Transient | 轻量、有状态、创建成本低的服务 | 验证器、短生命周期辅助工具 |
Rules of Thumb
经验法则
csharp
// SINGLETON: Stateless services, shared safely
services.AddSingleton<IMjmlTemplateRenderer, MjmlTemplateRenderer>();
services.AddSingleton<IEmailLinkGenerator, EmailLinkGenerator>();
// SCOPED: Database access, per-request state
services.AddScoped<IUserRepository, UserRepository>(); // DbContext dependency
services.AddScoped<IOrderService, OrderService>(); // Uses scoped repos
// TRANSIENT: Cheap, short-lived
services.AddTransient<CreateUserRequestValidator>();csharp
// SINGLETON: 无状态服务,可以安全共享
services.AddSingleton<IMjmlTemplateRenderer, MjmlTemplateRenderer>();
services.AddSingleton<IEmailLinkGenerator, EmailLinkGenerator>();
// SCOPED: 数据库访问、每个请求独立的状态
services.AddScoped<IUserRepository, UserRepository>(); // 依赖DbContext
services.AddScoped<IOrderService, OrderService>(); // 依赖Scoped仓储
// TRANSIENT: 轻量、短生命周期
services.AddTransient<CreateUserRequestValidator>();Scope Requirements
作用域要求
Scoped services require a scope to exist. In ASP.NET Core, each HTTP request creates a scope automatically. But in other contexts (background services, actors), you must create scopes manually.
csharp
// ASP.NET Controller - scope exists automatically
public class OrdersController : ControllerBase
{
private readonly IOrderService _orderService; // Scoped - works!
public OrdersController(IOrderService orderService)
{
_orderService = orderService;
}
}
// Background Service - no automatic scope!
public class OrderProcessingService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
public OrderProcessingService(IServiceProvider serviceProvider)
{
// Inject IServiceProvider, NOT scoped services directly
_serviceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
// Create scope manually for each unit of work
using var scope = _serviceProvider.CreateScope();
var orderService = scope.ServiceProvider.GetRequiredService<IOrderService>();
await orderService.ProcessPendingOrdersAsync(ct);
await Task.Delay(TimeSpan.FromMinutes(1), ct);
}
}
}Scoped服务需要存在对应的作用域。 在ASP.NET Core中,每个HTTP请求会自动创建一个作用域。但在其他场景下(后台服务、Actor),你必须手动创建作用域。
csharp
// ASP.NET Controller - 作用域自动创建
public class OrdersController : ControllerBase
{
private readonly IOrderService _orderService; // Scoped - 正常工作!
public OrdersController(IOrderService orderService)
{
_orderService = orderService;
}
}
// 后台服务 - 没有自动创建的作用域!
public class OrderProcessingService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
public OrderProcessingService(IServiceProvider serviceProvider)
{
// 注入IServiceProvider,不要直接注入Scoped服务
_serviceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
// 为每个工作单元手动创建作用域
using var scope = _serviceProvider.CreateScope();
var orderService = scope.ServiceProvider.GetRequiredService<IOrderService>();
await orderService.ProcessPendingOrdersAsync(ct);
await Task.Delay(TimeSpan.FromMinutes(1), ct);
}
}
}Akka.NET Actor Scope Management
Akka.NET Actor作用域管理
Actors don't have automatic DI scopes. If you need scoped services inside an actor, inject and create scopes manually.
IServiceProviderActor没有自动DI作用域。 如果你需要在Actor内部使用Scoped服务,需要注入并手动创建作用域。
IServiceProviderPattern: Scope Per Message
模式:每个消息对应一个作用域
csharp
public sealed class AccountProvisionActor : ReceiveActor
{
private readonly IServiceProvider _serviceProvider;
private readonly IActorRef _mailingActor;
public AccountProvisionActor(
IServiceProvider serviceProvider,
IRequiredActor<MailingActor> mailingActor)
{
_serviceProvider = serviceProvider;
_mailingActor = mailingActor.ActorRef;
ReceiveAsync<ProvisionAccount>(HandleProvisionAccount);
}
private async Task HandleProvisionAccount(ProvisionAccount msg)
{
// Create scope for this message processing
using var scope = _serviceProvider.CreateScope();
// Resolve scoped services
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<User>>();
var orderRepository = scope.ServiceProvider.GetRequiredService<IOrderRepository>();
var emailComposer = scope.ServiceProvider.GetRequiredService<IPaymentEmailComposer>();
// Do work with scoped services
var user = await userManager.FindByIdAsync(msg.UserId);
var order = await orderRepository.CreateAsync(msg.Order);
// DbContext commits when scope disposes
}
}csharp
public sealed class AccountProvisionActor : ReceiveActor
{
private readonly IServiceProvider _serviceProvider;
private readonly IActorRef _mailingActor;
public AccountProvisionActor(
IServiceProvider serviceProvider,
IRequiredActor<MailingActor> mailingActor)
{
_serviceProvider = serviceProvider;
_mailingActor = mailingActor.ActorRef;
ReceiveAsync<ProvisionAccount>(HandleProvisionAccount);
}
private async Task HandleProvisionAccount(ProvisionAccount msg)
{
// 为本次消息处理创建作用域
using var scope = _serviceProvider.CreateScope();
// 解析Scoped服务
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<User>>();
var orderRepository = scope.ServiceProvider.GetRequiredService<IOrderRepository>();
var emailComposer = scope.ServiceProvider.GetRequiredService<IPaymentEmailComposer>();
// 使用Scoped服务完成业务逻辑
var user = await userManager.FindByIdAsync(msg.UserId);
var order = await orderRepository.CreateAsync(msg.Order);
// DbContext会在作用域释放时提交变更
}
}Why This Pattern Works
该模式的优势
- Each message gets fresh DbContext - No stale entity tracking
- Proper disposal - Connections released after each message
- Isolation - One message's errors don't affect others
- Testable - Can inject mock IServiceProvider
- 每个消息都使用全新的DbContext - 没有过时的实体跟踪问题
- 正确的资源释放 - 每条消息处理完后都会释放连接
- 隔离性 - 单条消息的错误不会影响其他消息
- 可测试性 - 可以注入模拟的IServiceProvider
Singleton Services in Actors
Actor中的Singleton服务
For stateless services, inject directly (no scope needed):
csharp
public sealed class NotificationActor : ReceiveActor
{
private readonly IEmailLinkGenerator _linkGenerator; // Singleton - OK!
private readonly IActorRef _mailingActor;
public NotificationActor(
IEmailLinkGenerator linkGenerator, // Direct injection
IRequiredActor<MailingActor> mailingActor)
{
_linkGenerator = linkGenerator;
_mailingActor = mailingActor.ActorRef;
Receive<SendWelcomeEmail>(Handle);
}
}对于无状态服务,可以直接注入(不需要作用域):
csharp
public sealed class NotificationActor : ReceiveActor
{
private readonly IEmailLinkGenerator _linkGenerator; // Singleton - 安全!
private readonly IActorRef _mailingActor;
public NotificationActor(
IEmailLinkGenerator linkGenerator, // 直接注入
IRequiredActor<MailingActor> mailingActor)
{
_linkGenerator = linkGenerator;
_mailingActor = mailingActor.ActorRef;
Receive<SendWelcomeEmail>(Handle);
}
}Akka.DependencyInjection Reference
Akka.DependencyInjection参考
Akka.NET's DI integration is documented at:
- Akka.DependencyInjection: https://getakka.net/articles/actors/dependency-injection.html
- Akka.Hosting: https://github.com/akkadotnet/Akka.Hosting
Akka.NET的DI集成文档可参考:
- Akka.DependencyInjection: https://getakka.net/articles/actors/dependency-injection.html
- Akka.Hosting: https://github.com/akkadotnet/Akka.Hosting
Common Mistakes
常见错误
Injecting Scoped into Singleton
将Scoped服务注入Singleton服务
csharp
// BAD: Singleton captures scoped service - stale DbContext!
public class CacheService // Registered as Singleton
{
private readonly IUserRepository _repo; // Scoped!
public CacheService(IUserRepository repo) // Captured at startup!
{
_repo = repo; // This DbContext lives forever - BAD
}
}
// GOOD: Inject factory or IServiceProvider
public class CacheService
{
private readonly IServiceProvider _serviceProvider;
public CacheService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task<User> GetUserAsync(string id)
{
using var scope = _serviceProvider.CreateScope();
var repo = scope.ServiceProvider.GetRequiredService<IUserRepository>();
return await repo.GetByIdAsync(id);
}
}csharp
// BAD: Singleton服务捕获Scoped服务 - 会导致DbContext过时!
public class CacheService // 注册为Singleton
{
private readonly IUserRepository _repo; // Scoped服务!
public CacheService(IUserRepository repo) // 启动时就被捕获!
{
_repo = repo; // 该DbContext会一直存在 - 严重问题
}
}
// GOOD: 注入工厂或者IServiceProvider
public class CacheService
{
private readonly IServiceProvider _serviceProvider;
public CacheService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task<User> GetUserAsync(string id)
{
using var scope = _serviceProvider.CreateScope();
var repo = scope.ServiceProvider.GetRequiredService<IUserRepository>();
return await repo.GetByIdAsync(id);
}
}No Scope in Background Work
后台任务中没有创建作用域
csharp
// BAD: No scope for scoped services
public class BadBackgroundService : BackgroundService
{
private readonly IOrderService _orderService; // Scoped!
public BadBackgroundService(IOrderService orderService)
{
_orderService = orderService; // Will throw or behave unexpectedly
}
}
// GOOD: Create scope for each unit of work
public class GoodBackgroundService : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
public GoodBackgroundService(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
protected override async Task ExecuteAsync(CancellationToken ct)
{
using var scope = _scopeFactory.CreateScope();
var orderService = scope.ServiceProvider.GetRequiredService<IOrderService>();
// ...
}
}csharp
// BAD: Scoped服务没有对应作用域
public class BadBackgroundService : BackgroundService
{
private readonly IOrderService _orderService; // Scoped服务!
public BadBackgroundService(IOrderService orderService)
{
_orderService = orderService; // 会抛出异常或者行为异常
}
}
// GOOD: 为每个工作单元创建作用域
public class GoodBackgroundService : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
public GoodBackgroundService(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
protected override async Task ExecuteAsync(CancellationToken ct)
{
using var scope = _scopeFactory.CreateScope();
var orderService = scope.ServiceProvider.GetRequiredService<IOrderService>();
// ...
}
}Resources
参考资源
- Microsoft.Extensions.DependencyInjection: https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection
- Akka.Hosting: https://github.com/akkadotnet/Akka.Hosting
- Akka.DependencyInjection: https://getakka.net/articles/actors/dependency-injection.html
- Options Pattern: See skill
microsoft-extensions/configuration
- Microsoft.Extensions.DependencyInjection: https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection
- Akka.Hosting: https://github.com/akkadotnet/Akka.Hosting
- Akka.DependencyInjection: https://getakka.net/articles/actors/dependency-injection.html
- 选项模式: 参考技能
microsoft-extensions/configuration