ASP.NET Core DI 必须严格匹配服务生命周期:DbContext 须用 AddScoped,错配 Singleton 会导致并发异常;构造函数需 public 且参数类型全注册;注册顺序应基础服务→MVC扩展→依赖MVC的组件;后台任务中 Scoped 服务需手动创建 IServiceScope。

注册服务时必须匹配生命周期,否则会出诡异的 NullReferenceException
ASP.NET Core 默认支持三种生命周期:AddSingleton、AddScoped、AddTransient。错配最常见于把依赖数据库上下文 DbContext 注册为 AddSingleton——它不是线程安全的,多请求并发时会抛 InvalidOperationException: A second operation started on this context before a previous operation completed。
正确做法是:
-
DbContext必须用AddScoped(每个 HTTP 请求一个实例) - 仓储接口(如
IUserRepository)通常也用AddScoped,除非明确需要跨请求共享状态 - 工具类(如
IMailService无状态实现)可用AddTransient或AddSingleton,但注意单例中若持有HttpClient必须用IHttpClientFactory包装,不能直接 new
构造函数注入失败?检查是否漏了 public 修饰符或参数类型不匹配
DI 容器只解析 public 构造函数,且要求所有参数类型都已在容器中注册。常见错误包括:
- 控制器或服务类的构造函数写成
private MyController(...)→ 报InvalidOperationException: No constructor for type ... was found - 注入了未注册的接口,比如写了
public MyService(IConfigHelper helper),但没调用services.AddSingleton() - 参数类型是具体类而非接口(如
MyService(MyLogger logger)),而注册的是ILogger接口 → 容器无法匹配
调试建议:启动时加 ValidateOnBuild = true(.NET 6+):
var builder = WebApplication.CreateBuilder(new WebApplicationOptions { ValidateOnBuild = true });
,能提前暴露注册缺失问题。
在中间件或静态方法里要用 IServiceProvider 获取服务,但别滥用
中间件的 InvokeAsync 方法不支持构造函数注入,必须从 HttpContext.RequestServices 拿服务:
public async Task InvokeAsync(HttpContext context, ILogger<mymiddleware> logger)
{
var repo = context.RequestServices.GetRequiredService<iuserrepository>();
// ...
}</iuserrepository></mymiddleware>
但要注意:
- 不要在静态方法或属性初始化器里缓存
IServiceProvider,它可能已被释放(尤其 Scoped 服务) - 避免在循环里反复调用
GetRequiredService,性能差;应提取到方法开头一次性获取 -
GetService返回 null 不报错,容易埋坑;优先用() GetRequiredService让异常早点暴露()
第三方库注册顺序影响行为,特别是 AddControllers 和 AddRazorPages
像 AddControllers、AddRazorPages、AddEndpointsApiExplorer 这些扩展方法内部会注册自己的服务(如 IActionDescriptorCollectionProvider)。如果把自定义注册写在它们之后,可能被覆盖或失效。
标准顺序应是:
- 先注册基础服务(DB、日志、配置等)
- 再调用
AddControllers/AddRazorPages/AddEndpointsApiExplorer - 最后注册需要依赖 MVC 内部服务的组件(如自定义
IAuthorizationHandler)
一个典型反例:把 services.AddAuthentication() 放在 AddControllers 之后,会导致策略(Policy)找不到 IAuthorizationHandler 实现。
最常被忽略的是 Scoped 服务在后台任务(如 IHostedService)中的使用——必须手动创建 IServiceScope,否则拿到的是 Root Scope,Scoped 服务会变成“伪单例”。这个点一旦出错,数据隔离就全乱了。










