dotnet-csharp-dependency-injection

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

dotnet-csharp-dependency-injection

dotnet-csharp-dependency-injection

Advanced Microsoft.Extensions.DependencyInjection patterns for .NET applications. Covers service lifetimes, keyed services (net8.0+), decoration, factory delegates, scope validation, and hosted service registration.
Cross-references: [skill:dotnet-csharp-async-patterns] for
BackgroundService
async patterns, [skill:dotnet-csharp-configuration] for
IOptions<T>
binding.

适用于.NET应用的高级Microsoft.Extensions.DependencyInjection模式。涵盖服务生命周期、键控服务(net8.0+)、装饰器、工厂委托、作用域验证和托管服务注册。
交叉参考:[skill:dotnet-csharp-async-patterns] 了解
BackgroundService
异步模式,[skill:dotnet-csharp-configuration] 了解
IOptions<T>
绑定。

Service Lifetimes

服务生命周期

LifetimeRegistrationWhen to Use
Transient
AddTransient<T>()
Lightweight, stateless services. New instance per injection.
Scoped
AddScoped<T>()
Per-request state (EF Core
DbContext
, unit of work).
Singleton
AddSingleton<T>()
Thread-safe, stateless, or shared state (caches, config).
生命周期注册方式使用场景
Transient
AddTransient<T>()
轻量级、无状态服务。每次注入都会创建新实例。
Scoped
AddScoped<T>()
每个请求的状态(如EF Core
DbContext
、工作单元)。
Singleton
AddSingleton<T>()
线程安全、无状态或共享状态(如缓存、配置)。

Lifetime Mismatches (Captive Dependencies)

生命周期不匹配(捕获依赖)

Never inject a shorter-lived service into a longer-lived one:
csharp
// WRONG -- scoped DbContext captured in singleton = same context for all requests
builder.Services.AddSingleton<OrderService>();    // singleton
builder.Services.AddScoped<AppDbContext>();        // scoped -- CAPTIVE!

// CORRECT -- use IServiceScopeFactory in singletons
public sealed class OrderService(IServiceScopeFactory scopeFactory)
{
    public async Task ProcessAsync(CancellationToken ct = default)
    {
        using var scope = scopeFactory.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        await db.Orders.Where(o => o.IsPending).ToListAsync(ct);
    }
}
切勿将短生命周期服务注入长生命周期服务:
csharp
// 错误 -- 单例中捕获作用域DbContext = 所有请求使用同一个上下文
builder.Services.AddSingleton<OrderService>();    // 单例
builder.Services.AddScoped<AppDbContext>();        // 作用域 -- 被捕获!

// 正确 -- 在单例中使用IServiceScopeFactory
public sealed class OrderService(IServiceScopeFactory scopeFactory)
{
    public async Task ProcessAsync(CancellationToken ct = default)
    {
        using var scope = scopeFactory.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        await db.Orders.Where(o => o.IsPending).ToListAsync(ct);
    }
}

Enable Scope Validation (Development)

启用作用域验证(开发环境)

csharp
var builder = WebApplication.CreateBuilder(args);
// In Development, ValidateScopes is already true by default.
// For non-web hosts:
var host = Host.CreateDefaultBuilder(args)
    .UseDefaultServiceProvider(options =>
    {
        options.ValidateScopes = true;
        options.ValidateOnBuild = true;  // Validates all registrations at startup
    })
    .Build();

csharp
var builder = WebApplication.CreateBuilder(args);
// 在开发环境中,ValidateScopes默认已设为true。
// 对于非Web主机:
var host = Host.CreateDefaultBuilder(args)
    .UseDefaultServiceProvider(options =>
    {
        options.ValidateScopes = true;
        options.ValidateOnBuild = true;  // 在启动时验证所有注册
    })
    .Build();

Registration Patterns

注册模式

Interface-Implementation Pair

接口-实现配对

csharp
builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();
csharp
builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();

Multiple Implementations

多实现注册

csharp
// Register multiple implementations
builder.Services.AddScoped<INotifier, EmailNotifier>();
builder.Services.AddScoped<INotifier, SmsNotifier>();
builder.Services.AddScoped<INotifier, PushNotifier>();

