I'm writing a custom middleware for ASP.NET Core 2.2. According to Microsoft Docs on writing custom middlewares:
Middleware components can resolve their dependencies from dependency injection (DI) through constructor parameters.
UseMiddleware<T>
can also accept additional parameters directly.
This seems all good, but it doesn't say what happens when I mix the two ways, e.g. use DI and pass parameters in UseMiddleware<T>
. For example, I have the following middleware:
public class CustomMiddleware
{
public CustomMiddleware(RequestDelegate next, ILogger<CustomMiddleware> logger, CustomMiddlewareOptions options)
{
...
}
public async Task InvokeAsync(HttpContext context)
{
...
}
where logger
is provided by DI and options
is provided like the following:
app.UseMiddleware<CustomMiddleware>(new CustomMiddlewareOptions());
My own testing with 2.2 seems to show that this works fine, and the order of the parameters in the constructor doesn't matter (I can place DI parameter before or after manually-passed parameter, or even in between two manually-passed parameters). But I'm looking for some assurances that what I'm doing is OK. It would be really great if anyone could point to some docs or source code that supports this sort of usage. Thanks!
My own testing with 2.2 seems to show that this works fine, and the order of the parameters in the constructor doesn't matter (I can place DI parameter before or after manually-passed parameter, or even in between two manually-passed parameters). But I'm looking for some assurances
Yes. After reading the source code, I would say it is fine.
Your CustomMiddleware
is a by-convention Middleware (different from the Factory-based middleware), which is activated by ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs):
var ctorArgs = new object[args.Length + 1];
ctorArgs[0] = next;
Array.Copy(args, 0, ctorArgs, 1, args.Length); //
var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);
Here the the args
(Given Arguments) is the argument array that you pass into the UseMiddleware<CustomMiddleware>(args)
(without the next
).
And there're two stages when preparing the constructor arguments:
args
against the construct parameter types. And set values when the type matches. See source code Here
null
element using ServiceProvider.GetRequiredService<SomeService>()
.See source code here. If the service instance is still null
, then use the default
value.For example, let's say :
public CustomMiddleware(RequestDelegate next, A a, B b, C c, D d, E e){ ... }
And then we pass in two arguments when registering the middleware :
app.UseMiddleware(c, a)
where c
is an instance of C
Type, and a
is an instance of A
Type. So the givenParameters
Array is [next,c, a]
To create an instance of CustomMiddleware
, the compiler needs to know the complete constructor parameter values. DI extension gets this constructor parameter values array (_parameterValues
) within two stages.See :
The stage2 works in a way like below:
b'= sp.GetService(B);
if b' == null :
b' = default value of B
As you can see above, the ActivatorUtilities.CreateInstance(sp,mw,args)
API deals with the order and missing arguments automatically.
As a side note, the by-convention middlewares are activated at startup-time and will always be a singleton. If you want to use scoped service, see this thread
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With