本文主要是對.NET Core開發日誌——Middleware的補遺,可是會從看起來平平無奇的RequestDelegate開始敘述,因此以其做爲標題,也是合情合理。html
RequestDelegate是一種委託類型,其全貌爲public delegate Task RequestDelegate(HttpContext context)
,MSDN上對它的解釋,"A function that can process an HTTP request."——處理HTTP請求的函數。惟一參數,是最熟悉不過的HttpContext,返回值則是表示請求處理完成的異步操做類型。app
能夠將其理解爲ASP.NET Core中對一切HTTP請求處理的抽象(委託類型自己可視爲函數模板,其實現具備統一的參數列表及返回值類型),沒有它整個框架就失去了對HTTP請求的處理能力。框架
而且它也是構成Middleware的基石。或者更準確地說參數與返回值都是其的Func<RequestDelegate, RequestDelegate>
委託類型正是維持Middleware運轉的核心齒輪。異步
組裝齒輪的地方位於ApplicationBuilder類以內,其中包含着全部齒輪的集合。async
private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();
以及添加齒輪的方法:ide
public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware) { _components.Add(middleware); return this; }
在Startup類的Configure方法裏調用以上ApplicationBuilder的Use方法,就能夠完成一個最簡單的Middleware。函數
public void Configure(IApplicationBuilder app) { app.Use(_ => { return context => { return context.Response.WriteAsync("Hello, World!"); }; }); }
齒輪要想變成Middleware,在完成添加後,還須要通過組裝。ui
public RequestDelegate Build() { RequestDelegate app = context => { context.Response.StatusCode = 404; return Task.CompletedTask; }; foreach (var component in _components.Reverse()) { app = component(app); } return app; }
Build方法裏先定義了最底層的零件——app,context => { context.Response.StatusCode = 404; return Task.CompletedTask; }
,這段代碼意味着,若是沒有添加任何Middleware的話,ASP.NET Core站點啓動後,會直接出現404的錯誤。this
接下的一段,遍歷倒序排列的齒輪,開始正式組裝。日誌
在上述例子裏,只使用了一個齒輪:
_ => { return context => { return context.Response.WriteAsync("Hello, World!"); }; }
那麼第一次也是最後一次循環後,執行component(app)
操做,app被從新賦值爲:
context => context.Response.WriteAsync("Hello, World!");
組裝的結果即是app的值。
這個組裝過程在WebHost進行BuildApplication時開始操做。今後方法的返回值類型能夠看出,雖然明義上是建立Application,其實生成的是RequestDelegate。
private RequestDelegate BuildApplication() { try { ... var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>(); var builder = builderFactory.CreateBuilder(Server.Features); ... Action<IApplicationBuilder> configure = _startup.Configure; ... configure(builder); return builder.Build(); } ... }
而這個RequestDelegate最終會在HostingApplication類的ProcessRequestAsync方法裏被調用。
public virtual async Task StartAsync(CancellationToken cancellationToken = default) { ... var application = BuildApplication(); ... var hostingApp = new HostingApplication(application, _logger, diagnosticSource, httpContextFactory); ... } public HostingApplication( RequestDelegate application, ILogger logger, DiagnosticListener diagnosticSource, IHttpContextFactory httpContextFactory) { _application = application; _diagnostics = new HostingApplicationDiagnostics(logger, diagnosticSource); _httpContextFactory = httpContextFactory; } public Task ProcessRequestAsync(Context context) { return _application(context.HttpContext); }
上例中的執行結果便是顯示Hello, World!字符。
404的錯誤再也不出現,意味着這種Middleware只會完成本身對HTTP請求的處理,並不會將請求傳至下一層的Middleware。
要想達成不斷傳遞請求的目的,須要使用另外一種Use擴展方法。
public static IApplicationBuilder Use(this IApplicationBuilder app, Func<HttpContext, Func<Task>, Task> middleware) { return app.Use(next => { return context => { Func<Task> simpleNext = () => next(context); return middleware(context, simpleNext); }; }); }
在實際代碼中能夠這麼寫:
public void Configure(IApplicationBuilder app) { app.Use(async (context, next) => { await context.Response.WriteAsync("I am a Middleware!\n"); await next.Invoke(); }); app.Use(_ => { return context => { return context.Response.WriteAsync("Hello, World!"); }; }); }
如今多了個Middleware,繼續上面的組裝過程。app的值最終被賦值爲:
async context => { Func<Task> simpleNext = () => context.Response.WriteAsync("Hello, World!"); await context.Response.WriteAsync("I am a Middleware!\n"); await simpleNext.Invoke(); };
顯示結果爲:
I am a Middleware! Hello, World!
下面的流程圖中能夠清楚地說明這個過程。
若是把await next.Invoke()
註釋掉的話,
public void Configure(IApplicationBuilder app) { app.Use(async (context, next) => { await context.Response.WriteAsync("I am a Middleware!\n"); //await next.Invoke(); }); app.Use(_ => { return context => { return context.Response.WriteAsync("Hello, World!"); }; }); }
上例中第一個Middleware處理完後,不會繼續交給第二個Middleware處理。注意如下simpleNext的方法只被定義而沒有被調用。
async context => { Func<Task> simpleNext = () => context.Response.WriteAsync("Hello, World!"); await context.Response.WriteAsync("I am a Middleware!\n"); };
這種狀況被稱爲短路(short-circuiting)。
作短路處理的Middleware通常會放在全部Middleware的最後,以做爲整個pipeline的終點。
而且更常見的方式是用Run擴展方法。
public static void Run(this IApplicationBuilder app, RequestDelegate handler) { ... app.Use(_ => handler); }
因此能夠把上面例子的代碼改爲下面的形式:
public void Configure(IApplicationBuilder app) { app.Use(async (context, next) => { await context.Response.WriteAsync("I am a Middleware!\n"); await next.Invoke(); }); app.Run(async context => { await context.Response.WriteAsync("Hello, World!"); }); }
除了短路以外,Middleware處理時還能夠有分支的狀況。
public void Configure(IApplicationBuilder app) { app.Map("/branch1", ab => { ab.Run(async context => { await context.Response.WriteAsync("Map branch 1"); }); }); app.Map("/branch2", ab => { ab.Run(async context => { await context.Response.WriteAsync("Map branch 2"); }); }); app.Use(async (context, next) => { await context.Response.WriteAsync("I am a Middleware!\n"); await next.Invoke(); }); app.Run(async context => { await context.Response.WriteAsync("Hello, World!"); }); }
URL地址後面跟着branch1時:
URL地址後面跟着branch2時:
其它狀況下:
Map擴展方法的代碼實現:
public static IApplicationBuilder Map(this IApplicationBuilder app, PathString pathMatch, Action<IApplicationBuilder> configuration) { ... // create branch var branchBuilder = app.New(); configuration(branchBuilder); var branch = branchBuilder.Build(); var options = new MapOptions { Branch = branch, PathMatch = pathMatch, }; return app.Use(next => new MapMiddleware(next, options).Invoke); }
建立分支的辦法就是從新實例化一個ApplicationBuilder。
public IApplicationBuilder New() { return new ApplicationBuilder(this); }
對分支的處理則是封裝在MapMiddleware類之中。
public async Task Invoke(HttpContext context) { ... PathString matchedPath; PathString remainingPath; if (context.Request.Path.StartsWithSegments(_options.PathMatch, out matchedPath, out remainingPath)) { // Update the path var path = context.Request.Path; var pathBase = context.Request.PathBase; context.Request.PathBase = pathBase.Add(matchedPath); context.Request.Path = remainingPath; try { await _options.Branch(context); } finally { context.Request.PathBase = pathBase; context.Request.Path = path; } } else { await _next(context); } }
說到MapMiddleware,不得不說起各類以Use開頭的擴展方法,好比UseStaticFiles,UseMvc,UsePathBase等等。
這些方法內部都會調用UseMiddleware方法以使用各種定製的Middleware類。以下面UsePathBase的代碼:
public static IApplicationBuilder UsePathBase(this IApplicationBuilder app, PathString pathBase) { ... // Strip trailing slashes pathBase = pathBase.Value?.TrimEnd('/'); if (!pathBase.HasValue) { return app; } return app.UseMiddleware<UsePathBaseMiddleware>(pathBase); }
而從UseMiddleware方法中能夠獲知,Middleware類需知足二者條件之一才能被有效使用。其一是實現IMiddleware,其二,必須有Invoke或者InvokeAsync方法,且方法至少要有一個HttpContext類型參數(它還只能是放第一個),同時返回值須要是Task類型。
internal const string InvokeMethodName = "Invoke"; internal const string InvokeAsyncMethodName = "InvokeAsync"; public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args) { if (typeof(IMiddleware).GetTypeInfo().IsAssignableFrom(middleware.GetTypeInfo())) { ... 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(); ... 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; ... return factory(instance, context, serviceProvider); }; }); }
對ASP.NET Core中Middleware的介紹到此終於能夠告一段落,但願這兩篇文章可以爲讀者提供些許助力。