研发协作
在 .NET 中组织依赖注入注册并正确管理服务生命周期
当 ASP.NET Core 的 Program.cs 注册过多、需要在生产与测试间复用服务配置,或需要为后台服务/Akka 演员手动创建 DI 作用域时使用。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 49/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 59/100 ×0.15
适用场景
在 .NET 中组织依赖注入注册并正确管通用
解决思路
- 把相关服务注册按功能分组到扩展方法中,例如 AddUserServices()、AddOrderServices()、AddEmailServices(),让 Program.cs 只负责链式组合这些 Add* 方法。
- 每个扩展方法返回 IServiceCollection 以支持链式调用,例如 return services;。
- 在扩展方法中通过 services.AddOptions<T>().BindConfiguration(configSectionName).ValidateDataAnnotations().ValidateOnStart() 绑定并校验配置。
- 把扩展方法文件放在其注册的服务附近,命名约定为 {Feature}ServiceCollectionExtensions.cs。
- 遵循命名约定:Add{Feature}Services() 用于一般功能注册,Add{Feature}() 用于无歧义时的简写,Configure{Feature}() 用于主要设置选项,Use{Feature}() 用于 IApplicationBuilder 中间件。
- 在测试中复用生产扩展方法:在 WebApplicationFactory 的 ConfigureServices 中先调用生产 Add* 方法,再用 services.RemoveAll<T>() 加 services.AddSingleton<T, TestDouble>() 仅覆盖需要替换的外部依赖。
- 在 Akka.Hosting.TestKit 中同样复用生产配置:在 ConfigureAkka 中调用 builder.AddOrderActors(),在 ConfigureServices 中调用 services.AddOrderServices(),再覆盖外部依赖。
- 在独立单元测试中创建 ServiceCollection,调用生产扩展方法(如 services.AddUserServices()),再添加测试基础设施(如 InMemoryUserRepository),最后 BuildServiceProvider()。
- 对大型应用使用分层组合:顶层 AddAppServices() 调用 AddDomainServices()、AddInfrastructureServices()、AddApiServices(),各层再组合更细粒度的扩展方法。
- 对 Akka.NET 演员配置也使用扩展方法模式,例如在 AkkaConfigurationBuilder 上实现 AddOrderActors(),内部使用 WithActors 和 WithShardRegion 注册演员。
- 使用条件注册:根据 IHostEnvironment 判断,开发环境注册 MailpitEmailSender,生产环境注册 SmtpEmailSender。
- 使用工厂注册处理复杂初始化:services.AddSingleton<IPaymentProcessor>(sp => { var options = sp.GetRequiredService<IOptions<StripeOptions>>().Value; ... })。
- 在 .NET 8+ 中使用键控服务注册多个实现:services.AddKeyedSingleton<INotificationSender, EmailNotificationSender>("email"),再用 INotificationDispatcher 选择正确的实现。
- 根据状态选择生命周期:Singleton 用于无状态、线程安全、创建昂贵的服务(配置、HttpClient 工厂、缓存);Scoped 用于每请求有状态的服务(DbContext、仓储、用户上下文);Transient 用于轻量、有状态、创建廉价的服务(验证器、短生命周期助手)。
- 在 ASP.NET Core 控制器中可直接注入 Scoped 服务,因为每个 HTTP 请求自动创建作用域。
- 在 BackgroundService 中注入 IServiceProvider 或 IServiceScopeFactory,不要直接注入 Scoped 服务;在每个工作单元中用 _serviceProvider.CreateScope() 或 _scopeFactory.CreateScope() 手动创建作用域,再通过 scope.ServiceProvider.GetRequiredService<T>() 解析 Scoped 服务。
- 在 Akka.NET 演员中如需 Scoped 服务,注入 IServiceProvider,在每条消息处理时用 using var scope = _serviceProvider.CreateScope() 创建作用域,再解析 UserManager、IOrderRepository 等 Scoped 服务,确保每条消息获得新的 DbContext 并在作用域释放时提交。
- 在演员中可直接注入 Singleton 服务(如 IEmailLinkGenerator),无需创建作用域。
- 避免在 Singleton 中直接注入 Scoped 服务;改为注入 IServiceProvider,在需要时创建作用域并解析 Scoped 服务。
- 避免在 Program.cs 中堆砌数百行注册;避免创建过于泛化的扩展方法(如 AddServices() 注册 50 个不相关的东西);避免在扩展方法中硬编码连接字符串等关键配置,应显式接受配置参数。
适用边界 · 注意事项
- 不适用于不使用 Microsoft.Extensions.DependencyInjection 的 .NET 项目或其他 DI 容器(如 Autofac、Unity)的特定用法。
- 不适用于非 .NET 技术栈的项目。
- Akka.NET 演员作用域管理部分仅适用于使用 Akka.NET 和 Akka.Hosting 的项目。