关于 dependency-injection-patterns
面向 ASP.NET Core 开发者的依赖注入模式技能,介绍如何用扩展方法组织服务注册,并在测试中复用生产配置。也涵盖服务生命周期和作用域管理。
主要功能
- 01按功能组织服务注册
- 02可链式调用的扩展方法
- 03分层组合注册配置
- 04复用生产配置进行测试
- 05服务生命周期与作用域管理
适用场景
- 01整理大型 Program.cs 注册代码
- 02在测试中复用服务配置
- 03构建可配置的服务库
- 04为后台任务管理作用域
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ aaronontheweb/ dotnet-skills --skill dependency-injection-patterns安装后在 Claude Code 中输入/dependency-injection-patterns确认已加载。
附带文件advanced-patterns.md
详细介绍
依赖注入模式
何时使用此技能
在以下场景中使用此技能:
- 在 ASP.NET Core 应用程序中组织服务注册
- 避免 Program.cs/Startup.cs 文件中出现数百条注册,导致文件变得臃肿
- 让服务配置可在生产环境和测试中复用
- 设计与 Microsoft.Extensions.DependencyInjection 集成的库
参考文件
- advanced-patterns.md:使用 DI 扩展进行测试、Akka.NET Actor 作用域管理、条件式/工厂式/键控注册模式
问题
如果缺少组织规划,Program.cs 会变得难以管理:
// 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>();
// ... 150 more lines ...
问题:难以找到相关注册,没有清晰的边界,无法在测试中复用,容易发生合并冲突。
解决方案:扩展方法组合
将相关注册分组到扩展方法中:
// GOOD: Clean, composable Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddUserServices()
.AddOrderServices()
.AddEmailServices()
.AddPaymentServices()
.AddValidators();
var app = builder.Build();
扩展方法模式
基本结构
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;
}
}
文件组织
将扩展方法放在其注册的服务附近:
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()
约定:将 {Feature}ServiceCollectionExtensions.cs 放在该功能的服务旁边。
命名约定
| 模式 | 用途 |
|---|---|
Add{Feature}Services() | 通用功能注册 |
Add{Feature}() | 名称明确时使用的简短形式 |
Configure{Feature}() | 主要用于设置选项 |
Use{Feature}() | 中间件(用于 IApplicationBuilder) |
测试优势
Add* 模式可让你在测试中复用生产配置,只覆盖不同之处。适用于 WebApplicationFactory、Akka.Hosting.TestKit 和独立的 ServiceCollection。
完整的测试示例请参见 advanced-patterns.md。
分层扩展
对于较大的应用程序,可按层级组合扩展方法:
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();
});
完整的 Akka.Hosting 模式请参见 akka-hosting-actor-patterns 技能。
反模式
不要:在 Program.cs 中注册所有内容
// BAD: Massive Program.cs with 200+ lines of registrations
不要:创建过于通用的扩展方法
// BAD: Too vague, doesn't communicate what's registered
public static IServiceCollection AddServices(this IServiceCollection services) { ... }
不要:隐藏重要配置
// BAD: Buried 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));
}
最佳实践总结
| 实践 | 优势 |
|---|---|
将相关服务分组到 Add* 方法中 | Program.cs 更整洁,边界更清晰 |
| 将扩展方法放在其注册的服务附近 | 易于查找和维护 |
返回 IServiceCollection 以支持链式调用 | 流畅的 API |
| 接受配置参数 | 灵活性 |
使用一致的命名(Add{Feature}Services) | 易于发现 |
| 通过复用生产环境扩展方法进行测试 | 更有把握,减少重复 |
生命周期管理
| 生命周期 | 使用场景 | 示例 |
|---|---|---|
| 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 中,需要手动创建作用域。
Actor 作用域管理模式请参见 advanced-patterns.md。
常见错误
将 Scoped 服务注入 Singleton
// 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>();
// ...
}
}
资源
- 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技能
评论
支持 Markdown