Reputation:
I saw some code lilke this:
public class CustomMiddleware {
private RequestDelegate next;
public CustomMiddleware (RequestDelegate nextDelegate) {
next = nextDelegate;
}
public async Task Invoke(HttpContext context, IResponseFormatter formatter) {
...
await next(context);
}
}
and IResponseFormatter service is registered as:
public void ConfigureServices(IServiceCollection services) {
services.AddTransient<IResponseFormatter, GuidService>();
}
I know how DI works, but my understanding how middleware works is, next
(RequestDelegate
) represents the next middleware's Invoke
method, so in CustomMiddleware, even the second argument is resolved by DI, but the definition of RequestDelegate
is
public delegate Task RequestDelegate(HttpContext context);
how does the previous middleware before CustomMiddleware
knows that CustomMiddleware
's Invoke
method has changed by having an extra argument? It cannot know in advance, therefore the previous middleware's next
RequestDelegate
does't match the signature of CustomMiddleware
's Invoke
method?
Upvotes: 4
Views: 5652
Reputation: 7226
How does the previous middleware before CustomMiddleware know that CustomMiddleware's Invoke method has changed by having an extra argument?
Because of convention (and reflection).
Custom Middleware doesn't inherit any interfaces or base classes, so the only way for the runtime to know how to use the middleware is by convention:
The middleware class must include:
- A public constructor with a parameter of type RequestDelegate.
- A method named Invoke or InvokeAsync. This method must:
- Return a Task.
- Accept a first parameter of type HttpContext.
With this knowledge, safely run the middleware: you can use reflection to get the dependencies of Invoke
and execute it knowing the return type is Task
. For example:
MethodInfo method = middleware.GetType().GetMethod("Invoke");
ParameterInfo[] parameters = method.GetParameters();
// ... instatiate the dependencies using something like ServiceProvider
// and bundle them up into an object[].
method.Invoke(middleware/**, injected dependencies go here as an object[] **/);
It cannot know in advance, therefore the previous middleware's next RequestDelegate does't match the signature of CustomMiddleware's Invoke method?
RequestDelegate
does not match the signature of CustomMiddleware.Invoke
.
RequestDelegate
does not need to match the signature of CustomMiddleware.Invoke
.
All the RequestDelegate
(next
) cares about is passing the same HttpContext
instance down the middleware chain, and it will always have that because of the (previously mentioned) convention (emphasis added):
- A method named Invoke or InvokeAsync. This method must:
- Return a Task.
- Accept a first parameter of type HttpContext.
Finally, next
is not directly calling CustomMiddleware.Invoke
. In between middlewares, DI has the opportunity to inject services needed for the next middleware.
Upvotes: 2
Reputation: 247471
Internally, framework code uses reflection to determine arguments for both constructor and Invoke member of the middleware by the following convention
The middleware class must include:
- A public constructor with a parameter of type RequestDelegate.
- A public method named Invoke or InvokeAsync. This method must:
- Return a Task.
- Accept a first parameter of type HttpContext.
Additional parameters for the constructor and Invoke/InvokeAsync are populated by dependency injection (DI).
Reference Write custom ASP.NET Core middleware
As demonstrated from the Source code
/// <summary>
/// Adds a middleware type to the application's request pipeline.
/// </summary>
/// <param name="app">The <see cref="IApplicationBuilder"/> instance.</param>
/// <param name="middleware">The middleware type.</param>
/// <param name="args">The arguments to pass to the middleware type instance's constructor.</param>
/// <returns>The <see cref="IApplicationBuilder"/> instance.</returns>
public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, [DynamicallyAccessedMembers(MiddlewareAccessibility)] Type middleware, params object?[] args)
{
if (typeof(IMiddleware).IsAssignableFrom(middleware))
{
// IMiddleware doesn't support passing args directly since it's
// activated from the container
if (args.Length > 0)
{
throw new NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));
}
return UseMiddlewareInterface(app, middleware);
}
var applicationServices = app.ApplicationServices;
return app.Use(next =>
{
var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
var invokeMethods = methods.Where(m =>
string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)
|| string.Equals(m.Name, InvokeAsyncMethodName, StringComparison.Ordinal)
).ToArray();
if (invokeMethods.Length > 1)
{
throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName));
}
if (invokeMethods.Length == 0)
{
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware));
}
var methodInfo = invokeMethods[0];
if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType))
{
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task)));
}
var parameters = methodInfo.GetParameters();
if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
{
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext)));
}
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);
if (parameters.Length == 1)
{
return (RequestDelegate)methodInfo.CreateDelegate(typeof(RequestDelegate), instance);
}
var factory = Compile<object>(methodInfo, parameters);
return context =>
{
var serviceProvider = context.RequestServices ?? applicationServices;
if (serviceProvider == null)
{
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));
}
return factory(instance, context, serviceProvider);
};
});
}
Upvotes: 3