【问题标题】:How to do DI in asp.net core middleware?asp.net核心中间件怎么做DI?
【发布时间】: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)

【问题讨论】:

    标签: c# asp.net-core dependency-injection asp.net-core-2.0 asp.net-core-middleware


    【解决方案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>();
    
    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=&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 方法的那些服务是根据请求请求的。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-04-30
    • 1970-01-01
    • 2017-06-09
    • 1970-01-01
    • 2019-02-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多