// Inject all -- order matches registration order
public sealed class CompositeNotifier(IEnumerable<INotifier> notifiers)
{
    public async Task NotifyAsync(string message, CancellationToken ct = default)
    {
        foreach (var notifier in notifiers)
        {
            await notifier.NotifyAsync(message, ct);
        }
    }
}
csharp
// 注册多个实现
builder.Services.AddScoped<INotifier, EmailNotifier>();
builder.Services.AddScoped<INotifier, SmsNotifier>();
builder.Services.AddScoped<INotifier, PushNotifier>();

// 注入所有实现 -- 顺序与注册顺序一致
public sealed class CompositeNotifier(IEnumerable<INotifier> notifiers)
{
    public async Task NotifyAsync(string message, CancellationToken ct = default)
    {
        foreach (var notifier in notifiers)
        {
            await notifier.NotifyAsync(message, ct);
        }
    }
}

Factory Delegates

工厂委托

csharp
builder.Services.AddScoped<IOrderService>(sp =>
{
    var repo = sp.GetRequiredService<IOrderRepository>();
    var logger = sp.GetRequiredService<ILogger<OrderService>>();
    var options = sp.GetRequiredService<IOptions<OrderOptions>>();
    return new OrderService(repo, logger, options.Value.MaxRetries);
});
csharp
builder.Services.AddScoped<IOrderService>(sp =>
{
    var repo = sp.GetRequiredService<IOrderRepository>();
    var logger = sp.GetRequiredService<ILogger<OrderService>>();
    var options = sp.GetRequiredService<IOptions<OrderOptions>>();
    return new OrderService(repo, logger, options.Value.MaxRetries);
});

TryAdd
for Library Registrations

库注册使用
TryAdd

Libraries should use
TryAdd
so applications can override:
csharp
// Library code -- won't overwrite app registrations
builder.Services.TryAddScoped<IOrderRepository, DefaultOrderRepository>();

// Application code -- takes precedence if registered first
builder.Services.AddScoped<IOrderRepository, CustomOrderRepository>();

库应使用
TryAdd
,以便应用可以覆盖注册:
csharp
// 库代码 -- 不会覆盖应用的注册
builder.Services.TryAddScoped<IOrderRepository, DefaultOrderRepository>();

// 应用代码 -- 如果先注册则优先生效
builder.Services.AddScoped<IOrderRepository, CustomOrderRepository>();

Keyed Services (net8.0+)

键控服务(net8.0+)

Register and resolve services by a key, replacing the need for named service patterns.
csharp
// Registration
builder.Services.AddKeyedScoped<ICache, RedisCache>("distributed");
builder.Services.AddKeyedScoped<ICache, MemoryCache>("local");

// Injection via attribute
public sealed class OrderService(
    [FromKeyedServices("distributed")] ICache distributedCache,
    [FromKeyedServices("local")] ICache localCache)
{
    public async Task<Order?> GetAsync(int id, CancellationToken ct = default)
    {
        // Check local cache first, then distributed
        return await localCache.GetAsync<Order>(id.ToString(), ct)
            ?? await distributedCache.GetAsync<Order>(id.ToString(), ct);
    }
}

// Manual resolution
var cache = sp.GetRequiredKeyedService<ICache>("distributed");
net8.0+ only. On earlier TFMs, use factory patterns or a dictionary-based approach.

通过键注册和解析服务,替代命名服务模式。
csharp
// 注册
builder.Services.AddKeyedScoped<ICache, RedisCache>("distributed");
builder.Services.AddKeyedScoped<ICache, MemoryCache>("local");

// 通过特性注入
public sealed class OrderService(
    [FromKeyedServices("distributed")] ICache distributedCache,
    [FromKeyedServices("local")] ICache localCache)
{
    public async Task<Order?> GetAsync(int id, CancellationToken ct = default)
    {
        // 先检查本地缓存,再检查分布式缓存
        return await localCache.GetAsync<Order>(id.ToString(), ct)
            ?? await distributedCache.GetAsync<Order>(id.ToString(), ct);
    }
}

// 手动解析
var cache = sp.GetRequiredKeyedService<ICache>("distributed");
仅支持net8.0+。在更早的TFM中,使用工厂模式或基于字典的方法。

