前言
本文编写时源码参考github仓库主分支。
aspnetcore
提供了Use
方法供开发者自定义中间件,该方法接收一个委托对象,该委托接收一个RequestDelegate
对象,并返回一个RequestDelegate
对象,方法定义如下:
IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
委托RequestDelegate
的定义
/// <summary> /// A function that can process an HTTP request. /// </summary> /// <param name="context">The <see cref="HttpContext"/> for the request.</param> /// <returns>A task that represents the completion of request processing.</returns> public delegate Task RequestDelegate(HttpContext context);
如果我们直接使用IApplicationBuilder.Use
来写中间件逻辑,可以使用lamda表达式来简化代码,如下:
app.Use((RequestDelegate next) => { return (HttpContext ctx) => { // do your logic return next(ctx); }; });
如果写一些简单的逻辑,这种方式最为方便,问题是如果需要写的中间件代码比较多,依然这样去写,会导致我们Program.cs
文件代码非常多,如果有多个中间件,那么最后我们的的Program.cs
文件包含多个中间件代码,看上去十分混乱。
将中间件逻辑独立出来
为了解决我们上面的代码不优雅,我们希望能将每个中间件业务独立成一个文件,多个中间件代码不混乱的搞到一起。我们需要这样做。
单独的中间件文件
// Middleware1.cs public class Middleware1 { public static RequestDelegate Logic(RequestDelegate requestDelegate) { return (HttpContext ctx) => { // do your logic return requestDelegate(ctx); }; } }
调用中间件
app.Use(Middleware1.Logic); // 以下是其他中间件示例 app.Use(Middleware2.Logic); app.Use(Middleware3.Logic); app.Use(Middleware4.Logic);
这种方式可以很好的将各个中间件逻辑独立出来,Program.cs
此时变得十分简洁,然而我们还不满足这样,因为我们的Logic
方法中直接返回一个lamada表达式(RequestDelegate
对象),代码层级深了一层,每个中间件都多写这一层壳似乎不太优雅,能不能去掉这层lamada表达式呢?
UseMiddlewareExtensions
为了解决上面提到的痛点,UseMiddlewareExtensions
扩展类应运而生,它在Aspnetcore
底层大量使用,它主要提供一个泛型UseMiddleware<T>
方法用来方便我们注册中间件,下面是该方法的定义
public static IApplicationBuilder UseMiddleware<TMiddleware>(this IApplicationBuilder app, params object?[] args)
如果只看这个方法的声明,估计没人知道如何使用,因为该方法接收的泛型参数TMiddleware
没有添加任何限制,而另一个args
参数也是object
类型,而且是可以不传的,也就是它只需要传任意一个类型都不会在编译时报错。
比如这样,完全不会报错:
当然,如果你这样就运行程序,一定会收到下面的异常
System.InvalidOperationException:“No public 'Invoke' or 'InvokeAsync' method found for middleware of type 'System.String'.”
提示我们传的类型没有Invoke
或InvokeAsync
公共方法,这里大概能猜到,底层应该是通过反射进行动态调用Invoke
或InvokeAsync
公共方法的。
源码分析
想要知道其本质,唯有查看源码,以下源码来自UseMiddlewareExtensions
如下,该扩展类一共提供两个并且是重载的公共方法UseMiddleware
,一般都只会使用第一个UseMiddleware
,第一个UseMiddleware
方法内部再去调用第二个UseMiddleware
方法,源码中对类型前面添加的[DynamicallyAccessedMembers(MiddlewareAccessibility)]
属性可以忽略,它的作用是为了告诉编译器我们通过反射访问的范围,以防止对程序集对我们可能调用的方法或属性等进行裁剪。
internal const string InvokeMethodName = "Invoke"; internal const string InvokeAsyncMethodName = "InvokeAsync"; /// <summary> /// Adds a middleware type to the application's request pipeline. /// </summary> /// <typeparam name="TMiddleware">The middleware type.</typeparam> /// <param name="app">The <see cref="IApplicationBuilder"/> instance.</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<[DynamicallyAccessedMembers(MiddlewareAccessibility)] TMiddleware>(this IApplicationBuilder app, params object?[] args) { return app.UseMiddleware(typeof(TMiddleware), args); } /// <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; var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public); MethodInfo? invokeMethod = null; foreach (var method in methods) { if (string.Equals(method.Name, InvokeMethodName, StringComparison.Ordinal) || string.Equals(method.Name, InvokeAsyncMethodName, StringComparison.Ordinal)) { if (invokeMethod is not null) { throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName)); } invokeMethod = method; } } if (invokeMethod is null) { throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware)); } if (!typeof(Task).IsAssignableFrom(invokeMethod.ReturnType)) { throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task))); } var parameters = invokeMethod.GetParameters(); if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext)) { throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext))); } var state = new InvokeMiddlewareState(middleware); return app.Use(next => { var middleware = state.Middleware; 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)invokeMethod.CreateDelegate(typeof(RequestDelegate), instance); } var factory = Compile<object>(invokeMethod, parameters); return context => { var serviceProvider = context.RequestServices ?? applicationServices; if (serviceProvider == null) { throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider))); } return factory(instance, context, serviceProvider); }; }); }
第一个UseMiddleware
可以直接跳过,看第二个UseMiddleware
方法,该方法一上来就先判断我们传的泛型类型是不是IMiddleware
接口的派生类,如果是,直接交给UseMiddlewareInterface
方法。
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); }
这里总算看到应该有的东西了,如果声明UseMiddleware<T>
方法时,对泛型T
添加IMiddleware
限制,我们不看源码就知道如何编写我们的中间件逻辑了,只需要写一个类,继承IMiddleware
并实现InvokeAsync
方法即可, UseMiddlewareInterface
方法的实现比较简单,因为我们继承了接口,逻辑相对会简单点。
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) { // No middleware factory throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoMiddlewareFactory(typeof(IMiddlewareFactory))); } var middleware = middlewareFactory.Create(middlewareType); if (middleware == null) { // The factory returned null, it's a broken implementation throw new InvalidOperationException(Resources.FormatException_UseMiddlewareUnableToCreateMiddleware(middlewareFactory.GetType(), middlewareType)); } try { await middleware.InvokeAsync(context, next); } finally { middlewareFactory.Release(middleware); } }; }); }
public interface IMiddleware { /// <summary> /// Request handling method. /// </summary> /// <param name="context">The <see cref="HttpContext"/> for the current request.</param> /// <param name="next">The delegate representing the remaining middleware in the request pipeline.</param> /// <returns>A <see cref="Task"/> that represents the execution of this middleware.</returns> Task InvokeAsync(HttpContext context, RequestDelegate next); }
如果我们的类不满足IMiddleware
,继续往下看
通过反射查找泛型类中Invoke
或InvokeAsync
方法
var applicationServices = app.ApplicationServices; var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public); MethodInfo? invokeMethod = null; foreach (var method in methods) { if (string.Equals(method.Name, InvokeMethodName, StringComparison.Ordinal) || string.Equals(method.Name, InvokeAsyncMethodName, StringComparison.Ordinal)) { // 如果Invoke和InvokeAsync同时存在,则抛出异常,也就是,我们只能二选一 if (invokeMethod is not null) { throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName)); } invokeMethod = method; } } // 如果找不到Invoke和InvokeAsync则抛出异常,上文提到的那个异常。 if (invokeMethod is null) { throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware)); } // 如果Invoke和InvokeAsync方法的返回值不是Task或Task的派生类,则抛出异常 if (!typeof(Task).IsAssignableFrom(invokeMethod.ReturnType)) { throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task))); } Snippet // 如果Invoke和InvokeAsync方法没有参数,或第一个参数不是HttpContext,抛异常 var parameters = invokeMethod.GetParameters(); if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext)) { throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext))); }
上面一堆逻辑主要就是检查我们的Invoke
和InvokeAsync
方法是否符合要求,即:必须是接收HttpContext
参数,返回Task
对象,这恰好就是委托RequestDelegate
的定义。
构造RequestDelegate
这部分源码的解读都注释到相应的位置了,如下
var state = new InvokeMiddlewareState(middleware); // 调用Use函数,向管道中注册中间件 return app.Use(next => { var middleware = state.Middleware; var ctorArgs = new object[args.Length + 1]; // next是RequestDelegate对象,作为构造函数的第一个参数传入 ctorArgs[0] = next; Array.Copy(args, 0, ctorArgs, 1, args.Length); // 反射实例化我们传入的泛型类,并把next和args作为构造函数的参数传入 var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs); // 如果我们的Invoke方法只有一个参数,则直接创建该方法的委托 if (parameters.Length == 1) { return (RequestDelegate)invokeMethod.CreateDelegate(typeof(RequestDelegate), instance); } // 当Invoke方法不止一个参数HttpContext,通过Compile函数创建动态表达式目录树, // 表达式目录树的构造此处略过,其目的是实现将除第一个参数的其他参数通过IOC注入 var factory = Compile<object>(invokeMethod, parameters); return context => { // 获取serviceProvider用于在上面构造的表达式目录树中实现依赖注入 var serviceProvider = context.RequestServices ?? applicationServices; if (serviceProvider == null) { throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider))); } // 将所需的参数传入构造的表达式目录树工厂 return factory(instance, context, serviceProvider); }; });
至此,整个扩展类的源码就解读完了。
通过UseMiddleware注入自定义中间件
通过上面的源码解读,我们知道了其实我们传入的泛型类型是有严格的要求的,主要有两种
通过继承IMiddleware
继承IMiddleware
并实现该接口的InvokeAsync
函数
public class Middleware1 : IMiddleware { public async Task InvokeAsync(HttpContext context, RequestDelegate next) { // do your logic await next(context); } }
通过反射
我们知道,在不继承IMiddleware
的情况下,底层会通过反射实例化泛型类型,并通过构造函数传入RequestDelegate
,而且要有一个公共函数Invoke
或InvokeAsync
,并且接收的第一个参数是HttpContext
,返回Task
,根据要求我们将Middleware1.cs
改造如下
public class Middleware1 { RequestDelegate next; public Middleware1(RequestDelegate next) { this.next = next; } public async Task Invoke(HttpContext httpContext) { // do your logic await this.next(httpContext); } }
总结
通过源码的学习,我们弄清楚底层注册中间件的来龙去脉,两种方式根据自己习惯进行使用,笔者认为通过接口的方式更加简洁直观简单,并且省去了反射带来的性能损失,推荐使用。既然通过继承接口那么爽,为啥还费那么大劲实现反射的方式呢?由源码可知,如果继承接口的话,就不能进行动态传参了。
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); }
所以在需要传参的场景,则必须使用反射的方式,所以两种方式都有其存在的必要。
如果本文对您有帮助,还请点赞转发关注一波支持作者。