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>();
  1. 为什么会这样?

使用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 方法的那些服务。