dependency-injection-patterns dependency-injection-patterns

组织ASP.NET Core应用程序中的服务注册,避免庞大的Program.cs文件,使服务配置在生产和测试之间可重用,设计与Microsoft.Extensions.DependencyInjection集成的库。

后端开发 0 次安装 0 次浏览 更新于 2/26/2026

依赖注入模式

何时使用此技能

当需要:

  • 在ASP.NET Core应用程序中组织服务注册
  • 避免Program.cs/Startup.cs文件中有数百个注册的庞大体积
  • 使服务配置在生产和测试之间可重用
  • 设计与Microsoft.Extensions.DependencyInjection集成的库

问题

没有组织,Program.cs变得难以管理:

// 坏: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多行 ...

问题:

  • 难以找到相关注册
  • 子系统之间没有明确的界限
  • 不能在测试中重用配置
  • 团队设置中的合并冲突
  • 没有封装内部依赖

解决方案:扩展方法组合

将相关注册分组到扩展方法中:

// 好:干净,可组合的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)
    {
        // 仓库
        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<IOrderEmailComposer, OrderEmailComposer>();

        // SMTP客户端依赖于环境
        services.AddScoped<IEmailSender, SmtpEmailSender>();

        return services;
    }
}

依赖其他扩展

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;
    }
}

文件组织

将扩展方法放置在它们注册的服务附近:

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()

约定{Feature}ServiceCollectionExtensions.cs紧挨着特性的服务。

命名约定

模式 用途
Add{Feature}Services() 一般特性注册
Add{Feature}() 无歧义时的短形式
Configure{Feature}() 主要设置选项时
Use{Feature}() 中间件(在IApplicationBuilder上)
// 特性服务
services.AddUserServices();
services.AddEmailServices();
services.AddPaymentServices();

// 第三方集成
services.AddStripePayments();
services.AddSendGridEmail();

// 配置繁重
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 =>
            {
                // 生产服务已经通过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

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>();
    }
}

独立单元测试

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);
    }
}

分层扩展

对于更大的应用程序,层次化地组合扩展:

// 顶级:应用程序需要的一切
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集成

相同的模式适用于Akka.NET演员配置:

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-actor-patterns技能以获取完整的Akka.Hosting模式。

常见模式

条件注册

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;
}

基于工厂的注册

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;
}

键控服务(.NET 8+)

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;
}

反模式

不要:在Program.cs中注册一切

// 坏:庞大的Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
// ... 200多行 ...

不要:创建过于通用的扩展

// 坏:太模糊,不能传达注册的内容
public static IServiceCollection AddServices(this IServiceCollection services)
{
    // 注册50个随机事项
}

不要:隐藏重要配置

// 坏:隐藏重要设置
public static IServiceCollection AddDatabase(this IServiceCollection services)
{
    services.AddDbContext<AppDbContext>(options =>
        options.UseSqlServer("hardcoded-connection-string"));  // 隐藏!
}

// 好:明确接受配置
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:无状态服务,安全共享
services.AddSingleton<IMjmlTemplateRenderer, MjmlTemplateRenderer>();
services.AddSingleton<IEmailLinkGenerator, EmailLinkGenerator>();

// SCOPED:数据库访问,每个请求状态
services.AddScoped<IUserRepository, UserRepository>();  // DbContext依赖
services.AddScoped<IOrderService, OrderService>();       // 使用范围仓库

// TRANSIENT:便宜,短命
services.AddTransient<CreateUserRequestValidator>();

范围要求

范围服务需要范围存在。 在ASP.NET Core中,每个HTTP请求自动创建一个范围。但在其他上下文(后台服务,演员)中,必须手动创建范围。

// ASP.NET控制器 - 自动存在范围
public class OrdersController : ControllerBase
{
    private readonly IOrderService _orderService;  // 范围 - 有效!

    public OrdersController(IOrderService orderService)
    {
        _orderService = orderService;
    }
}

// 后台服务 - 没有自动范围!
public class OrderProcessingService : BackgroundService
{
    private readonly IServiceProvider _serviceProvider;

    public OrderProcessingService(IServiceProvider serviceProvider)
    {
        // 注入IServiceProvider,而不是直接注入范围服务
        _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演员范围管理

演员没有自动DI范围。 如果您需要在演员内部使用范围服务,注入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)
    {
        // 为这个消息处理创建范围
        using var scope = _serviceProvider.CreateScope();

        // 解析范围服务
        var userManager = scope.ServiceProvider.GetRequiredService<UserManager<User>>();
        var orderRepository = scope.ServiceProvider.GetRequiredService<IOrderRepository>();
        var emailComposer = scope.ServiceProvider.GetRequiredService<IPaymentEmailComposer>();

        // 使用范围服务进行工作
        var user = await userManager.FindByIdAsync(msg.UserId);
        var order = await orderRepository.CreateAsync(msg.Order);

        // DbContext在范围释放时提交
    }
}

为什么这个模式有效

  1. 每个消息获得新的DbContext - 没有陈旧的实体跟踪
  2. 正确处置 - 连接在每个消息后释放
  3. 隔离 - 一个消息的错误不影响其他消息
  4. 可测试 - 可以注入模拟IServiceProvider

演员中的Singleton服务

对于无状态服务,直接注入(不需要范围):

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参考

Akka.NET的DI集成文档在:

常见错误

将范围注入到Singleton中

// 坏:Singleton捕获范围服务 - 陈旧的DbContext!
public class CacheService  // 注册为Singleton
{
    private readonly IUserRepository _repo;  // 范围!

    public CacheService(IUserRepository repo)  // 在启动时捕获!
    {
        _repo = repo;  // 这个DbContext永远存在 - 坏
    }
}

// 好:注入工厂或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);
    }
}

后台工作中没有范围

// 坏:没有范围用于范围服务
public class BadBackgroundService : BackgroundService
{
    private readonly IOrderService _orderService;  // 范围!

    public BadBackgroundService(IOrderService orderService)
    {
        _orderService = orderService;  // 将抛出或行为异常
    }
}

// 好:为每个工作单元创建范围
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>();
        // ...
    }
}