Decoration Pattern

装饰器模式

The built-in container does not natively support decoration. Use one of these approaches:
内置容器本身不支持装饰器。可使用以下方法之一:

Manual Decoration

手动装饰

csharp
builder.Services.AddScoped<SqlOrderRepository>();
builder.Services.AddScoped<IOrderRepository>(sp =>
{
    var inner = sp.GetRequiredService<SqlOrderRepository>();
    var logger = sp.GetRequiredService<ILogger<LoggingOrderRepository>>();
    return new LoggingOrderRepository(inner, logger);
});

public sealed class LoggingOrderRepository(
    IOrderRepository inner,
    ILogger<LoggingOrderRepository> logger) : IOrderRepository
{
    public async Task<Order?> GetByIdAsync(int id, CancellationToken ct = default)
    {
        logger.LogInformation("Getting order {OrderId}", id);
        return await inner.GetByIdAsync(id, ct);
    }
}
csharp
builder.Services.AddScoped<SqlOrderRepository>();
builder.Services.AddScoped<IOrderRepository>(sp =>
{
    var inner = sp.GetRequiredService<SqlOrderRepository>();
    var logger = sp.GetRequiredService<ILogger<LoggingOrderRepository>>();
    return new LoggingOrderRepository(inner, logger);
});

public sealed class LoggingOrderRepository(
    IOrderRepository inner,
    ILogger<LoggingOrderRepository> logger) : IOrderRepository
{
    public async Task<Order?> GetByIdAsync(int id, CancellationToken ct = default)
    {
        logger.LogInformation("获取订单 {OrderId}", id);
        return await inner.GetByIdAsync(id, ct);
    }
}

Scrutor Library (Popular Alternative)

Scrutor库(常用替代方案)

csharp
builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();
builder.Services.Decorate<IOrderRepository, LoggingOrderRepository>();
builder.Services.Decorate<IOrderRepository, CachingOrderRepository>();
// Outer -> CachingOrderRepository -> LoggingOrderRepository -> SqlOrderRepository

csharp
builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();
builder.Services.Decorate<IOrderRepository, LoggingOrderRepository>();
builder.Services.Decorate<IOrderRepository, CachingOrderRepository>();
// 外层 -> CachingOrderRepository -> LoggingOrderRepository -> SqlOrderRepository

Hosted Services and Background Workers

托管服务与后台工作者

BackgroundService
(Preferred)

BackgroundService
(推荐)

csharp
public sealed class QueueProcessorWorker(
    IServiceScopeFactory scopeFactory,
    ILogger<QueueProcessorWorker> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        logger.LogInformation("Queue processor starting");

        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                using var scope = scopeFactory.CreateScope();
                var processor = scope.ServiceProvider
                    .GetRequiredService<IQueueProcessor>();

                await processor.ProcessNextBatchAsync(stoppingToken);
            }
            catch (Exception ex) when (ex is not OperationCanceledException)
            {
                logger.LogError(ex, "Error processing queue batch");
            }

            await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
        }
    }
}

// Registration
builder.Services.AddHostedService<QueueProcessorWorker>();
csharp
public sealed class QueueProcessorWorker(
    IServiceScopeFactory scopeFactory,
    ILogger<QueueProcessorWorker> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        logger.LogInformation("队列处理器启动");

        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                using var scope = scopeFactory.CreateScope();
                var processor = scope.ServiceProvider
                    .GetRequiredService<IQueueProcessor>();

                await processor.ProcessNextBatchAsync(stoppingToken);
            }
            catch (Exception ex) when (ex is not OperationCanceledException)
            {
                logger.LogError(ex, "处理队列批次时出错");
            }

            await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
        }
    }
}

// 注册
builder.Services.AddHostedService<QueueProcessorWorker>();

IHostedService
(Startup/Shutdown Hooks)

IHostedService
(启动/关闭钩子)

csharp
public sealed class DatabaseMigrationService(
    IServiceScopeFactory scopeFactory,
    ILogger<DatabaseMigrationService> logger) : IHostedService
{
    public async Task StartAsync(CancellationToken cancellationToken)
    {
        using var scope = scopeFactory.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        await db.Database.MigrateAsync(cancellationToken);
        logger.LogInformation("Database migration completed");
    }

    public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}

