ASP.NET 5中Middleware的基本用法
在ASP.NET 5里面引入了OWIN的概念,大致意思是將網站部署、服務器、中間組件以及應用分離開,這里提到的Middleware就是中間組件。
這里引用asp.net網站的介紹圖
Middleware的作用有點類似於httpmodule,服務器接收到的請求都會傳遞到各個Middleware,多個Middleware形成一個處理管道。
由於不針對於特定的請求,所以Middleware的執行范圍是在Application之外,這種模式很適合處理日志記錄,權限驗證和事務處理,也可以理解為AOP的一種實現方式。
在ASP.NET 5里面,ASP.NET Identity就提供了用於權限認證的Middleware,在Startup類里面調用UseXXX的方法就可以加入支持不同權限驗證的Middleware,具體可以參考ASP.NET Identity的介紹。
除了Middleware除了第三方組件提供的之外,我們還可以實現自定義的Middleware。
在ASP.NET 5里面,我們自定義一個Middleware需要繼承類OwinMiddleware,如下形式

public class LoggerMiddleware : OwinMiddleware { private readonly ILog _logger; public LoggerMiddleware(OwinMiddleware next, ILog logger) : base(next) { _logger = logger; } public override async Task Invoke(IOwinContext context) { _logger.LogInfo("Middleware begin"); await this.Next.Invoke(context); _logger.LogInfo("Middleware end"); } }
構造方法傳入了下一個Middleware的實例,用於在執行完當前Middleware之后,執行下一個Middleware。
然后在Startup的Configuration方法里調用
public class Startup { public void Configuration(IAppBuilder app) { app.Use<LoggerMiddleware>(new TraceLogger()); } }
以上是ASP.NET 5中Middleware的使用簡述。
————————————————————————————————————————————————————————————————————
ASP.NET Core的Middleware的用法
而在最新版的ASP.NET Core中Middleware的使用方式有了變化,下面通過一步步來看如何在ASP.NET Core中創建自定義的Middleware,描述一下新版中如何是定義Middleware以及多個Middleware的調用
1.創建一個空白的ASP.NET Core Web項目
2.然后在項目的根目錄創建一個Middlewares文件夾
3. 接下來將創建多個不同功能的Middleware
a.創建輸出內容的Middleware
在沒有使用任何Middleware時,這時候的項目能運行,運行之后只會輸出Hello World。那是因為在Startup的Configure方法里,有一個默認的Run
app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); });
不管輸入什么url路徑,都只會返回Hello World,這也可以理解為一個Middleware,是一個兜底的處理。
其實在ASP.NET Core里面,MVC的引入也是通過Middleware的形式實現的,在引入MVC的組件后,可以通過app.UseMvc()方法引入組件,這里不詳述。
回到即將創建的自定義的Middleware,在新版中創建自定義的Middleware,不需要繼承OwinMiddleware類,也沒有提供這類的定義。
自定義的Middleware看起來跟普通的class沒什么區別,下面在Middlewares文件夾新建一個ContentMiddleware類,然后編寫代碼如下

using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; namespace CustomMiddleware.Middlewares { public class ContentMiddleware { private RequestDelegate _nextDelegate; public ContentMiddleware(RequestDelegate nextDelegate) { _nextDelegate = nextDelegate; } public async Task Invoke(HttpContext httpContext) { if (httpContext.Request.Path.ToString().ToLower() == "/middleware") { await httpContext.Response.WriteAsync( "Handled by content middleware", Encoding.UTF8); } else { await _nextDelegate.Invoke(httpContext); } } } }
從這個類我們看到,沒有繼承任何類或者實現任何接口,但是方法的簽名類似於之前版本的實現。
在.NET Core版本中,自定義Middleware提供一個包含RequestDelegate 類型參數的構造方法,另外提供如下格式的Invoke方法簽名即可
public async Task Invoke(HttpContext httpContext)。
其中RequestDelegate實例表示的是下一個執行的Middleware,這點跟OwinMiddleware類似。
Invoke方法接收的是HttpContext類似的參數,相比之前OwinConext,感覺這樣更加直接。
這個ContentMiddleware的實現是,判斷請求的路徑如果是/middleware,就返回內容,否則交給下一個組件處理。
定義完成之后,在Startup的Configure方法中,在app.Run()代碼之前加入下面的代碼
app.UseMiddleware<ContentMiddleware>();
運行之后輸入http://localhost:53814/middleware就會看到瀏覽器輸出了以下內容
”Handled by content middleware“
如果輸入其他url還是會顯示”Hello world”。
b.創建可終止后續請求的Middleware
剛才提供Middleware的作用可用於校驗請求內容,然后根據請求信息作出是否繼續處理請求的操作。下面我們創建一個Middleware,判斷如果是IE瀏覽器就返回403狀態碼,那么后續的請求將不被執行。
在Middlewares文件夾創建ValidateBrowserMiddleware類,填入代碼如下

