asp.net核心中间件如何做DI?
How to do DI in asp.net core middleware?
我正在尝试将依赖项注入我的中间件构造函数,如下所示
public class CreateCompanyMiddleware
{
private readonly RequestDelegate _next;
private readonly UserManager<ApplicationUser> _userManager;
public CreateCompanyMiddleware(RequestDelegate next
, UserManager<ApplicationUser> userManager
)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
await _next.Invoke(context);
}
}
我的 Startup.cs 文件看起来像
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseMySql(Configuration.GetConnectionString("IdentityConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
...
app.UseMiddleware<CreateCompanyMiddleware>();
...
但是我收到了这个错误
An error occurred while starting the application.
InvalidOperationException: Cannot resolve scoped service 'Microsoft.AspNetCore.Identity.UserManager`1[Common.Models.ApplicationUser]' from root provider.
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.ValidateResolution(Type serviceType, IServiceScope scope, IServiceScope rootScope)
UserManager<ApplicationUser>
(默认情况下)注册为 scoped 依赖项,而您的 CreateCompanyMiddleware
中间件是在应用程序启动时构建的(有效地使其成为 单例)。这是一个相当标准的错误,表示您不能将 scoped 依赖项带入 singleton class.
在这种情况下修复很简单 - 您可以将 UserManager<ApplicationUser>
注入到您的 Invoke
方法中:
public async Task Invoke(HttpContext context, UserManager<ApplicationUser> userManager)
{
await _next.Invoke(context);
}
这在 ASP.NET Core Middleware: Per-request middleware dependencies 中有记录:
Because middleware is constructed at app startup, not per-request, scoped lifetime services used by middleware constructors aren't shared with other dependency-injected types during each request. If you must share a scoped service between your middleware and other types, add these services to the Invoke
method's signature. The Invoke
method can accept additional parameters that are populated by DI:
另一种方法是通过 IMiddleware
接口创建一个中间件并将其注册为服务
例如,中间件
public class CreateCompanyMiddlewareByInterface : IMiddleware
{
private readonly UserManager<ApplicationUser> _userManager;
public CreateCompanyMiddlewareByInterface(UserManager<ApplicationUser> userManager )
{
this._userManager = userManager;
}
public Task InvokeAsync(HttpContext context, RequestDelegate next)
{
return next(context);
}
}
和服务注册:
services.AddScoped<CreateCompanyMiddlewareByInterface>();
- 为什么会这样?
使用IMiddleware
的中间件由UseMiddlewareInterface(appBuilder, middlewareType type)
构建:
private static IApplicationBuilder UseMiddlewareInterface(IApplicationBuilder app, Type middlewareType)
{
return app.Use(next =>
{
return async context =>
{
var middlewareFactory = (IMiddlewareFactory)context.RequestServices.GetService(typeof(IMiddlewareFactory));
if (middlewareFactory == null) { /* throw ... */ }
var middleware = middlewareFactory.Create(middlewareType);
if (middleware == null) { /* throw ... */ }
try{
await middleware.InvokeAsync(context, next);
}
finally{
middlewareFactory.Release(middleware);
}
};
});
}
此处 context=>{}
中的代码是按请求执行的。所以每次有传入请求时,var middleware = middlewareFactory.Create(middlewareType);
将被执行,然后从 ServiceProvider
请求 middlewareType
的中间件(已注册为服务)。
至于约定俗成的中间件,没有工厂创建它们。
这些实例都是 ActivatorUtilities.CreateInstance()
在启动时创建的。以及任何 Invoke
约定中间件的方法,例如
Task Invoke(HttpContext context,UserManager<ApplicationUser> userManage, ILoggerFactory loggeryFactory , ... )
将被编译成如下函数:
Task Invoke(Middleware instance, HttpContext httpContext, IServiceprovider provider)
{
var useManager /* = get service from service provider */ ;
var log = /* = get service from service provider */ ;
// ...
return instance.Invoke(httpContext,userManager,log, ...);
}
如您所见,此处实例是在启动时创建的,并且每个请求都会请求 Invoke
方法的那些服务。
我正在尝试将依赖项注入我的中间件构造函数,如下所示
public class CreateCompanyMiddleware
{
private readonly RequestDelegate _next;
private readonly UserManager<ApplicationUser> _userManager;
public CreateCompanyMiddleware(RequestDelegate next
, UserManager<ApplicationUser> userManager
)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
await _next.Invoke(context);
}
}
我的 Startup.cs 文件看起来像
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseMySql(Configuration.GetConnectionString("IdentityConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
...
app.UseMiddleware<CreateCompanyMiddleware>();
...
但是我收到了这个错误
An error occurred while starting the application. InvalidOperationException: Cannot resolve scoped service 'Microsoft.AspNetCore.Identity.UserManager`1[Common.Models.ApplicationUser]' from root provider. Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.ValidateResolution(Type serviceType, IServiceScope scope, IServiceScope rootScope)
UserManager<ApplicationUser>
(默认情况下)注册为 scoped 依赖项,而您的 CreateCompanyMiddleware
中间件是在应用程序启动时构建的(有效地使其成为 单例)。这是一个相当标准的错误,表示您不能将 scoped 依赖项带入 singleton class.
在这种情况下修复很简单 - 您可以将 UserManager<ApplicationUser>
注入到您的 Invoke
方法中:
public async Task Invoke(HttpContext context, UserManager<ApplicationUser> userManager)
{
await _next.Invoke(context);
}
这在 ASP.NET Core Middleware: Per-request middleware dependencies 中有记录:
Because middleware is constructed at app startup, not per-request, scoped lifetime services used by middleware constructors aren't shared with other dependency-injected types during each request. If you must share a scoped service between your middleware and other types, add these services to the
Invoke
method's signature. TheInvoke
method can accept additional parameters that are populated by DI:
另一种方法是通过 IMiddleware
接口创建一个中间件并将其注册为服务
例如,中间件
public class CreateCompanyMiddlewareByInterface : IMiddleware
{
private readonly UserManager<ApplicationUser> _userManager;
public CreateCompanyMiddlewareByInterface(UserManager<ApplicationUser> userManager )
{
this._userManager = userManager;
}
public Task InvokeAsync(HttpContext context, RequestDelegate next)
{
return next(context);
}
}
和服务注册:
services.AddScoped<CreateCompanyMiddlewareByInterface>();
- 为什么会这样?
使用IMiddleware
的中间件由UseMiddlewareInterface(appBuilder, middlewareType type)
构建:
private static IApplicationBuilder UseMiddlewareInterface(IApplicationBuilder app, Type middlewareType)
{
return app.Use(next =>
{
return async context =>
{
var middlewareFactory = (IMiddlewareFactory)context.RequestServices.GetService(typeof(IMiddlewareFactory));
if (middlewareFactory == null) { /* throw ... */ }
var middleware = middlewareFactory.Create(middlewareType);
if (middleware == null) { /* throw ... */ }
try{
await middleware.InvokeAsync(context, next);
}
finally{
middlewareFactory.Release(middleware);
}
};
});
}
此处 context=>{}
中的代码是按请求执行的。所以每次有传入请求时,var middleware = middlewareFactory.Create(middlewareType);
将被执行,然后从 ServiceProvider
请求 middlewareType
的中间件(已注册为服务)。
至于约定俗成的中间件,没有工厂创建它们。
这些实例都是 ActivatorUtilities.CreateInstance()
在启动时创建的。以及任何 Invoke
约定中间件的方法,例如
Task Invoke(HttpContext context,UserManager<ApplicationUser> userManage, ILoggerFactory loggeryFactory , ... )
将被编译成如下函数:
Task Invoke(Middleware instance, HttpContext httpContext, IServiceprovider provider)
{
var useManager /* = get service from service provider */ ;
var log = /* = get service from service provider */ ;
// ...
return instance.Invoke(httpContext,userManager,log, ...);
}
如您所见,此处实例是在启动时创建的,并且每个请求都会请求 Invoke
方法的那些服务。