dependency-injection-patterns

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.DependencyIntegration集成的类库

Reference Files

参考文件

  • advanced-patterns.md: Testing with DI extensions, Akka.NET actor scope management, conditional/factory/keyed registration patterns

  • advanced-patterns.md:DI扩展的测试方法、Akka.NET actor作用域管理、条件/工厂/键控注册模式

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>();
// ... 150 more lines ...
Problems: hard to find related registrations, no clear boundaries, can't reuse in tests, merge conflicts.

如果不进行组织,Program.cs会变得难以维护:
csharp
// 糟糕示例: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>();
// ... 还有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();

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

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

var app = builder.Build();

Extension Method Pattern

扩展方法模式

Basic Structure

基础结构

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

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<IEmailSender, SmtpEmailSender>();

        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
    UserServiceCollectionExtensions.cs   # AddUserServices()
  MyApp.Orders/
    OrderServiceCollectionExtensions.cs  # AddOrderServices()
  MyApp.Email/
    EmailServiceCollectionExtensions.cs  # AddEmailServices()
Convention:
{Feature}ServiceCollectionExtensions.cs
next to the feature's services.

将扩展方法放在其注册的服务附近:
src/
  MyApp.Api/
    Program.cs                    # 组合所有Add*方法
  MyApp.Users/
    Services/
      UserService.cs
    UserServiceCollectionExtensions.cs   # 包含AddUserServices()
  MyApp.Orders/
    OrderServiceCollectionExtensions.cs  # 包含AddOrderServices()
  MyApp.Email/
    EmailServiceCollectionExtensions.cs  # 包含AddEmailServices()
约定
{Feature}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)

模式使用场景
Add{Feature}Services()
通用功能注册
Add{Feature}()
含义明确时的简写形式
Configure{Feature}()
主要用于设置选项时
Use{Feature}()
中间件(基于IApplicationBuilder)

Testing Benefits

测试优势

The
Add*
pattern lets you reuse production configuration in tests and only override what's different. Works with WebApplicationFactory, Akka.Hosting.TestKit, and standalone ServiceCollection.
See advanced-patterns.md for complete testing examples.

Add*模式允许你在测试中复用生产环境配置,仅覆盖需要修改的部分。适用于WebApplicationFactory、Akka.Hosting.TestKit和独立的ServiceCollection。
完整测试示例请查看 advanced-patterns.md

Layered Extensions

分层扩展方法

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

对于大型应用,可以按层级组合扩展方法:
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();
    }
}

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

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

// 在Program.cs中的使用方式
builder.Services.AddAkka("MySystem", (builder, sp) =>
{
    builder
        .AddOrderActors()
        .AddInventoryActors()
        .AddNotificationActors();
});
完整的Akka.Hosting模式请查看
akka-hosting-actor-patterns
技巧文档。

Anti-Patterns

反模式

Don't: Register Everything in Program.cs

不要:在Program.cs中注册所有服务

csharp
// BAD: Massive Program.cs with 200+ lines of registrations
csharp
// 糟糕示例:包含200+行注册代码的臃肿Program.cs

Don't: Create Overly Generic Extensions

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

csharp
// BAD: Too vague, doesn't communicate what's registered
public static IServiceCollection AddServices(this IServiceCollection services) { ... }
csharp
// 糟糕示例:过于模糊,无法明确注册了哪些服务
public static IServiceCollection AddServices(this IServiceCollection services) { ... }

Don't: Hide Important Configuration

不要:隐藏重要配置

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

csharp
// 糟糕示例:配置被隐藏
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));
}

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{Feature}Services)更好的可发现性
通过复用生产环境扩展方法进行测试提升信心、减少代码重复

Lifetime Management

生命周期管理

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
csharp
// 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 services require a scope. ASP.NET Core creates one per HTTP request. In background services and actors, create scopes manually.
See advanced-patterns.md for actor scope management patterns.

生命周期使用场景示例
Singleton(单例)无状态、线程安全、创建成本高配置、HttpClient工厂、缓存
Scoped(作用域)每个请求有独立状态、数据库上下文DbContext、仓储、用户上下文
Transient(瞬时)轻量级、有状态、创建成本低验证器、短生命周期助手类
csharp
// SINGLETON:无状态服务,可安全共享
services.AddSingleton<IMjmlTemplateRenderer, MjmlTemplateRenderer>();

// SCOPED:数据库访问、每个请求独立状态
services.AddScoped<IUserRepository, UserRepository>();

// TRANSIENT:轻量、短生命周期
services.AddTransient<CreateUserRequestValidator>();
作用域服务需要在作用域内使用。ASP.NET Core会为每个HTTP请求创建一个作用域。在后台服务和actor中,需要手动创建作用域。
actor作用域管理模式请查看 advanced-patterns.md

Common Mistakes

常见错误

Injecting Scoped into Singleton

将作用域服务注入单例服务

csharp
// 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);
    }
}
csharp
// 糟糕示例:单例服务捕获作用域服务 - 导致DbContext过期!
public class CacheService  // 注册为单例
{
    private readonly IUserRepository _repo;  // 作用域服务 - 在启动时被捕获!
}

// 优秀示例:注入IServiceProvider,每次操作创建作用域
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);
    }
}

No Scope in Background Work

后台任务中未创建作用域

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

csharp
// 糟糕示例:未为作用域服务创建作用域
public class BadBackgroundService : BackgroundService
{
    private readonly IOrderService _orderService;  // 作用域服务 - 会抛出异常!
}

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

Resources

参考资源