如何在asp.net核心中间件中做DI?

时间:2018-09-06 12:21:17

标签: c# asp.net-core asp.net-core-2.0 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>();

    ...

但是我收到此错误

  

启动应用程序时发生错误。   InvalidOperationException:无法从根提供程序解析作用域服务'Microsoft.AspNetCore.Identity.UserManager`1 [Common.Models.ApplicationUser]'。   Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.ValidateResolution(类型serviceType,IServiceScope范围,IServiceScope rootScope)

2 个答案:

答案 0 :(得分:24)

UserManager<ApplicationUser>(默认情况下)注册为作用域依赖项,而您的CreateCompanyMiddleware中间件是在应用启动时构建的(有效地使其成为 singleton )。这是一个相当标准的错误,它表明您不能将 scoped 依赖项放入 singleton 类中。

在这种情况下,修复很简单-您可以将UserManager<ApplicationUser>注入到Invoke方法中:

public async Task Invoke(HttpContext context, UserManager<ApplicationUser> userManager)
{
    await _next.Invoke(context);
}

这在ASP.NET Core Middleware: Per-request dependencies中有记录:

  

因为中间件是在应用启动时构建的,而不是按请求构建的,所以中间件构造函数使用的作用域生存期服务不会在每个请求期间与其他依赖项注入类型共享。如果必须在中间件和其他类型之间共享作用域服务,请将这些服务添加到Invoke方法的签名中。 Invoke方法可以接受DI填充的其他参数。

答案 1 :(得分:5)

  1. @Kirk LarKin的回答很棒。

  2. 另一种方法是通过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);,然后从middlewareType请求ServiceProvider的中间件(已注册为服务)。

对于约定俗成的中间件,没有工厂创建它们。

这些实例全部由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方法的那些服务。