asp.net核心中间件怎么做DI?

Posted

技术标签:

【中文标题】asp.net核心中间件怎么做DI?【英文标题】:How to do DI in asp.net core middleware? 【发布时间】:2019-02-11 17:08:40 【问题描述】:

我正在尝试将依赖项注入到我的中间件构造函数中,如下所示

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(Type serviceType, IServiceScope scope, IServiceScope rootScope)

【问题讨论】:

【参考方案1】:

UserManager&lt;ApplicationUser&gt;(默认情况下)注册为 scoped 依赖项,而您的 CreateCompanyMiddleware 中间件是在应用程序启动时构建的(实际上使其成为 singleton)。这是一个相当标准的错误,表示您不能将 scoped 依赖项放入 singleton 类中。

在这种情况下修复很简单 - 您可以将 UserManager&lt;ApplicationUser&gt; 注入到您的 Invoke 方法中:

public async Task Invoke(HttpContext context, UserManager<ApplicationUser> userManager)

    await _next.Invoke(context);

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

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

【讨论】:

对我有用的好简单的解决方案。请注意,如果您忘记在 startup.cs 中添加 DI 类,则中间件中的错误不会总是出现在屏幕上,并且断点也不会被命中。看起来代码正在运行,但可能不是。【参考方案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>();
    那么为什么会这样呢?

使用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=&gt; 中的代码是按请求执行的。所以每次有传入请求时,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 方法的那些服务是根据请求请求的。

【讨论】:

这里描述了工厂方法:docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/…

以上是关于asp.net核心中间件怎么做DI?的主要内容,如果未能解决你的问题,请参考以下文章

asp.net核心中间件中Map和MapWhen分支的区别?

csharp RequestCounter自定义中间件asp.net核心

处理asp.net核心中的异常?

第1单元-管道和中间件简介

ASP.NET Core Web API - 如何在中间件管道中隐藏 DbContext 事务?

asp.net怎么实现前后端分离 asp.net如何实现前后端分离