mcpField
D
dependency-injection-patterns

dependency-injection-patterns

github.com/aaronontheweb开发1.2k681 次安装今天更新
代码仓库

关于 dependency-injection-patterns

面向 ASP.NET Core 开发者的依赖注入模式技能,介绍如何用扩展方法组织服务注册,并在测试中复用生产配置。也涵盖服务生命周期和作用域管理。

主要功能

  1. 01按功能组织服务注册
  2. 02可链式调用的扩展方法
  3. 03分层组合注册配置
  4. 04复用生产配置进行测试
  5. 05服务生命周期与作用域管理

适用场景

  1. 01整理大型 Program.cs 注册代码
  2. 02在测试中复用服务配置
  3. 03构建可配置的服务库
  4. 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>();
        // ...
    }
}

资源

评论

支持 Markdown