ASP.NET Core的處理流程是一個管道,而中間件是裝配到管道中的用於處理請求和響應的組件。中間件按照裝配的前後順序執行,並決定是否進入下一個組件。中間件管道的處理流程以下圖(圖片來源於官網):app
管道式的處理方式,更加方便咱們對程序進行擴展。asp.net
ASP.NET Core中間件模型是咱們可以快捷的開發本身的中間件,完成對應用的擴展,咱們先從一個簡單的例子瞭解一下中間件的開發。async
首先,咱們建立一個ASP.NET Core 應用,在Startup.cs中有以下代碼:ui
app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); });
這段代碼中,使用Run方法運行一個委託,這就是最簡單的中間件,它攔截了全部請求,返回一段文本做爲響應。Run委託終止了管道的運行,所以也叫做終端中間件。this
咱們再看另一個例子:spa
app.Use(async (context, next) => { //Do something here //Invoke next middleware await next.Invoke(); //Do something here });
這段代碼中,使用Use方法運行一個委託,咱們能夠在Next調用以前和以後分別執行自定義的代碼,從而能夠方便的進行日誌記錄等工做。這段代碼中,使用next.Invoke()方法調用下一個中間件,從而將中間件管道連貫起來;若是不調用next.Invoke()方法,則會形成管道短路。.net
處理上面兩種方式,ASP.NET Core 還可使用Map建立基於路徑匹配的分支、使用MapWhen建立基於條件的分支。代碼以下:代理
private static void HandleMap(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Handle Map"); }); } private static void HandleBranch(IApplicationBuilder app) { app.Run(async context => { var branchVer = context.Request.Query["branch"]; await context.Response.WriteAsync($"Branch used = {branchVer}"); }); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.Map("/map", HandleMap); app.MapWhen(context => context.Request.Query.ContainsKey("branch"), HandleBranch); app.Run(async context => { await context.Response.WriteAsync("Hello World!"); }); }
上面的代碼演示瞭如何使用Map和MapWhen建立基於路徑和條件的分支。另外,Map方法還支持層級的分支,咱們參照下面的代碼:日誌
app.Map("/level1", level1App => { level1App.Map("/level2a", level2AApp => { // "/level1/level2a" processing }); level1App.Map("/level2b", level2BApp => { // "/level1/level2b" processing }); });
須要注意,使用 Map 時,將從 HttpRequest.Path 中刪除匹配的Path,並針對每一個請求將該線段追加到 HttpRequest.PathBase。例如對於路徑/level1/level2a
,當在level1App中進行處理時,它的請求路徑被截斷爲/level2a
,當在level2AApp中進行處理時,它的路徑就變成/
了,而相應的PathBase會變爲/level1/level2a
。code
看到這裏,咱們已經知道中間件的基本用法,是時候寫一個真正意義的中間件了。
在 ASP.NET Core 官網上面提供了一個簡單的例子,經過中間件來設置應用的區域信息,代碼以下:
public void Configure(IApplicationBuilder app) { app.Use((context, next) => { var cultureQuery = context.Request.Query["culture"]; if (!string.IsNullOrWhiteSpace(cultureQuery)) { var culture = new CultureInfo(cultureQuery); CultureInfo.CurrentCulture = culture; CultureInfo.CurrentUICulture = culture; } // Call the next delegate/middleware in the pipeline return next(); }); app.Run(async (context) => { await context.Response.WriteAsync( $"Hello {CultureInfo.CurrentCulture.DisplayName}"); }); }
經過這段代碼,咱們能夠經過QueryString的方式設置應用的區域信息。可是這樣的代碼怎樣複用呢?注意,中間件必定要是可複用、方便複用的。咱們來改造這段代碼:
public class RequestCultureMiddleware { private readonly RequestDelegate _next; public RequestCultureMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { //...... // Call the next delegate/middleware in the pipeline await _next(context); } }
這裏定義一個委託,用於執行具體的業務邏輯,而後在Configure中調用這個委託:
app.UseMiddleware<RequestCultureMiddleware>();
這樣仍是不太方便,不像咱們使用app.UseMvc()這麼方便,那麼咱們來添加一個擴展方法,來實現更方便的複用:
public static class RequestCultureMiddlewareExtensions { public static IApplicationBuilder UseRequestCulture( this IApplicationBuilder builder) { return builder.UseMiddleware<RequestCultureMiddleware>(); } }
而後咱們就能夠這樣使用中間件了:
app.UseRequestCulture();
經過委託構造中間件,應用程序在運行時建立這個中間件,並將它添加到管道中。這裏須要注意的是,中間件的建立是單例的,每一箇中間件在應用程序生命週期內只有一個實例。那麼問題來了,若是咱們業務邏輯須要多個實例時,該如何操做呢?請繼續往下看。
經過上面的代碼咱們已經知道了如何編寫一箇中間件,如何方便的複用這個中間件。在中間件的建立過程當中,容器會爲咱們建立一箇中間件實例,而且整個應用程序生命週期中只會建立一個該中間件的實例。一般咱們的程序不容許這樣的注入邏輯。
其實,咱們能夠把中間件理解成業務邏輯的入口,真正的業務邏輯是經過Application Service層實現的,咱們只須要把應用服務注入到Invoke方法中便可。
ASP.NET Core爲咱們提供了這種機制,容許咱們按照請求進行依賴的注入,也就是每次請求建立一個服務。代碼以下:
public class CustomMiddleware { private readonly RequestDelegate _next; public CustomMiddleware(RequestDelegate next) { _next = next; } // IMyScopedService is injected into Invoke public async Task Invoke(HttpContext httpContext, IMyScopedService svc) { svc.MyProperty = 1000; await _next(httpContext); } }
在這段代碼中,CustomMiddleware的實例仍然是單例的,可是IMyScopedService是按照請求進行注入的,每次請求都會建立IMyScopedService的實例,svc對象的生命週期是PerRequest的。
這裏提供一個完整的示例,能夠理解爲一箇中間件的開發模板,方便之後使用的時候參考。整個過程分如下幾步:
代碼以下:
namespace MiddlewareDemo { using Microsoft.AspNetCore.Http; using System.Threading.Tasks; //1.定義並實現業務邏輯 public interface IMyScopedService { int MyProperty { get; set; } } public class MyScopedService : IMyScopedService { public int MyProperty { get; set; } } //2.建立中間件代理類 public class CustomMiddleware { private readonly RequestDelegate _next; public CustomMiddleware(RequestDelegate next) { _next = next; } // IMyScopedService is injected into Invoke public async Task Invoke(HttpContext httpContext, IMyScopedService svc) { svc.MyProperty = 1000; await _next(httpContext); } } } //3.1 添加依賴服務註冊 namespace Microsoft.Extensions.DependencyInjection { using MiddlewareDemo; public static partial class CustomMiddlewareExtensions { /// <summary> /// 添加服務的依賴註冊 /// </summary> public static IServiceCollection AddCustom(this IServiceCollection services) { return services.AddScoped<IMyScopedService, MyScopedService>(); } } } //3.2 建立中間件擴展類 namespace Microsoft.AspNetCore.Builder { using MiddlewareDemo; public static partial class CustomMiddlewareExtensions { /// <summary> /// 使用中間件 /// </summary> public static IApplicationBuilder UseCustom(this IApplicationBuilder builder) { return builder.UseMiddleware<CustomMiddleware>(); } } } //4. 使用中間件 public void ConfigureServices(IServiceCollection services) { services.AddCustom(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseCustom(); }
咱們前面介紹的中間件開發都是基於約定的,可讓咱們快速上手進行開發。同時ASP.NET Core還提供了基於工廠激活的中間件開發方式,咱們能夠經過實現IMiddlewareFactory、IMiddleware接口進行中間件開發。
public class FactoryActivatedMiddleware : IMiddleware { private readonly AppDbContext _db; public FactoryActivatedMiddleware(AppDbContext db) { _db = db; } public async Task InvokeAsync(HttpContext context, RequestDelegate next) { var keyValue = context.Request.Query["key"]; if (!string.IsNullOrWhiteSpace(keyValue)) { _db.Add(new Request() { DT = DateTime.UtcNow, MiddlewareActivation = "FactoryActivatedMiddleware", Value = keyValue }); await _db.SaveChangesAsync(); } await next(context); } }
上面這段代碼演示瞭如何使用基於工廠激活的中間件,在使用過程當中有兩點須要注意:1.須要在ConfigureServices中進行服務註冊;2.在UseMiddleware()方法中不支持傳遞參數。