中間件(Middleware)是ASP.NET Core中的一個重要特性。所謂中間件就是嵌入到應用管道中用於處理請求和響應的一段代碼。ASP.NET Core Middleware能夠分爲兩種類型:html
IMiddlewaregithub
這種中間件沒有實現特定的接口或者繼承特定類,它更像是Duck Typing (你走起路來像個鴨子, 叫起來像個鴨子, 那麼你就是個鴨子)。有兩種表現形式:web
這種方式又稱爲內聯中間件(in-line middleware),可使用Run, Map, Use,MapWhen等擴展方法來實現。如:api
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)
方法來實現向請求處理管道中注入中間件,後面會對源碼作分析。mvc
這種形式利於代碼的複用,如:app
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>(); } }
將自定義中間件配置到請求處理管道中asp.net
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseXfhMiddleware(); }
IMiddleware
提供了強類型約束的中間件,其默認實現是MiddlewareFactory,接口定義以下:async
public interface IMiddleware { Task InvokeAsync(HttpContext context, RequestDelegate next); }
IMiddlewareFactory
用於建立IMiddleware
實例及對實例進行回收,接口定義:ide
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方法,從方法簽名中能夠看到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 => { // 判斷傳入的中間件是否符合約束 }); }
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上看到。
上述全部中間件,最終都會調用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的處理管道是基於事件模型的,處理管道有多個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>
類型的列表。
ASP.NET Core Middleware
Factory-based middleware activation in ASP.NET Core
Migrate HTTP handlers and modules to ASP.NET Core middleware
用ASP.NET Core 2.0 創建規範的 REST API -- 預備知識
ASP.NET MVC 5 APPLICATION LIFECYCLE – HIGH-LEVEL VIEW(強烈推薦)
ASP.NET WEB API 2: HTTP MESSAGE LIFECYLE(牆裂推薦)
ASP.NET MVC5請求處理管道和生命週期