ASP.NET Core 依赖注入深入
大约 9 分钟约 2699 字
ASP.NET Core 依赖注入深入
简介
依赖注入(Dependency Injection, DI)是 ASP.NET Core 的基础能力之一,几乎所有框架组件都建立在它之上:控制器、日志、配置、DbContext、HttpClient、认证授权、中间件都依赖同一套容器体系。真正理解 DI,不只是会写 AddScoped,而是要理解生命周期、依赖边界、对象图构建方式和常见反模式。
特点
实现
基础注册与注入方式
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddTransient<IEmailSender, SmtpEmailSender>();
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddSingleton<ISystemClock, SystemClock>();
var app = builder.Build();public interface IOrderService
{
Task<long> CreateAsync(CreateOrderRequest request, CancellationToken cancellationToken);
}
public class OrderService : IOrderService
{
private readonly IOrderRepository _repository;
private readonly IEmailSender _emailSender;
private readonly ILogger<OrderService> _logger;
public OrderService(
IOrderRepository repository,
IEmailSender emailSender,
ILogger<OrderService> logger)
{
_repository = repository;
_emailSender = emailSender;
_logger = logger;
}
public async Task<long> CreateAsync(CreateOrderRequest request, CancellationToken cancellationToken)
{
var orderId = await _repository.InsertAsync(request, cancellationToken);
await _emailSender.SendAsync("ops@example.com", "新订单", $"订单 {orderId} 已创建");
_logger.LogInformation("Order created: {OrderId}", orderId);
return orderId;
}
}[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
private readonly IOrderService _orderService;
public OrdersController(IOrderService orderService)
{
_orderService = orderService;
}
[HttpPost]
public async Task<IActionResult> Create(CreateOrderRequest request, CancellationToken cancellationToken)
{
var orderId = await _orderService.CreateAsync(request, cancellationToken);
return Ok(new { orderId });
}
}三种生命周期与典型场景
public interface IOperationId
{
Guid Value { get; }
}
public sealed class TransientOperation : IOperationId
{
public Guid Value { get; } = Guid.NewGuid();
}
public sealed class ScopedOperation : IOperationId
{
public Guid Value { get; } = Guid.NewGuid();
}
public sealed class SingletonOperation : IOperationId
{
public Guid Value { get; } = Guid.NewGuid();
}builder.Services.AddTransient<TransientOperation>();
builder.Services.AddScoped<ScopedOperation>();
builder.Services.AddSingleton<SingletonOperation>();生命周期选择建议:
- Transient:轻量、无状态、创建成本低的服务
- Scoped:与单次请求绑定的服务,如 DbContext、业务事务单元
- Singleton:线程安全、可长期复用、无请求上下文依赖的服务[ApiController]
[Route("api/di-demo")]
public class DiDemoController : ControllerBase
{
[HttpGet]
public object Get(
[FromServices] TransientOperation transient1,
[FromServices] TransientOperation transient2,
[FromServices] ScopedOperation scoped1,
[FromServices] ScopedOperation scoped2,
[FromServices] SingletonOperation singleton1,
[FromServices] SingletonOperation singleton2)
{
return new
{
transient1 = transient1.Value,
transient2 = transient2.Value,
scoped1 = scoped1.Value,
scoped2 = scoped2.Value,
singleton1 = singleton1.Value,
singleton2 = singleton2.Value
};
}
}工厂注册、开放泛型与多实现
builder.Services.AddSingleton<ICacheService>(sp =>
{
var config = sp.GetRequiredService<IConfiguration>();
var connectionString = config.GetConnectionString("Redis")!;
return new RedisCacheService(connectionString);
});public interface IRepository<T>
{
Task<T?> GetByIdAsync(long id, CancellationToken cancellationToken);
}
public class Repository<T> : IRepository<T> where T : class
{
private readonly AppDbContext _dbContext;
public Repository(AppDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<T?> GetByIdAsync(long id, CancellationToken cancellationToken)
{
return await _dbContext.Set<T>().FindAsync(new object[] { id }, cancellationToken);
}
}
builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>));public interface INotificationSender
{
string Channel { get; }
Task SendAsync(string message);
}
public class EmailSender : INotificationSender
{
public string Channel => "email";
public Task SendAsync(string message) => Task.CompletedTask;
}
public class SmsSender : INotificationSender
{
public string Channel => "sms";
public Task SendAsync(string message) => Task.CompletedTask;
}
builder.Services.AddSingleton<INotificationSender, EmailSender>();
builder.Services.AddSingleton<INotificationSender, SmsSender>();public class NotificationManager
{
private readonly IEnumerable<INotificationSender> _senders;
public NotificationManager(IEnumerable<INotificationSender> senders)
{
_senders = senders;
}
public Task NotifyAllAsync(string message)
{
return Task.WhenAll(_senders.Select(sender => sender.SendAsync(message)));
}
}常见反模式与边界控制
// 反例:Singleton 依赖 Scoped 服务
builder.Services.AddScoped<AppDbContext>();
builder.Services.AddSingleton<ReportService>();
public class ReportService
{
private readonly AppDbContext _dbContext; // 问题:生命周期不兼容
public ReportService(AppDbContext dbContext)
{
_dbContext = dbContext;
}
}// 正确方式1:把服务改成 Scoped
builder.Services.AddScoped<ReportService>();// 正确方式2:如果 Singleton 必须存在,通过 IServiceScopeFactory 按需创建作用域
public class BackgroundReportScheduler
{
private readonly IServiceScopeFactory _scopeFactory;
public BackgroundReportScheduler(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public async Task RunAsync(CancellationToken cancellationToken)
{
using var scope = _scopeFactory.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var count = await dbContext.Orders.CountAsync(cancellationToken);
Console.WriteLine(count);
}
}// 反例:滥用 IServiceProvider 变成 Service Locator
public class BadService
{
private readonly IServiceProvider _serviceProvider;
public BadService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task RunAsync()
{
var userService = _serviceProvider.GetRequiredService<IUserService>();
await userService.GetAsync(1);
}
}
// 问题:依赖关系不透明,测试和维护都变差模块化注册与扩展方法
按层组织注册
// 按层组织 DI 注册,避免 Program.cs 膨胀
public static class ServiceCollectionExtensions
{
// 基础设施层
public static IServiceCollection AddInfrastructure(
this IServiceCollection services, IConfiguration configuration)
{
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(configuration.GetConnectionString("Default")));
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = configuration.GetConnectionString("Redis");
});
services.AddSingleton<IConnectionMultiplexer>(_ =>
ConnectionMultiplexer.Connect(configuration.GetConnectionString("Redis")!));
services.AddHttpClient();
return services;
}
// 应用层
public static IServiceCollection AddApplication(
this IServiceCollection services)
{
// 自动扫描程序集注册服务
var assembly = Assembly.GetExecutingAssembly();
var serviceTypes = assembly.GetTypes()
.Where(t => t.IsClass && !t.IsAbstract && t.Name.EndsWith("Service"));
foreach (var type in serviceTypes)
{
var interfaceType = type.GetInterfaces()
.FirstOrDefault(i => i.Name == $"I{type.Name}");
if (interfaceType != null)
{
var lifetime = GetLifetime(type);
services.Add(new ServiceDescriptor(interfaceType, type, lifetime));
}
}
return services;
}
private static ServiceLifetime GetLifetime(Type type)
{
if (type.GetInterfaces().Contains(typeof(ISingletonService)))
return ServiceLifetime.Singleton;
if (type.GetInterfaces().Contains(typeof(IScopedService)))
return ServiceLifetime.Scoped;
return ServiceLifetime.Transient;
}
}
// Program.cs — 清洁的注册
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddInfrastructure(builder.Configuration);
builder.Services.AddApplication();
builder.Services.AddControllers();Autofac 集成(模块化 DI)
// 安装:Autofac.Extensions.DependencyInjection
builder.Host.UseAutofac();
// 模块化注册
public class InfrastructureModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<AppDbContext>()
.AsSelf()
.InstancePerLifetimeScope();
builder.RegisterType<RedisCacheService>()
.As<ICacheService>()
.SingleInstance();
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.Where(t => t.Name.EndsWith("Repository"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
}
}
public class ApplicationModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.Where(t => t.Name.EndsWith("Service"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
// 属性注入(Autofac 特有)
builder.RegisterType<OrderController>()
.PropertiesAutowired();
}
}
// 注册模块
builder.Host.ConfigureContainer<ContainerBuilder>(containerBuilder =>
{
containerBuilder.RegisterModule<InfrastructureModule>();
containerBuilder.RegisterModule<ApplicationModule>();
});DI 与测试
单元测试中的 Mock
// 使用 Moq 替换依赖进行单元测试
public class OrderServiceTests
{
[Fact]
public async Task CreateOrder_ShouldDeductStock()
{
// Arrange
var mockRepo = new Mock<IOrderRepository>();
var mockCache = new Mock<ICacheService>();
var mockLogger = new Mock<ILogger<OrderService>>();
mockRepo.Setup(r => r.AddAsync(It.IsAny<Order>()))
.ReturnsAsync(1);
var service = new OrderService(
mockRepo.Object,
mockCache.Object,
mockLogger.Object);
// Act
var result = await service.CreateOrderAsync(new CreateOrderDto
{
UserId = 1,
ProductId = 100,
Quantity = 2
});
// Assert
Assert.True(result.Success);
mockRepo.Verify(r => r.AddAsync(It.IsAny<Order>()), Times.Once);
}
[Fact]
public async Task CreateOrder_WhenStockInsufficient_ShouldFail()
{
var mockRepo = new Mock<IOrderRepository>();
var mockStockService = new Mock<IStockService>();
mockStockService
.Setup(s => s.CheckStockAsync(100, 2))
.ReturnsAsync(false);
var service = new OrderService(mockRepo.Object, mockStockService.Object);
await Assert.ThrowsAsync<DomainException>(
() => service.CreateOrderAsync(new CreateOrderDto
{
ProductId = 100, Quantity = 2
}));
}
}
// 集成测试中使用真实的 DI 容器
public class IntegrationTestBase : IClassFixture<WebApplicationFactory<Program>>
{
protected readonly WebApplicationFactory<Program> _factory;
public IntegrationTestBase(WebApplicationFactory<Program> factory)
{
_factory = factory.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
// 替换数据库为内存数据库
services.RemoveAll(typeof(DbContextOptions<AppDbContext>));
services.AddDbContext<AppDbContext>(options =>
options.UseInMemoryDatabase("TestDb"));
// 替换外部服务为 Mock
services.RemoveAll(typeof(IPaymentGateway));
services.AddTransient<IPaymentGateway, MockPaymentGateway>();
});
});
}
}常见 DI 陷阱与解决
Captive Dependency 问题
// ❌ 错误:Singleton 捕获 Scoped 服务
public class SingletonService
{
private readonly IScopedService _scoped; // 错误!
public SingletonService(IScopedService scoped) => _scoped = scoped;
// _scoped 在 Singleton 创建时被注入,之后一直使用同一个实例
// 如果 IScopedService 依赖 DbContext,DbContext 已被释放
}
// ✅ 解决方案 1:使用 IServiceProvider + CreateScope
public class SingletonService
{
private readonly IServiceProvider _sp;
public SingletonService(IServiceProvider sp) => _sp = sp;
public void DoWork()
{
using var scope = _sp.CreateScope();
var scoped = scope.ServiceProvider.GetRequiredService<IScopedService>();
scoped.Execute(); // 安全:每次获取新的 Scoped 实例
}
}
// ✅ 解决方案 2:注入 IServiceScopeFactory
public class BackgroundTaskService : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
public BackgroundTaskService(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using var scope = _scopeFactory.CreateScope();
var service = scope.ServiceProvider.GetRequiredService<IOrderService>();
await service.ProcessPendingOrdersAsync();
await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
}
}
}
// ✅ 解决方案 3:改为 Scoped 生命周期
// 如果服务确实需要在每次请求中创建,就注册为 Scoped
builder.Services.AddScoped<SingletonService>(); // 改为 ScopedService Locator 反模式
// ❌ Service Locator:隐藏依赖关系
public class BadService
{
private readonly IServiceProvider _sp;
public BadService(IServiceProvider sp) => _sp = sp;
public void DoWork()
{
// 运行时才知道依赖了什么,难以测试和维护
var repo = _sp.GetRequiredService<IRepository>();
var cache = _sp.GetRequiredService<ICacheService>();
var logger = _sp.GetRequiredService<ILogger<BadService>>();
}
}
// ✅ 构造函数注入:依赖关系明确
public class GoodService
{
private readonly IRepository _repo;
private readonly ICacheService _cache;
private readonly ILogger<GoodService> _logger;
public GoodService(
IRepository repo,
ICacheService cache,
ILogger<GoodService> logger)
{
_repo = repo;
_cache = cache;
_logger = logger;
}
}优点
缺点
缺点
总结
ASP.NET Core DI 的重点不是“会注册服务”,而是能否清楚划分对象生命周期、依赖边界和职责分层。大多数业务系统只要坚持构造函数注入、合理选择生命周期、避免 Service Locator 和过度抽象,就已经能把 DI 用得足够稳。
关键知识点
- Scoped 生命周期通常最适合业务服务和 DbContext。
- Singleton 不应直接依赖 Scoped 服务。
- 构造函数注入是默认推荐方式,依赖最透明。
- 多实现解析和开放泛型是常见中大型项目需求。
项目落地视角
- Repository、Service、Domain Service 通常都通过 DI 统一注册。
- BackgroundService 里访问 Scoped 服务时必须显式建 scope。
- HttpClient、缓存、配置、日志都建议走框架标准注册方式。
- 大型项目可以封装扩展方法,让基础设施注册集中管理。
常见误区
- 所有服务都注册成 Singleton,希望“更省资源”。
- 业务类里到处注入
IServiceProvider动态解析依赖。 - 为了“面向接口”而给每个类都强行加接口。
- 把 DI 当成魔法,不理解对象什么时候创建、什么时候销毁。
进阶路线
- 学习 Options、HttpClientFactory、MediatR 等典型 DI 结合场景。
- 研究 Scrutor、Autofac 等扩展容器能力与装配方式。
- 建立模块化注册规范,如
AddInfrastructure()、AddApplication()。 - 深入理解 ASP.NET Core 请求管道和对象作用域关系。
适用场景
- ASP.NET Core Web API / MVC 项目。
- 后台任务、消息消费、领域服务组织。
- 中大型项目的模块化设计与可测试性建设。
- 需要统一管理配置、缓存、日志和数据访问的系统。
落地建议
- 以“职责边界”和“生命周期”作为注册设计的第一原则。
- 优先使用构造函数注入,不要滥用
IServiceProvider。 - 对基础设施注册做扩展方法封装,减少 Program.cs 膨胀。
- 在代码评审中重点关注生命周期不匹配和过度抽象问题。
排错清单
- 报
Cannot consume scoped service from singleton时先检查生命周期链路。 - 服务解析失败时先检查是否注册、泛型是否匹配、命名空间是否引用正确。
- 同一请求中对象状态异常时,检查是否误用了 Transient。
- 后台任务中拿不到 DbContext 时,检查是否创建了作用域。
复盘问题
- 当前系统里哪些服务真的应该是 Singleton?
- 哪些依赖其实不该被注入,而是应该拆职责?
- 你的 DI 使用,是让系统更清晰,还是只是增加了抽象层?
- 如果今天要给某个服务补单元测试,当前依赖设计是否友好?