using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; namespace CustomMiddleware.Middlewares { public class ValidateBrowserMiddleware { private readonly RequestDelegate _nextDelegate; public ValidateBrowserMiddleware(RequestDelegate nextDelegate) { _nextDelegate = nextDelegate; } public async Task Invoke(HttpContext httpContext) { if (httpContext.Request.Headers["User-Agent"] .Any(h => h.ToLower().Contains("trident"))) { httpContext.Response.StatusCode = 403; } else { await _nextDelegate.Invoke(httpContext); } } } }
同樣在Startup的Configure方法加入引用,把這個組件放入到其他Middleware引用之前。

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseMiddleware<ValidateBrowserMiddleware>(); app.UseMiddleware<ContentMiddleware>(); app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); }
Middleware的引用順序很關鍵,最先引用的最先處理Request,並且是最后處理Response,是一種后進先出的處理順序。
這時候運行網站,如果是在IE瀏覽器下訪問,那么會返回403的錯誤信息(禁止訪問),后續的請求都不會執行,提早中斷了請求。
c.修改請求上下文的Middleware
這里說的上下文是Httpcontext,這里有一個Items屬性,可以存放一些數據用於當前的請求。
在Middlewares文件夾創建EditContextMiddleware,修改代碼如下

using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; namespace CustomMiddleware.Middlewares { public class EditContextMiddleware { private readonly RequestDelegate _nextDelegate; public EditContextMiddleware(RequestDelegate nextDelegate) { _nextDelegate = nextDelegate; } public async Task Invoke(HttpContext httpContext) { httpContext.Items["IEBrowser"] = httpContext.Request.Headers["User-Agent"] .Any(v => v.ToLower().Contains("trident")); await _nextDelegate.Invoke(httpContext); } } }
這個Middleware在httpContext中加入了一個是否IEBrowser的數據項,然后接着執行后續的組件,也就是說后續的組件可以在httpContext中獲取這個值。
基於這個改動我們可以修改ValidateBrowserMiddleware的Invoke方法的實現
public async Task Invoke(HttpContext httpContext) { var isIEBrowser = httpContext.Items["IEBrowser"] as bool?; if (isIEBrowser.GetValueOrDefault()) { httpContext.Response.StatusCode = 403; } else { await _nextDelegate.Invoke(httpContext); } }
這個改動的一個前提是EditContextMiddleware的引入必須在ValidateBrowserMiddleware之前,如下

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseMiddleware<EditContextMiddleware>(); app.UseMiddleware<ValidateBrowserMiddleware>(); app.UseMiddleware<ContentMiddleware>(); app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); }
d.最后一個是修改Repsonse的Middleware
之前的都是處理Request或者修改HttpContext內容,根據上一個Middleware的處理結果來修改Response也是常見的做法。
在Middlewares文件夾創建類StatusCodeMiddleware,代碼內容如下

public class StatusCodeMiddleware { private readonly RequestDelegate _requestDelegate; public StatusCodeMiddleware(RequestDelegate requestDelegate) { _requestDelegate = requestDelegate; } public async Task Invoke(HttpContext httpContext) { await _requestDelegate.Invoke(httpContext); switch (httpContext.Response.StatusCode) { case 403: httpContext.Response.StatusCode = 200; await httpContext.Response.WriteAsync("IE not supported", Encoding.UTF8); break; case 404: await httpContext.Response.WriteAsync("No page found", Encoding.UTF8); break; } } }
上述代碼的根據不同的Http狀態碼返回Response輸出,如果是IE訪問那么會顯示一段文字,而不是一個403錯誤頁。
由於處理的是Repsonse的內容,因此是在Invoke方法之后執行,並且這個Middleware必須放在第一位,這樣才能確保這Middleware處理的是最后一個Repsonse,因為Repsonse的處理順序跟Request的處理順序是相反的。
StatusCodeMiddleware的引用代碼:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseMiddleware<StatusCodeMiddleware>(); app.UseMiddleware<EditContextMiddleware>(); app.UseMiddleware<ValidateBrowserMiddleware>(); app.UseMiddleware<ContentMiddleware>(); app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); }
至此,已經使用了4個自定義的Middleware,對於一個請求每個middleware的處理順序如下
以上是ASP.NET Core中使用自定義Middleware的基本用法,基於這個實現我們做更多有意義的事情,比如日志記錄、事務處理等。
上述例子的代碼在如下路徑
https://github.com/shenba2014/AspDotNetCoreMvcExamples/tree/master/CustomMiddleware