关于 microsoft-extensions-dependency-injection
面向 .NET 开发者的技能,指导将 Microsoft.Extensions.DependencyInjection 注册整理为可组合的扩展方法,并涵盖配置复用、生命周期、测试及 Akka.NET Actor 作用域管理。
主要功能
- 01用扩展方法组织注册
- 02分层组合扩展方法
- 03在测试中复用注册配置
- 04选择服务生命周期
- 05管理 Akka.NET Actor 作用域
适用场景
- 01拆分庞大的 Program.cs
- 02在测试中复用生产环境注册
- 03配置特定功能的服务
- 04在 Actor 中使用有作用域的依赖
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill microsoft-extensions-dependency-injection安装后在 Claude Code 中输入/microsoft-extensions-dependency-injection确认已加载。
详细介绍
依赖注入模式
何时使用此技能
在以下情况下使用此技能:
- 在 ASP.NET Core 应用程序中组织服务注册
- 避免
Program.cs/Startup.cs文件包含数百项注册而变得臃肿 - 让生产环境与测试之间能够复用服务配置
- 设计与 Microsoft.Extensions.DependencyInjection 集成的库
问题
如果不加以组织,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>();
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 ...
问题:
- 难以查找相关注册
- 子系统之间没有明确边界
- 无法在测试中复用配置
- 团队协作时容易发生合并冲突
- 无法封装内部依赖
解决方案:扩展方法组合
将相关注册分组到扩展方法中:
// GOOD: Clean, composable Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddUserServices()
.AddOrderServices()
.AddEmailServices()
.AddPaymentServices()
.AddValidators();
var app = builder.Build();
每个 Add* 方法都会封装一组内聚的注册。
扩展方法模式
基本结构
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;
}
}
配合配置使用
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;
}
}
依赖其他扩展方法
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;
}
}
文件组织
将扩展方法放在其注册的服务附近:
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()
约定:将 {Feature}ServiceCollectionExtensions.cs 放在功能服务旁边。
命名约定
| 模式 | 用途 |
|---|---|
Add{Feature}Services() | 通用功能注册 |
Add{Feature}() | 含义明确时使用的简短形式 |
Configure{Feature}() | 主要用于设置选项 |
Use{Feature}() | 中间件(用于 IApplicationBuilder) |
// Feature services
services.AddUserServices();
services.AddEmailServices();
services.AddPaymentServices();
// Third-party integrations
services.AddStripePayments();
services.AddSendGridEmail();
// Configuration-heavy
services.ConfigureAuthentication();
services.ConfigureAuthorization();
测试优势
主要优势:在测试中复用生产环境配置。
WebApplicationFactory
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);
}
}
Akka.Hosting.TestKit
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>();
}
}
独立单元测试
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);
}
}
分层扩展方法
对于较大的应用程序,可以按层级组合扩展方法:
// 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();
}
}
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);
})
.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();
});
完整的 Akka.Hosting 模式请参阅 akka/hosting-actor-patterns 技能。
常见模式
条件注册
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;
}
基于工厂的注册
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;
}
键控服务(.NET 8+)
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;
}
反模式
不要:在 Program.cs 中注册所有内容
// BAD: Massive Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
// ... 200 more lines ...
不要:创建过于通用的扩展方法
// BAD: Too vague, doesn't communicate what's registered
public static IServiceCollection AddServices(this IServiceCollection services)
{
// Registers 50 random things
}
不要:隐藏重要配置
// 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));
}
最佳实践总结
| 实践 | 优势 |
|---|---|
将相关服务分组到 Add* 方法中 | 让 Program.cs 保持简洁,边界清晰 |
| 将扩展方法放在其注册的服务附近 | 便于查找和维护 |
返回 IServiceCollection 以支持链式调用 | 流畅的 API |
| 接受配置参数 | 灵活性 |
使用一致的命名(Add{Feature}Services) | 易于发现 |
| 通过复用生产环境扩展方法进行测试 | 更有信心,减少重复 |
生命周期管理
根据状态选择合适的生命周期:
| 生命周期 | 适用场景 | 示例 |
|---|---|---|
| Singleton | 无状态、线程安全、创建开销大 | 配置、HttpClient 工厂、缓存 |
| Scoped | 每个请求有状态、数据库上下文 | DbContext、存储库、用户上下文 |
| Transient | 轻量、有状态、创建成本低 | 验证器、短期辅助对象 |
经验法则
// 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>();
作用域要求
**Scoped 服务需要存在一个作用域。**在 ASP.NET Core 中,每个 HTTP 请求都会自动创建一个作用域。但在其他上下文(后台服务、Actor)中,必须手动创建作用域。
// 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);
}
}
}
Akka.NET Actor 作用域管理
**Actor 不会自动获得 DI 作用域。**如果需要在 Actor 中使用 Scoped 服务,请注入 IServiceProvider 并手动创建作用域。
模式:每条消息一个作用域
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
}
}
此模式为何有效
- 每条消息都会创建新的 DbContext——不会跟踪过期实体
- 妥善释放——每条消息处理完后都会释放连接
- 隔离性——一条消息的错误不会影响其他消息
- 可测试——可以注入模拟的 IServiceProvider
Actor 中的单例服务
对于无状态服务,直接注入即可(无需作用域):
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);
}
}
Akka.DependencyInjection 参考资料
Akka.NET 的 DI 集成文档:
- Akka.DependencyInjection:https://getakka.net/articles/actors/dependency-injection.html
- Akka.Hosting:https://github.com/akkadotnet/Akka.Hosting
常见错误
将作用域服务注入单例
// 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);
}
}
后台工作中未创建作用域
// 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>();
// ...
}
}
资源
- 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技能
评论
支持 Markdown