How to do DI in asp.net core middleware?
H

2

105

I am trying to inject dependency into my middleware constructor as follows

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);
    }
}

My Startup.cs file looks like

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseMySql(Configuration.GetConnectionString("IdentityConnection")));

    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();
    ...

    app.UseMiddleware<CreateCompanyMiddleware>();

    ...

But I am getting this error

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)

Howlyn answered 6/9, 2018 at 12:21 Comment(0)
B
226

UserManager<ApplicationUser> is (by default) registered as a scoped dependency, whereas your CreateCompanyMiddleware middleware is constructed at app startup (effectively making it a singleton). This is a fairly standard error saying that you can't take a scoped dependency into a singleton class.

The fix is simple in this case - you can inject the UserManager<ApplicationUser> into your Invoke method:

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

This is documented in 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:

Benzoic answered 6/9, 2018 at 12:24 Comment(1)
Nice simple solution that worked for me. Note, if you forget to add the DI classes in the startup.cs then errors in the middleware won't always appear on screen and break points won't get hit. It can appear that the code is working, but it's probably not.Jacky
C
32

Another way to do that is to create a middleware by IMiddleware interface and register it as a service

For example , the middleware

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);
    }
} 

and service registeration :

services.AddScoped<CreateCompanyMiddlewareByInterface>();
  1. So why it happens ?

The middlewares using IMiddleware are built by 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);
            }
        };
    });
}

here the codes inside the context=>{} are executed per-request . So every time there's an incoming request , the var middleware = middlewareFactory.Create(middlewareType); will be executed and then ask for a middleware of middlewareType ( which is already registered as a service ) from the ServiceProvider .

As for by-convention middlewares , there's no factory creating them .

Those instances are all created by ActivatorUtilities.CreateInstance() at startup time . And any Invoke method of by-convention middlewares , such as

Task Invoke(HttpContext context,UserManager<ApplicationUser> userManage, ILoggerFactory loggeryFactory , ... )

will be compiled into a function like below :

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, ...);
}

As you see , here the instance is created at startup time , and those services of Invoke method are requested per request .

Crossbeam answered 7/9, 2018 at 1:32 Comment(1)
The Factory approach is described here: learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/…Lineup

© 2022 - 2024 — McMap. All rights reserved.