做黄金比较专业的网站,电子商务网站设计代做,郑州网站建设排行榜,网上公司注册前言本文编写时源码参考github仓库主分支。aspnetcore提供了Use方法供开发者自定义中间件#xff0c;该方法接收一个委托对象#xff0c;该委托接收一个RequestDelegate对象#xff0c;并返回一个RequestDelegate对象#xff0c;方法定义如下#xff1a;IApplicationBuild… 前言本文编写时源码参考github仓库主分支。aspnetcore提供了Use方法供开发者自定义中间件该方法接收一个委托对象该委托接收一个RequestDelegate对象并返回一个RequestDelegate对象方法定义如下IApplicationBuilder Use(FuncRequestDelegate, RequestDelegate middleware);委托RequestDelegate的定义/// summary
/// A function that can process an HTTP request.
/// /summary
/// param namecontextThe see crefHttpContext/ for the request./param
/// returnsA 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 logicreturn next(ctx);};
});如果写一些简单的逻辑这种方式最为方便问题是如果需要写的中间件代码比较多依然这样去写会导致我们Program.cs文件代码非常多如果有多个中间件那么最后我们的的Program.cs文件包含多个中间件代码看上去十分混乱。将中间件逻辑独立出来为了解决我们上面的代码不优雅我们希望能将每个中间件业务独立成一个文件多个中间件代码不混乱的搞到一起。我们需要这样做。单独的中间件文件// Middleware1.cs
public class Middleware1
{public static RequestDelegate Logic(RequestDelegate requestDelegate){return (HttpContext ctx) {// do your logicreturn 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底层大量使用,它主要提供一个泛型UseMiddlewareT方法用来方便我们注册中间件下面是该方法的定义public static IApplicationBuilder UseMiddlewareTMiddleware(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 applications request pipeline.
/// /summary
/// typeparam nameTMiddlewareThe middleware type./typeparam
/// param nameappThe see crefIApplicationBuilder/ instance./param
/// param nameargsThe arguments to pass to the middleware type instances constructor./param
/// returnsThe see crefIApplicationBuilder/ 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 applications request pipeline.
/// /summary
/// param nameappThe see crefIApplicationBuilder/ instance./param
/// param namemiddlewareThe middleware type./param
/// param nameargsThe arguments to pass to the middleware type instances constructor./param
/// returnsThe see crefIApplicationBuilder/ instance./returns
public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app,[DynamicallyAccessedMembers(MiddlewareAccessibility)] Type middleware,params object?[] args)
{if (typeof(IMiddleware).IsAssignableFrom(middleware)){// IMiddleware doesnt support passing args directly since its// activated from the containerif (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 Compileobject(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 doesnt support passing args directly since its// activated from the containerif (args.Length 0){throw new NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));}return UseMiddlewareInterface(app, middleware);}这里总算看到应该有的东西了如果声明UseMiddlewareT方法时对泛型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 factorythrow new InvalidOperationException(Resources.FormatException_UseMiddlewareNoMiddlewareFactory(typeof(IMiddlewareFactory)));}var middleware middlewareFactory.Create(middlewareType);if (middleware null){// The factory returned null, its a broken implementationthrow 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 namecontextThe see crefHttpContext/ for the current request./param/// param namenextThe delegate representing the remaining middleware in the request pipeline./param/// returnsA see crefTask/ that represents the execution of this middleware./returnsTask 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 Compileobject(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 logicawait 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 logicawait this.next(httpContext);}
}总结通过源码的学习我们弄清楚底层注册中间件的来龙去脉两种方式根据自己习惯进行使用笔者认为通过接口的方式更加简洁直观简单并且省去了反射带来的性能损失推荐使用。既然通过继承接口那么爽为啥还费那么大劲实现反射的方式呢由源码可知如果继承接口的话就不能进行动态传参了。if (typeof(IMiddleware).IsAssignableFrom(middleware)){// IMiddleware doesnt support passing args directly since its// activated from the containerif (args.Length 0){throw new NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));}return UseMiddlewareInterface(app, middleware);}所以在需要传参的场景则必须使用反射的方式所以两种方式都有其存在的必要。如果本文对您有帮助还请点赞转发关注一波支持作者。