builder.Services.AddHostedService<DatabaseMigrationService>();
csharp
public sealed class DatabaseMigrationService(
    IServiceScopeFactory scopeFactory,
    ILogger<DatabaseMigrationService> logger) : IHostedService
{
    public async Task StartAsync(CancellationToken cancellationToken)
    {
        using var scope = scopeFactory.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        await db.Database.MigrateAsync(cancellationToken);
        logger.LogInformation("数据库迁移完成");
    }

    public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}

builder.Services.AddHostedService<DatabaseMigrationService>();

Key Rules for Hosted Services

托管服务关键规则

  • Always use
    IServiceScopeFactory
    to create scopes -- hosted services are singletons
  • Never inject scoped services directly into hosted service constructors
  • Handle exceptions inside
    ExecuteAsync
    -- unhandled exceptions stop the host (net8.0+)
  • See [skill:dotnet-csharp-async-patterns] for async patterns in background workers

  • 始终使用
    IServiceScopeFactory
    创建作用域 -- 托管服务是单例
  • 切勿直接将作用域服务注入托管服务构造函数
  • ExecuteAsync
    内处理异常 -- 未处理的异常会停止主机(net8.0+)
  • 查看[skill:dotnet-csharp-async-patterns]了解后台工作者的异步模式

Organizing Registrations

组织注册

Group related registrations into extension methods for clean
Program.cs
:
csharp
// ServiceCollectionExtensions.cs
public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddOrderServices(this IServiceCollection services)
    {
        services.AddScoped<IOrderRepository, SqlOrderRepository>();
        services.AddScoped<IOrderService, OrderService>();
        services.AddHostedService<OrderProcessorWorker>();
        return services;
    }

    public static IServiceCollection AddNotificationServices(this IServiceCollection services)
    {
        services.AddScoped<INotifier, EmailNotifier>();
        services.AddScoped<INotifier, SmsNotifier>();
        return services;
    }
}

// Program.cs
builder.Services.AddOrderServices();
builder.Services.AddNotificationServices();

将相关注册分组到扩展方法中,使
Program.cs
更简洁:
csharp
// ServiceCollectionExtensions.cs
public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddOrderServices(this IServiceCollection services)
    {
        services.AddScoped<IOrderRepository, SqlOrderRepository>();
        services.AddScoped<IOrderService, OrderService>();
        services.AddHostedService<OrderProcessorWorker>();
        return services;
    }

    public static IServiceCollection AddNotificationServices(this IServiceCollection services)
    {
        services.AddScoped<INotifier, EmailNotifier>();
        services.AddScoped<INotifier, SmsNotifier>();
        return services;
    }
}

// Program.cs
builder.Services.AddOrderServices();
builder.Services.AddNotificationServices();

Testing with DI

使用DI进行测试

csharp
[Fact]
public async Task OrderService_UsesRepository()
{
    // Arrange -- build a service provider for integration tests
    var services = new ServiceCollection();
    services.AddScoped<IOrderRepository, InMemoryOrderRepository>();
    services.AddScoped<IOrderService, OrderService>();
    services.AddLogging();

    using var provider = services.BuildServiceProvider();
    using var scope = provider.CreateScope();
    var service = scope.ServiceProvider.GetRequiredService<IOrderService>();

    // Act
    var order = await service.GetByIdAsync(1);

    // Assert
    Assert.NotNull(order);
}
For unit tests, prefer direct constructor injection with mocks rather than building a full container.

csharp
[Fact]
public async Task OrderService_UsesRepository()
{
    // 准备 -- 为集成测试构建服务提供器
    var services = new ServiceCollection();
    services.AddScoped<IOrderRepository, InMemoryOrderRepository>();
    services.AddScoped<IOrderService, OrderService>();
    services.AddLogging();

    using var provider = services.BuildServiceProvider();
    using var scope = provider.CreateScope();
    var service = scope.ServiceProvider.GetRequiredService<IOrderService>();

    // 执行
    var order = await service.GetByIdAsync(1);

    // 断言
    Assert.NotNull(order);
}
对于单元测试,优先使用直接构造函数注入模拟对象,而非构建完整容器。

References

参考资料