依赖注入如何与中间件一起工作?
how does dependency injection work with Middleware?
我看到一些代码是这样的:
public class CustomMiddleware {
private RequestDelegate next;
public CustomMiddleware (RequestDelegate nextDelegate) {
next = nextDelegate;
}
public async Task Invoke(HttpContext context, IResponseFormatter formatter) {
...
await next(context);
}
}
并且 IResponseFormatter 服务注册为:
public void ConfigureServices(IServiceCollection services) {
services.AddTransient<IResponseFormatter, GuidService>();
}
我知道DI是怎么工作的,但是我对中间件的理解是,next
(RequestDelegate
)代表下一个中间件的Invoke
方法,所以在CustomMiddleware中,即使是第二个参数被DI解析了,但是RequestDelegate
的定义是
public delegate Task RequestDelegate(HttpContext context);
CustomMiddleware
之前的中间件如何知道CustomMiddleware
的Invoke
方法因为有一个额外的参数而改变了?它无法预先知道,因此之前的中间件的 next
RequestDelegate
与 CustomMiddleware
的 Invoke
方法的签名不匹配?
在内部,框架代码使用反射通过以下约定确定中间件的构造函数和 Invoke 成员的参数
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).
引用Write custom ASP.NET Core middleware
所示
/// <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);
};
});
}
How does the previous middleware before CustomMiddleware know that CustomMiddleware's Invoke method has changed by having an extra argument?
由于惯例(和反射)。
自定义中间件不继承任何接口或基础 类,因此 运行时间了解如何使用中间件的唯一方法是 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.
有了这些知识,安全地 运行 中间件:您可以使用反射来获取 Invoke
的依赖项并在知道 return 类型是 Task
的情况下执行它。例如:
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
与 CustomMiddleware.Invoke
的签名不匹配。
RequestDelegate
不需要匹配CustomMiddleware.Invoke
的签名。
所有 RequestDelegate
(next
) 关心的是将相同的 HttpContext
实例传递到中间件链下,并且由于(前面提到的)约定,它将始终具有该实例(强调):
- A method named Invoke or InvokeAsync. This method must:
- Return a Task.
- Accept a first parameter of type HttpContext.
最后,next
不是直接调用CustomMiddleware.Invoke
。在中间件之间,DI 有机会注入下一个中间件所需的服务。
我看到一些代码是这样的:
public class CustomMiddleware {
private RequestDelegate next;
public CustomMiddleware (RequestDelegate nextDelegate) {
next = nextDelegate;
}
public async Task Invoke(HttpContext context, IResponseFormatter formatter) {
...
await next(context);
}
}
并且 IResponseFormatter 服务注册为:
public void ConfigureServices(IServiceCollection services) {
services.AddTransient<IResponseFormatter, GuidService>();
}
我知道DI是怎么工作的,但是我对中间件的理解是,next
(RequestDelegate
)代表下一个中间件的Invoke
方法,所以在CustomMiddleware中,即使是第二个参数被DI解析了,但是RequestDelegate
的定义是
public delegate Task RequestDelegate(HttpContext context);
CustomMiddleware
之前的中间件如何知道CustomMiddleware
的Invoke
方法因为有一个额外的参数而改变了?它无法预先知道,因此之前的中间件的 next
RequestDelegate
与 CustomMiddleware
的 Invoke
方法的签名不匹配?
在内部,框架代码使用反射通过以下约定确定中间件的构造函数和 Invoke 成员的参数
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).
引用Write custom ASP.NET Core middleware
所示/// <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);
};
});
}
How does the previous middleware before CustomMiddleware know that CustomMiddleware's Invoke method has changed by having an extra argument?
由于惯例(和反射)。
自定义中间件不继承任何接口或基础 类,因此 运行时间了解如何使用中间件的唯一方法是 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.
有了这些知识,安全地 运行 中间件:您可以使用反射来获取 Invoke
的依赖项并在知道 return 类型是 Task
的情况下执行它。例如:
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
与 CustomMiddleware.Invoke
的签名不匹配。
RequestDelegate
不需要匹配CustomMiddleware.Invoke
的签名。
所有 RequestDelegate
(next
) 关心的是将相同的 HttpContext
实例传递到中间件链下,并且由于(前面提到的)约定,它将始终具有该实例(强调):
- A method named Invoke or InvokeAsync. This method must:
- Return a Task.
- Accept a first parameter of type HttpContext.
最后,next
不是直接调用CustomMiddleware.Invoke
。在中间件之间,DI 有机会注入下一个中间件所需的服务。