How to do DI in asp.net core middleware?
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)
Solution 1:
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. TheInvoke
method can accept additional parameters that are populated by DI:
Solution 2:
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>();
- 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 .