本章將和大家分享在ASP.NET Core中如何使用UseMiddleware擴展方法註冊自定義中間件及其實現原理。 ...
有的中間件功能比較簡單,有的則比較複雜,並且依賴其它組件。除了直接用 ApplicationBuilder 的 Use() 方法註冊中間件外,還可以使用 ApplicationBuilder 的擴展方法 UseMiddleware() 註冊自定義中間件。
廢話不多說,我們在上一篇的基礎上加一個自定義中間件類 CustomMiddleware ,如下所示:
using Microsoft.AspNetCore.Http; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace NETCoreMiddleware.Middlewares { /// <summary> /// 自定義中間件 /// </summary> public class CustomMiddleware { /// <summary> /// 保存下一個中間件 /// </summary> private readonly RequestDelegate _next; /// <summary> /// 構造函數 /// </summary> /// <param name="next">下一個中間件</param> public CustomMiddleware(RequestDelegate next) { Console.WriteLine($"{typeof(CustomMiddleware)} 被構造"); _next = next; } /// <summary> /// 中間件方法 /// </summary> public async Task InvokeAsync(HttpContext context) { await Task.Run(() => Console.WriteLine($"This is CustomMiddleware Start")); await _next.Invoke(context); //可通過不調用 next 參數使請求管道短路 await Task.Run(() => Console.WriteLine($"This is CustomMiddleware End")); } } }
接著將該中間件裝配到我們的Http請求處理管道中,如下所示(標紅部分):
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using NETCoreMiddleware.Middlewares; namespace NETCoreMiddleware { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } //服務註冊(往容器中添加服務) // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(); } /// <summary> /// 配置Http請求處理管道 /// Http請求管道模型---就是Http請求被處理的步驟 /// 所謂管道,就是拿著HttpContext,經過多個步驟的加工,生成Response,這就是管道 /// </summary> /// <param name="app"></param> /// <param name="env"></param> // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { #region 環境參數 if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); } #endregion 環境參數 //靜態文件中間件 app.UseStaticFiles(); #region Use中間件 //中間件1 app.Use(next => { Console.WriteLine("middleware 1"); return async context => { await Task.Run(() => { Console.WriteLine(""); Console.WriteLine("===================================Middleware==================================="); Console.WriteLine($"This is middleware 1 Start"); }); await next.Invoke(context); await Task.Run(() => { Console.WriteLine($"This is middleware 1 End"); Console.WriteLine("===================================Middleware==================================="); }); }; }); //中間件2 app.Use(next => { Console.WriteLine("middleware 2"); return async context => { await Task.Run(() => Console.WriteLine($"This is middleware 2 Start")); await next.Invoke(context); //可通過不調用 next 參數使請求管道短路 await Task.Run(() => Console.WriteLine($"This is middleware 2 End")); }; }); //中間件3 app.Use(next => { Console.WriteLine("middleware 3"); return async context => { await Task.Run(() => Console.WriteLine($"This is middleware 3 Start")); await next.Invoke(context); await Task.Run(() => Console.WriteLine($"This is middleware 3 End")); }; }); //中間件4 //Use方法的另外一個重載 app.Use(async (context, next) => { await Task.Run(() => Console.WriteLine($"This is middleware 4 Start")); await next(); await Task.Run(() => Console.WriteLine($"This is middleware 4 End")); }); #endregion Use中間件 #region UseMiddleware中間件 app.UseMiddleware<CustomMiddleware>(); #endregion UseMiddleware中間件 #region 終端中間件 //app.Use(_ => handler); app.Run(async context => { await Task.Run(() => Console.WriteLine($"This is Run")); }); #endregion 終端中間件 #region 最終把請求交給MVC app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "areas", pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}"); endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); }); #endregion 最終把請求交給MVC } } }
下麵我們使用命令行(CLI)方式啟動我們的網站,如下所示:
啟動成功後,我們來訪問一下 “/home/index” ,控制台輸出結果如下所示:
可以發現我們自定義的中間件生效了。
下麵我們結合ASP.NET Core源碼來分析下其實現原理:
我們將游標移動到 UseMiddleware 處按 F12 轉到定義,如下所示:
可以發現它是位於 UseMiddlewareExtensions 擴展類中的,我們找到 UseMiddlewareExtensions 類的源碼,如下所示:
// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Abstractions; using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Builder { /// <summary> /// Extension methods for adding typed middleware. /// </summary> public static class UseMiddlewareExtensions { internal const string InvokeMethodName = "Invoke"; internal const string InvokeAsyncMethodName = "InvokeAsync"; private static readonly MethodInfo GetServiceInfo = typeof(UseMiddlewareExtensions).GetMethod(nameof(GetService), BindingFlags.NonPublic | BindingFlags.Static); /// <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<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, Type middleware, params object[] args) { if (typeof(IMiddleware).GetTypeInfo().IsAssignableFrom(middleware.GetTypeInfo())) { // 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); }; }); } 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); } }; }); } private static Func<T, HttpContext, IServiceProvider, Task> Compile<T>(MethodInfo methodInfo, ParameterInfo[] parameters) { // If we call something like // // public class Middleware // { // public Task Invoke(HttpContext context, ILoggerFactory loggerFactory) // { // // } // } // // We'll end up with something like this: // Generic version: // // Task Invoke(Middleware instance, HttpContext httpContext, IServiceProvider provider) // { // return instance.Invoke(httpContext, (ILoggerFactory)UseMiddlewareExtensions.GetService(provider, typeof(ILoggerFactory)); // } // Non generic version: // // Task Invoke(object instance, HttpContext httpContext, IServiceProvider provider) // { // return ((Middleware)instance).Invoke(httpContext, (ILoggerFactory)UseMiddlewareExtensions.GetService(provider, typeof(ILoggerFactory)); // } var middleware = typeof(T); var httpContextArg = Expression.Parameter(typeof(HttpContext), "httpContext"); var providerArg = Expression.Parameter(typeof(IServiceProvider), "serviceProvider"); var instanceArg = Expression.Parameter(middleware, "middleware"); var methodArguments = new Expression[parameters.Length]; methodArguments[0] = httpContextArg; for (int i = 1; i < parameters.Length; i++) { var parameterType = parameters[i].ParameterType; if (parameterType.IsByRef) { throw new NotSupportedException(Resources.FormatException_InvokeDoesNotSupportRefOrOutParams(InvokeMethodName)); } var parameterTypeExpression = new Expression[] { providerArg, Expression.Constant(parameterType, typeof(Type)), Expression.Constant(methodInfo.DeclaringType, typeof(Type)) }; var getServiceCall = Expression.Call(GetServiceInfo, parameterTypeExpression); methodArguments[i] = Expression.Convert(getServiceCall, parameterType); } Expression middlewareInstanceArg = instanceArg; if (methodInfo.DeclaringType != typeof(T)) { middlewareInstanceArg = Expression.Convert(middlewareInstanceArg, methodInfo.DeclaringType); } var body = Expression.Call(middlewareInstanceArg, methodInfo, methodArguments); var lambda = Expression.Lambda<Func<T, HttpContext, IServiceProvider, Task>>(body, instanceArg, httpContextArg, providerArg); return lambda.Compile(); } private static object GetService(IServiceProvider sp, Type type, Type middleware) { var service = sp.GetService(type); if (service == null) { throw new InvalidOperationException(Resources.FormatException_InvokeMiddlewareNoService(type, middleware)); } return service; } } }Microsoft.AspNetCore.Builder.UseMiddlewareExtensions類源碼
從中我們可以知道,它最終會調用下麵的這個方法:
/// <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, Type middleware, params object[] args) { if (typeof(IMiddleware).GetTypeInfo().IsAssignableFrom(middleware.GetTypeInfo())) { // 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); }; }); }
其中 ActivatorUtilities 類的源碼如下:
// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.ExceptionServices; #if ActivatorUtilities_In_DependencyInjection using Microsoft.Extensions.Internal; namespace Microsoft.Extensions.DependencyInjection #else namespace Microsoft.Extensions.Internal #endif { /// <summary> /// Helper code for the various activator services. /// </summary> #if ActivatorUtilities_In_DependencyInjection public #else // Do not take a dependency on this class unless you are explicitly trying to avoid taking a // dependency on Microsoft.AspNetCore.DependencyInjection.Abstractions. internal #endif static class ActivatorUtilities { private static readonly MethodInfo GetServiceInfo = GetMethodInfo<Func<IServiceProvider, Type, Type, bool, object>>((sp, t, r, c) => GetService(sp, t, r, c)); /// <summary> /// Instantiate a type with constructor arguments provided directly and/or from an <see cref="IServiceProvider"/>. /// </summary> /// <param name="provider">The service provider used to resolve dependencies</param> /// <param name="instanceType">The type to activate</param> /// <param name="parameters">Constructor arguments not provided by the <paramref name="provider"/>.</param> /// <returns>An activated object of type instanceType</returns> public static object CreateInstance(IServiceProvider provider, Type instanceType, params object[] parameters) { int bestLength = -1; var seenPreferred = false; ConstructorMatcher bestMatcher = default; if (!instanceType.GetTypeInfo().IsAbstract) { foreach (var constructor in instanceType .GetTypeInfo() .DeclaredConstructors) { if (!constructor.IsStatic && constructor.IsPublic) { var matcher = new ConstructorMatcher(constructor); var isPreferred = constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute),