microsoft-extensions-dependency-injection

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Dependency Injection Patterns

依赖注入模式

When to Use This Skill

何时使用该技能

Use this skill when:
  • Organizing service registrations in ASP.NET Core applications
  • Avoiding massive Program.cs/Startup.cs files with hundreds of registrations
  • Making service configuration reusable between production and tests
  • Designing libraries that integrate with Microsoft.Extensions.DependencyInjection

当你需要处理以下场景时可以使用该技能:
  • 整理ASP.NET Core应用中的服务注册逻辑
  • 避免Program.cs/Startup.cs文件中堆积数百行注册代码导致过于臃肿
  • 让生产环境和测试环境的服务配置可以复用
  • 设计需要集成Microsoft.Extensions.DependencyInjection的类库

The Problem

存在的问题

Without organization, Program.cs becomes unmanageable:
csharp
// 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 ...
Problems:
  • Hard to find related registrations
  • No clear boundaries between subsystems
  • Can't reuse configuration in tests
  • Merge conflicts in team settings
  • No encapsulation of internal dependencies

如果不做整理,Program.cs会变得难以维护:
csharp
// BAD: 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多行代码 ...
存在的问题:
  • 难以找到相关的注册代码
  • 子系统之间没有明确的边界
  • 测试环境中无法复用配置
  • 团队协作时容易出现合并冲突
  • 内部依赖没有封装

The Solution: Extension Method Composition

解决方案:扩展方法组合

Group related registrations into extension methods:
csharp
// GOOD: Clean, composable Program.cs
var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddUserServices()
    .AddOrderServices()
    .AddEmailServices()
    .AddPaymentServices()
    .AddValidators();

var app = builder.Build();
Each
Add*
method encapsulates a cohesive set of registrations.

将相关的注册逻辑分组到扩展方法中:
csharp
// GOOD: 简洁、可组合的Program.cs
var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddUserServices()
    .AddOrderServices()
    .AddEmailServices()
    .AddPaymentServices()
    .AddValidators();

var app = builder.Build();
每个
Add*
方法都封装了一组内聚的注册逻辑。

Extension Method Pattern

扩展方法模式

Basic Structure

基础结构

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

With Configuration

带配置的扩展方法

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

With Dependencies on Other Extensions

依赖其他扩展的扩展方法

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

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

File Organization

文件组织方式

Place extension methods near the services they register:
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()
Convention:
{Feature}ServiceCollectionExtensions.cs
next to the feature's 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()实现
约定
{功能模块}ServiceCollectionExtensions.cs
文件放在对应功能模块的服务同级目录下。

Naming Conventions

命名约定

PatternUse For
Add{Feature}Services()
General feature registration
Add{Feature}()
Short form when unambiguous
Configure{Feature}()
When primarily setting options
Use{Feature}()
Middleware (on IApplicationBuilder)
csharp
// Feature services
services.AddUserServices();
services.AddEmailServices();
services.AddPaymentServices();

// Third-party integrations
services.AddStripePayments();
services.AddSendGridEmail();

// Configuration-heavy
services.ConfigureAuthentication();
services.ConfigureAuthorization();

模式适用场景
Add{功能模块}Services()
通用功能模块注册
Add{功能模块}()
不会产生歧义的场景下的简写形式
Configure{功能模块}()
主要用于设置选项的场景
Use{功能模块}()
中间件注册(作用于IApplicationBuilder)
csharp
// 功能模块服务注册
services.AddUserServices();
services.AddEmailServices();
services.AddPaymentServices();

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

// 重配置为主的场景
services.ConfigureAuthentication();
services.ConfigureAuthorization();

Testing Benefits

测试优势

The main advantage: reuse production configuration in tests.
最主要的优势:可以在测试中复用生产环境的配置

WebApplicationFactory

WebApplicationFactory示例

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

Akka.Hosting.TestKit示例

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

Standalone Unit Tests

独立单元测试示例

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

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

Layered Extensions

分层扩展

For larger applications, compose extensions hierarchically:
csharp
// 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();
    }
}

对于大型应用,可以分层组合扩展方法:
csharp
// 顶层:应用需要的所有服务
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 Integration

Akka.Hosting集成

The same pattern works for Akka.NET actor configuration:
csharp
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();
});
See
akka/hosting-actor-patterns
skill for complete Akka.Hosting patterns.

同样的模式也适用于Akka.NET Actor配置:
csharp
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模式可参考
akka/hosting-actor-patterns
技能。

Common Patterns

常用模式

Conditional Registration

条件注册

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

Factory-Based Registration

基于工厂的注册

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

Keyed Services (.NET 8+)

键控服务(.NET 8+)

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

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

Anti-Patterns

反模式

Don't: Register Everything in Program.cs

不要:把所有注册逻辑都放在Program.cs里

csharp
// BAD: Massive Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
// ... 200 more lines ...
csharp
// BAD: 过于臃肿的Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
// ... 还有200多行代码 ...

Don't: Create Overly Generic Extensions

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

csharp
// BAD: Too vague, doesn't communicate what's registered
public static IServiceCollection AddServices(this IServiceCollection services)
{
    // Registers 50 random things
}
csharp
// BAD: 过于模糊,无法体现注册的内容
public static IServiceCollection AddServices(this IServiceCollection services)
{
    // 注册了50个毫无关联的服务
}

Don't: Hide Important Configuration

不要:隐藏重要配置

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

csharp
// BAD: 重要配置被隐藏
public static IServiceCollection AddDatabase(this IServiceCollection services)
{
    services.AddDbContext<AppDbContext>(options =>
        options.UseSqlServer("hardcoded-connection-string"));  // 硬编码隐藏在方法内部!
}

