ASP.NET Core Middleware
中介軟體(Middleware)是ASP.NET Core中的一個重要特性。所謂中介軟體就是嵌入到應用管道中用於處理請求和響應的一段程式碼。ASP.NET Core Middleware可以分為兩種型別:
-
ofollow,noindex" target="_blank">Conventional Middleware
Conventional Middleware
這種中介軟體沒有實現特定的介面或者繼承特定類,它更像是Duck Typing (你走起路來像個鴨子, 叫起來像個鴨子, 那麼你就是個鴨子)。有兩種表現形式:
匿名方法
這種方式又稱為內聯中介軟體(in-line middleware),可以使用 Run , Map , Use , MapWhen 等擴充套件方法來實現。如:
public class Startup { public void Configure(IApplicationBuilder app) { app.Use(async (context, next) => { // Do work that doesn't write to the Response. await next.Invoke(); // Do logging or other work that doesn't write to the Response. }); } }
IApplicationBuilder
的擴充套件方法: Run
、 Map
、 MapWhen
及
Use(this IApplicationBuilder app, Func<HttpContext, Func<Task>, Task> middleware)
,最終都會呼叫 IApplicationBuilder
介面中的 Use(Func<RequestDelegate, RequestDelegate> middleware)
方法來實現向請求處理管道中注入中介軟體,後面會對原始碼做分析。
自定義中介軟體類
這種形式利於程式碼的複用,如:
public class XfhMiddleware { private readonly RequestDelegate _next; //在應用程式的生命週期中,中介軟體的建構函式只會被呼叫一次 public XfhMiddleware(RequestDelegate next) { this._next = next; } public async Task InvokeAsync(HttpContext context) { // Do something... await _next(context); } } public static class XfhMiddlewareExtension { public static IApplicationBuilder UseXfhMiddleware(this IApplicationBuilder builder) { // 使用UseMiddleware將自定義中介軟體新增到請求處理管道中 return builder.UseMiddleware<XfhMiddleware>(); } }
將自定義中介軟體配置到請求處理管道中
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseXfhMiddleware(); }
IMiddleware
IMiddleware
提供了強型別約束的中介軟體,其預設實現是 MiddlewareFactory ,介面定義如下:
public interface IMiddleware { Task InvokeAsync(HttpContext context, RequestDelegate next); }
IMiddlewareFactory
用於建立 IMiddleware
例項及對例項進行回收,介面定義:
public interface IMiddlewareFactory { public IMiddleware Create (Type middlewareType); public void Release (IMiddleware middleware); }
自定義 IMiddleware
型別中介軟體
public class MyMiddleware : IMiddleware { public async Task InvokeAsync(HttpContext context, RequestDelegate next) { await next(context); } } public static class MyMiddlewareExtensions { public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder) { return builder.UseMiddleware<MyMiddleware>(); } }
將中介軟體注入到請求處理管道:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMyMiddleware(); }
使用 IMiddleware
型別的中介軟體需要在容器中進行註冊,否則拋異常,具體原因下面分析:

將中介軟體注入到容器中:
public void ConfigureServices(IServiceCollection services) { services.AddSingleton<MyMiddleware>(); services.AddMvc(); }
一段警告
下面貼一段微軟文件中的警告,大意是不要試圖去改變已發往客戶端的響應內容,否則可能會引發異常。實在是太懶了,不想翻譯就把原文貼出來了:
Warning
Don't call next.Invoke
after the response has been sent to the client. Changes to HttpResponse after the response has started throw an exception. For example, changes such as setting headers and a status code throw an exception. Writing to the response body after calling next:
-
May cause a protocol violation. For example, writing more than the stated Content-Length.
-
May corrupt the body format. For example, writing an HTML footer to a CSS file.
HasStarted is a useful hint to indicate if headers have been sent or the body has been written to.
UseMiddleware
前面將自定義中介軟體注入到請求處理管道時用到了 UseMiddleware 方法,從方法簽名中可以看到 UserMiddleware
可以 接受多個引數 :
public static class UseMiddlewareExtensions { public static IApplicationBuilder UseMiddleware<TMiddleware>(this IApplicationBuilder app, params object[] args); public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args); }
接下來我們看下 UserMiddleware
方法的具體實現,由於該方法程式碼量較大,所以這裡只看其中的關鍵部分,方法整體流程如下:
public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args) { // IMiddleware型別 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); } // Conventional Middleware var applicationServices = app.ApplicationServices; return app.Use(next => { // 判斷傳入的中介軟體是否符合約束 }); }
- 該方法首先判斷傳入的middleware是否是
IMiddleware
型別,如果是則呼叫UseMiddlewareInterface
從這段程式碼中可以看到 IMiddlewareFactory
負責建立並回收 IMiddleware
物件
public static class UseMiddlewareExtensions { private static IApplicationBuilder UseMiddlewareInterface(IApplicationBuilder app, Type middlewareType) { return app.Use(next => { return async context => { // 從容器中獲取IMiddlewareFactory例項 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); } }; }); } }
從 MiddlewareFactory
的 Create
方法中可以看到, IMiddleware
例項是從容器中獲取的,若容器中找不到則會丟擲異常:
public class MiddlewareFactory : IMiddlewareFactory { private readonly IServiceProvider _serviceProvider; public MiddlewareFactory(IServiceProvider serviceProvider) { this._serviceProvider = serviceProvider; } public IMiddleware Create(Type middlewareType) { return ServiceProviderServiceExtensions.GetRequiredService(this._serviceProvider, middlewareType) as IMiddleware; } public void Release(IMiddleware middleware) { } }
- 若是
Conventional Middleware
則判斷傳入的middleware是否符合約束
首先判斷傳入的middleware中是否僅包含一個名稱為Invoke或InvokeAsync的公共例項方法
// UseMiddlewareExtensions類中的兩個常量 internal const string InvokeMethodName = "Invoke"; internal const string InvokeAsyncMethodName = "InvokeAsync"; // UserMiddleware方法 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)); }
其次判斷方法的返回型別是否是 Task
:
var methodInfo = invokeMethods[0]; if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType)) { throw new InvalidOperationException( Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task))); }
然後再判斷,方法的第一個引數是否是 HttpContext
型別:
var parameters = methodInfo.GetParameters(); if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext)) { throw new InvalidOperationException( Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext))); }
對於 Invoke
或 InvokeAsync
僅包含一個 HttpContext
型別引數的情況用到了反射( ActivatorUtilities.CreateInstance 方法中)來構建 RequestDelegate
var ctorArgs = new object[args.Length + 1]; ctorArgs[0] = next; Array.Copy(args, 0, ctorArgs, 1, args.Length); var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middlewa if (parameters.Length == 1) { return (RequestDelegate) methodInfo.CreateDelegate(typeof(RequestDelegate), in }
對於包含多個引數的情況,則使用了表示式樹來構建 RequestDelegate
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); };
完整的程式碼可以在 Github 上看到。
Use(Func<RequestDelegate, RequestDelegate> middleware)
上述所有中介軟體,最終都會呼叫 IApplicationBuilder
介面中的 Use(Func<RequestDelegate, RequestDelegate> middleware)
方法來實現向請求處理管道中註冊中介軟體,該方法在 ApplicationBuilder 類的實現如下:
public class ApplicationBuilder : IApplicationBuilder { private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>(); public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware) { this._components.Add(middleware); return this; } }
從上面程式碼中可以看到,中介軟體是一個 RequestDelegate
型別的委託,請求處理管道其實是一個委託列表,請求委託簽名如下:
public delegate Task RequestDelegate(HttpContext context);
與ASP.NET處理管道的區別
傳統的ASP.NET的處理管道是基於事件模型的,處理管道有多個 IHttpModule
和一個 IHttpHandler
組成。請求處理管道中各個模組被呼叫的順序取決於兩方面:
Web.config

ASP.NET Core的請求處理管道則是有一堆中介軟體組成,相對ASP.NET更簡單。
中介軟體處理請求和響應的順序只與其在程式碼中的註冊順序有關:處理請求按註冊順序依次執行,處理響應按註冊順序反方向依次執行。
其次,在ASP.NET Core中只需使用程式碼,而無需使用 Global.asax
和 Web.config
來配置請求處理管道。
小結
所謂中介軟體就是嵌入到應用管道中用於處理請求和響應的一段程式碼,它主要有兩個作用:
- 處理請求和響應
- 可以阻止請求發往請求處理管道中的下一個中介軟體
在ASP.NET Core中,中介軟體是以 RequestDelegate
委託的形式體現的。
ASP.NET Core中整個請求處理管道的建立是圍繞這種 IApplicationBuilder
介面進行的,請求處理管道是一個 List<RequestDelegate>
型別的列表。