用途
该技能用于在 ASP.NET Core 应用中组织依赖注入(DI)的服务注册。当 Program.cs / Startup.cs 因大量注册而变得臃肿、难以查找相关注册、无法在测试中复用配置,或需要设计可与 Microsoft.Extensions.DependencyInjection 集成的库时,可使用本技能。
主要能力
- 将相关服务注册分组为 IServiceCollection 扩展方法,形成可组合的 Add* 方法。
- 支持带配置参数的扩展方法,例如绑定配置节、校验选项。
- 提供文件组织与命名约定建议。
- 支持分层组合扩展方法,适用于较大应用。
- 支持 Akka.Hosting 的 actor 配置复用同一模式。
- 说明生命周期管理(Singleton、Scoped、Transient)的适用场景。
- 列出常见错误与反模式。
使用方式
基本结构:在功能命名空间下创建静态类,提供返回 IServiceCollection 的扩展方法,内部完成注册后返回 services 以便链式调用。
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;
}
}
带配置的写法:
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<IEmailSender, SmtpEmailSender>();
return services;
}
}
在 Program.cs 中组合:
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddUserServices()
.AddOrderServices()
.AddEmailServices()
.AddPaymentServices()
.AddValidators();
var app = builder.Build();
文件组织:将扩展方法放在其注册的服务附近,约定为 {Feature}ServiceCollectionExtensions.cs。
src/
MyApp.Api/
Program.cs # Composes all Add* methods
MyApp.Users/
Services/
UserService.cs
UserServiceCollectionExtensions.cs # AddUserServices()
MyApp.Orders/
OrderServiceCollectionExtensions.cs # AddOrderServices()
MyApp.Email/
EmailServiceCollectionExtensions.cs # AddEmailServices()
命名约定:Add{Feature}Services() 用于一般功能注册;Add{Feature}() 为无歧义时的简写;Configure{Feature}() 用于主要设置选项;Use{Feature}() 用于 IApplicationBuilder 上的中间件。
分层扩展:较大应用可分层组合。
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();
}
}
Akka.Hosting 集成:同一模式可用于 Akka.NET actor 配置。
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);
});
}
}
// Usage in Program.cs
builder.Services.AddAkka("MySystem", (builder, sp) =>
{
builder
.AddOrderActors()
.AddInventoryActors()
.AddNotificationActors();
});
生命周期管理:
| Lifetime | Use When | Examples |
|---|---|---|
| Singleton | 无状态、线程安全、创建开销大 | 配置、HttpClient 工厂、缓存 |
| Scoped | 每请求有状态、数据库上下文 | DbContext、仓储、用户上下文 |
| Transient | 轻量、有状态、创建开销小 | 校验器、短生命周期辅助类 |
// SINGLETON: Stateless services, shared safely
services.AddSingleton<IMjmlTemplateRenderer, MjmlTemplateRenderer>();
// SCOPED: Database access, per-request state
services.AddScoped<IUserRepository, UserRepository>();
// TRANSIENT: Cheap, short-lived
services.AddTransient<CreateUserRequestValidator>();
Scoped 服务需要作用域。ASP.NET Core 每个 HTTP 请求创建一个作用域;在后台服务和 actor 中需手动创建作用域。
适用场景
- 在 ASP.NET Core 应用中组织服务注册。
- 避免 Program.cs / Startup.cs 出现数百行注册。
- 让服务配置在生产与测试之间复用。
- 设计可与 Microsoft.Extensions.DependencyInjection 集成的库。
- 使用 Akka.Hosting 配置 actor。
限制与注意事项
反模式:不要在 Program.cs 中注册所有内容;不要创建过于泛化的扩展(如 AddServices);不要隐藏重要配置(如硬编码连接字符串),应显式接受配置参数。
常见错误:单例捕获作用域服务会导致 DbContext 过期,应注入 IServiceProvider 并按操作创建作用域;后台任务缺少作用域会抛错,应使用 IServiceScopeFactory 为每个工作单元创建作用域。
// BAD: Singleton captures scoped service - stale DbContext!
public class CacheService // Registered as Singleton
{
private readonly IUserRepository _repo; // Scoped - captured at startup!
}
// GOOD: Inject IServiceProvider, create scope per operation
public class CacheService
{
private readonly IServiceProvider _serviceProvider;
public async Task<User> GetUserAsync(string id)
{
using var scope = _serviceProvider.CreateScope();
var repo = scope.ServiceProvider.GetRequiredService<IUserRepository>();
return await repo.GetByIdAsync(id);
}
}
// BAD: No scope for scoped services
public class BadBackgroundService : BackgroundService
{
private readonly IOrderService _orderService; // Scoped - will throw!
}
// GOOD: Create scope for each unit of work
public class GoodBackgroundService : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
protected override async Task ExecuteAsync(CancellationToken ct)
{
using var scope = _scopeFactory.CreateScope();
var orderService = scope.ServiceProvider.GetRequiredService<IOrderService>();
// ...
}
}
最佳实践摘要:将相关服务分组为 Add* 方法;将扩展放在其注册的服务附近;返回 IServiceCollection 以支持链式调用;接受配置参数;使用一致命名(Add{Feature}Services);通过复用生产扩展进行测试。
参考文件 advanced-patterns.md 包含:使用 DI 扩展进行测试、Akka.NET actor 作用域管理、条件/工厂/键控注册模式。
资源:
- 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: 参见 microsoft-extensions-configuration 技能