// GOOD: 显式接收配置参数
public static IServiceCollection AddDatabase(
    this IServiceCollection services,
    string connectionString)
{
    services.AddDbContext<AppDbContext>(options =>
        options.UseSqlServer(connectionString));
}

Best Practices Summary

最佳实践总结

PracticeBenefit
Group related services into
Add*
methods
Clean Program.cs, clear boundaries
Place extensions near the services they registerEasy to find and maintain
Return
IServiceCollection
for chaining
Fluent API
Accept configuration parametersFlexibility
Use consistent naming (
Add{Feature}Services
)
Discoverability
Test by reusing production extensionsConfidence, less duplication

实践收益
将相关服务分组到
Add*
方法中
简洁的Program.cs,清晰的边界
将扩展方法放在所注册服务的附近便于查找和维护
返回
IServiceCollection
支持链式调用
流式API体验
接收配置参数更高的灵活性
使用统一的命名规范(
Add{功能模块}Services
更好的可发现性
测试中复用生产环境的扩展方法更高的可信度,减少重复代码

Lifetime Management

生命周期管理

Choose the right lifetime based on state:
LifetimeUse WhenExamples
SingletonStateless, thread-safe, expensive to createConfiguration, HttpClient factories, caches
ScopedStateful per-request, database contextsDbContext, repositories, user context
TransientLightweight, stateful, cheap to createValidators, short-lived helpers
根据服务的状态选择合适的生命周期:
生命周期适用场景示例
Singleton无状态、线程安全、创建成本高的服务配置、HttpClient工厂、缓存
Scoped每个请求有独立状态、数据库上下文DbContext、仓储、用户上下文
Transient轻量、有状态、创建成本低的服务验证器、短生命周期辅助工具

Rules of Thumb

经验法则

csharp
// 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>();
csharp
// SINGLETON: 无状态服务,可以安全共享
services.AddSingleton<IMjmlTemplateRenderer, MjmlTemplateRenderer>();
services.AddSingleton<IEmailLinkGenerator, EmailLinkGenerator>();

// SCOPED: 数据库访问、每个请求独立的状态
services.AddScoped<IUserRepository, UserRepository>();  // 依赖DbContext
services.AddScoped<IOrderService, OrderService>();       // 依赖Scoped仓储

// TRANSIENT: 轻量、短生命周期
services.AddTransient<CreateUserRequestValidator>();

Scope Requirements

作用域要求

Scoped services require a scope to exist. In ASP.NET Core, each HTTP request creates a scope automatically. But in other contexts (background services, actors), you must create scopes manually.
csharp
// 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);
        }
    }
}

Scoped服务需要存在对应的作用域。 在ASP.NET Core中,每个HTTP请求会自动创建一个作用域。但在其他场景下(后台服务、Actor),你必须手动创建作用域。
csharp
// ASP.NET Controller - 作用域自动创建
public class OrdersController : ControllerBase
{
    private readonly IOrderService _orderService;  // Scoped - 正常工作!

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

// 后台服务 - 没有自动创建的作用域!
public class OrderProcessingService : BackgroundService
{
    private readonly IServiceProvider _serviceProvider;

    public OrderProcessingService(IServiceProvider serviceProvider)
    {
        // 注入IServiceProvider,不要直接注入Scoped服务
        _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 Actor Scope Management

Akka.NET Actor作用域管理

Actors don't have automatic DI scopes. If you need scoped services inside an actor, inject
IServiceProvider
and create scopes manually.
Actor没有自动DI作用域。 如果你需要在Actor内部使用Scoped服务,需要注入
IServiceProvider
并手动创建作用域。

Pattern: Scope Per Message

模式:每个消息对应一个作用域

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

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

        // 使用Scoped服务完成业务逻辑
        var user = await userManager.FindByIdAsync(msg.UserId);
        var order = await orderRepository.CreateAsync(msg.Order);

        // DbContext会在作用域释放时提交变更
    }
}

Why This Pattern Works

该模式的优势

  1. Each message gets fresh DbContext - No stale entity tracking
  2. Proper disposal - Connections released after each message
  3. Isolation - One message's errors don't affect others
  4. Testable - Can inject mock IServiceProvider
  1. 每个消息都使用全新的DbContext - 没有过时的实体跟踪问题
  2. 正确的资源释放 - 每条消息处理完后都会释放连接
  3. 隔离性 - 单条消息的错误不会影响其他消息
  4. 可测试性 - 可以注入模拟的IServiceProvider

Singleton Services in Actors

Actor中的Singleton服务

For stateless services, inject directly (no scope needed):
csharp
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);
    }
}
对于无状态服务,可以直接注入(不需要作用域):
csharp
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 Reference

Akka.DependencyInjection参考

Akka.NET's DI integration is documented at:

Akka.NET的DI集成文档可参考:

Common Mistakes

常见错误

Injecting Scoped into Singleton

将Scoped服务注入Singleton服务

csharp
// 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);
    }
}
csharp
// BAD: Singleton服务捕获Scoped服务 - 会导致DbContext过时!
public class CacheService  // 注册为Singleton
{
    private readonly IUserRepository _repo;  // Scoped服务!

    public CacheService(IUserRepository repo)  // 启动时就被捕获!
    {
        _repo = repo;  // 该DbContext会一直存在 - 严重问题
    }
}

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

No Scope in Background Work

后台任务中没有创建作用域

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

csharp
// BAD: Scoped服务没有对应作用域
public class BadBackgroundService : BackgroundService
{
    private readonly IOrderService _orderService;  // Scoped服务!

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

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

Resources

参考资源