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, eg 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
But I'm looking for some assurances that what I'm doing is OK.
Well, this is opinion-based. While everything is working in your case it is okay, I believe. But I would prefere using options pattern introduced in ASP.NET Core
.
public class CustomMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<CustomMiddleware> _logger;
private readonly CustomMiddlewareOptions _options;
public CustomMiddleware(RequestDelegate next, ILogger<CustomMiddleware> logger, IOptions<CustomMiddlewareOptions> options)
{
_next = next;
_logger = logger;
_options = options.Value;
}
//...
}
For this one you will need to configure CustomMiddlewareOptions
in Startup
services.Configure<CustomMiddlewareOptions>(options =>
{
options.Id = 1;
options.Name = "options";
//set other properties
});
In this case you should add middleware without parameters
app.UseMiddleware<CustomMiddleware>();
Note
It's also worth to take a look at RequestLocalizationMiddleware
public RequestLocalizationMiddleware(RequestDelegate next, IOptions<RequestLocalizationOptions> options)
and how framework offers you to use the middleware in ApplicationBuilderExtensions
public static IApplicationBuilder UseRequestLocalization(this IApplicationBuilder app)
{
//omitted
return app.UseMiddleware<RequestLocalizationMiddleware>();
}
or
public static IApplicationBuilder UseRequestLocalization(this IApplicationBuilder app, RequestLocalizationOptions options)
{
//omitted
return app.UseMiddleware<RequestLocalizationMiddleware>(Options.Create(options));
}
As you can see ASP.NET Core
developers also prefer to stick with IOptions
in middleware constructor and while manually specifying additional parameter they just wrap them into Options
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.