ASP.NET Core中間件
1.前言
整個(gè)HTTP Request請求跟HTTP Response返回結(jié)果之間的處理流程是一個(gè)請求管道(request pipeline)。而中間件(middleware)則是一種裝配到請求管道以處理請求和響應(yīng)的組件。每個(gè)組件:
- 可選擇是否將請求傳遞到管道中的下一個(gè)組件。
- 可在管道中的下一個(gè)組件前后執(zhí)行工作。
中間件(middleware)處理流程如下圖所示:

2.使用中間件
ASP.NET Core請求管道中每個(gè)中間件都包含一系列的請求委托(request delegates)來處理每個(gè)HTTP請求,依次調(diào)用。請求委托通過使用IApplicationBuilder類型的Run、Use和Map擴(kuò)展方法在Strartup.Configure方法中配置。下面我們通過配置Run、Use和Map擴(kuò)展方法示例來了解下中間件。
2.1 Run
public class Startup
{
public void Configure(IApplicationBuilder app)
{
//第一個(gè)請求委托Run
app.Run(async context =>//內(nèi)嵌匿名方法
{
await context.Response.WriteAsync("Hello, World!");
});
//第二個(gè)請求委托Run
app.Run(async context =>//內(nèi)嵌匿名方法
{
await context.Response.WriteAsync("Hey, World!");
});
}
}響應(yīng)結(jié)果:

由上述代碼可知,Run方法指定為一個(gè)內(nèi)嵌匿名方法(稱為并行中間件,in-line middleware),而內(nèi)嵌匿名方法中并沒有指定執(zhí)行下一個(gè)請求委托,這一個(gè)過程叫管道短路,而該中間件又叫“終端中間件”(terminal middleware),因?yàn)樗柚怪虚g件下一步處理請求。所以在Run第一個(gè)請求委托的時(shí)候就已經(jīng)終止請求,并沒有執(zhí)行第二個(gè)請求委托直接返回Hello, World!輸出文本。而根據(jù)官網(wǎng)解釋,Run是一種約定,有些中間件組件可能會暴露他們自己的Run方法,而這些方法只能在管道末尾處運(yùn)行(也就是說Run方法只在中間件執(zhí)行最后一個(gè)請求委托時(shí)才使用)。
2.2 Use
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
context.Response.ContentType = "text/plain; charset=utf-8";
await context.Response.WriteAsync("進(jìn)入第一個(gè)委托 執(zhí)行下一個(gè)委托之前\r\n");
//調(diào)用管道中的下一個(gè)委托
await next.Invoke();
await context.Response.WriteAsync("結(jié)束第一個(gè)委托 執(zhí)行下一個(gè)委托之后\r\n");
});
app.Run(async context =>
{
await context.Response.WriteAsync("進(jìn)入第二個(gè)委托\(zhòng)r\n");
await context.Response.WriteAsync("Hello from 2nd delegate.\r\n");
await context.Response.WriteAsync("結(jié)束第二個(gè)委托\(zhòng)r\n");
});
}響應(yīng)結(jié)果:

由上述代碼可知,Use方法將多個(gè)請求委托鏈接在一起。而next參數(shù)表示管道中的下一個(gè)委托。如果不調(diào)用next參數(shù)調(diào)用下一個(gè)請求委托則會使管道短路。比如,一個(gè)授權(quán)(authorization)中間件只有通過身份驗(yàn)證之后才能調(diào)用下一個(gè)委托,否則它就會被短路,并返回“Not Authorized”的響應(yīng)。所以應(yīng)盡早在管道中調(diào)用異常處理委托,這樣它們就能捕獲在管道的后期階段發(fā)生的異常。
2.3 Map和MapWhen
- Map:Map擴(kuò)展基于請求路徑創(chuàng)建管道分支。
- MapWhen:MapWhen擴(kuò)展基于請求條件創(chuàng)建管道分支。
Map示例:
public class Startup
{
private static void HandleMapTest1(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 1");
});
}
private static void HandleMapTest2(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 2");
});
}
public void Configure(IApplicationBuilder app)
{
app.Map("/map1", HandleMapTest1);
app.Map("/map2", HandleMapTest2);
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
});
}
}下面表格使用前面的代碼顯示來自http://localhost:5001的請求和響應(yīng)。
請求 | 響應(yīng) |
localhost:5001 | Hello from non-Map delegate. |
localhost:5001/map1 | Map Test 1 |
localhost:5001/map2 | Map Test 2 |
localhost:5001/map3 | Hello from non-Map delegate. |
由上述代碼可知,Map方法將從HttpRequest.Path中刪除匹配的路徑段,并針對每個(gè)請求將該路徑追加到HttpRequest.PathBase。也就是說當(dāng)我們在瀏覽器上輸入map1請求地址的時(shí)候,系統(tǒng)會執(zhí)行map1分支管道輸出其請求委托信息,同理執(zhí)行map2就會輸出對應(yīng)請求委托信息。
MapWhen示例:
public class Startup
{
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)
{
app.MapWhen(context => context.Request.Query.ContainsKey("branch"),
HandleBranch);
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
});
}
}下面表格使用前面的代碼顯示來自http://localhost:5001的請求和響應(yīng)。
請求 | 響應(yīng) |
http://localhost:5001 | Hello from non-Map delegate. <p> |
https://localhost:5001/?branch=master | Branch used = master |
由上述代碼可知,MapWhen是基于branch條件而創(chuàng)建管道分支的,我們在branch條件上輸入master就會創(chuàng)建其對應(yīng)管道分支。也就是說,branch條件上輸入任何一個(gè)字符串條件,都會創(chuàng)建一個(gè)新的管理分支。
而且還Map支持嵌套,例如:
public void Configure(IApplicationBuilder app)
{
app.Map("/level1", level1App => {
level1App.Map("/level2a", level2AApp => {
// "/level1/level2a" processing
});
level1App.Map("/level2b", level2BApp => {
// "/level1/level2b" processing
});
});
}還可同時(shí)匹配多個(gè)段:
public class Startup
{
private static void HandleMultiSeg(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map multiple segments.");
});
}
public void Configure(IApplicationBuilder app)
{
app.Map("/map1/seg1", HandleMultiSeg);
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from non-Map delegate.");
});
}
}3.順序
向Startup.Configure方法添加中間件組件的順序定義了在請求上調(diào)用它們的順序,以及響應(yīng)的相反順序。此排序?qū)τ诎踩浴⑿阅芎凸δ苤陵P(guān)重要。
以下Startup.Configure方法將為常見應(yīng)用方案添加中間件組件:
- 異常/錯(cuò)誤處理(Exception/error handling)
- HTTP嚴(yán)格傳輸安全協(xié)議(HTTP Strict Transport Security Protocol)
- HTTPS重定向(HTTPS redirection)
- 靜態(tài)文件服務(wù)器(Static file server)
- Cookie策略實(shí)施(Cookie policy enforcement)
- 身份驗(yàn)證(Authentication)
- 會話(Session)
- MVC
請看如下代碼:
public void Configure(IApplicationBuilder app)
{
if (env.IsDevelopment())
{
// When the app runs in the Development environment:
// Use the Developer Exception Page to report app runtime errors.
// Use the Database Error Page to report database runtime errors.
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
// When the app doesn't run in the Development environment:
// Enable the Exception Handler Middleware to catch exceptions
// thrown in the following middlewares.
// Use the HTTP Strict Transport Security Protocol (HSTS)
// Middleware.
app.UseExceptionHandler("/Error");
app.UseHsts();
}
// Return static files and end the pipeline.
app.UseStaticFiles();
// Authenticate before the user accesses secure resources.
app.UseAuthentication();
}從上述示例代碼中,每個(gè)中間件擴(kuò)展方法都通過Microsoft.AspNetCore.Builder命名空間在 IApplicationBuilder上公開。但是為什么我們要按照這個(gè)順序去添加中間件組件呢?下面我們挑幾個(gè)中間件來了解下。
- UseExceptionHandler(異常/錯(cuò)誤處理)是添加到管道的第一個(gè)中間件組件。因此我們可以捕獲在應(yīng)用程序調(diào)用中發(fā)生的任何異常。那為什么要將異常/錯(cuò)誤處理放在第一位呢?那是因?yàn)檫@樣我們就不用擔(dān)心因前面中間件短路而導(dǎo)致捕獲不到整個(gè)應(yīng)用程序所有異常信息。
- UseStaticFiles(靜態(tài)文件)中間件在管道中提前調(diào)用,方便它可以處理請求和短路,而無需通過剩余中間組件。也就是說靜態(tài)文件中間件不用經(jīng)過UseAuthentication(身份驗(yàn)證)檢查就可以直接訪問,即可公開訪問由靜態(tài)文件中間件服務(wù)的任何文件,包括wwwroot下的文件。
- UseAuthentication(身份驗(yàn)證)僅在MVC選擇特定的Razor頁面或Controller和Action之后才會發(fā)生。
經(jīng)過上面描述,大家都了解中間件順序的重要性了吧。
4.編寫中間件(重點(diǎn))
雖然ASP.NET Core為我們提供了一組豐富的內(nèi)置中間件組件,但在某些情況下,你可能需要寫入自定義中間件。
4.1中間件類
下面我們自定義一個(gè)查詢當(dāng)前區(qū)域性的中間件:
public class Startup
{
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}");
});
}
}可通過傳入?yún)^(qū)域性參數(shù)條件進(jìn)行測試。例如http://localhost:7997/?culture=zh、http://localhost:7997/?culture=en。
但是如果每個(gè)自定義中間件都在Startup.Configure方法中編寫如上一大堆代碼,那么對于程序來說,將是災(zāi)難性的(不利于維護(hù)和調(diào)用)。為了更好管理代碼,我們應(yīng)該把內(nèi)嵌匿名方法封裝到新建的自定義類(示例自定義RequestCultureMiddleware類)里面去:
public class RequestCultureMiddleware
{
private readonly RequestDelegate _next;
public RequestCultureMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
context.Response.ContentType = "text/plain; charset=utf-8";
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
await _next(context);
}
}通過Startup.Configure方法調(diào)用中間件:
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.UseMiddleware<RequestCultureMiddleware>();
app.Run(async (context) =>
{
await context.Response.WriteAsync(
$"Hello {CultureInfo.CurrentCulture.DisplayName}");
});
}
}4.2中間件擴(kuò)展方法
Startup.Configure方法調(diào)用中間件設(shè)置可以通過自定義的擴(kuò)展方法將其公開(調(diào)用內(nèi)置IApplicationBuilder公開中間件)。示例創(chuàng)建一個(gè)RequestCultureMiddlewareExtensions擴(kuò)展類并通過IApplicationBuilder公開:
public static class RequestCultureMiddlewareExtensions
{
public static IApplicationBuilder UseRequestCulture(this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestCultureMiddleware>();
}
}再通過Startup.Configure方法調(diào)用中間件:
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.UseRequestCulture();
app.Run(async (context) =>
{
await context.Response.WriteAsync(
$"Hello {CultureInfo.CurrentCulture.DisplayName}");
});
}
}響應(yīng)結(jié)果:

通過委托構(gòu)造中間件,應(yīng)用程序在運(yùn)行時(shí)創(chuàng)建這個(gè)中間件,并將它添加到管道中。這里需要注意的是,中間件的創(chuàng)建是單例的,每個(gè)中間件在應(yīng)用程序生命周期內(nèi)只有一個(gè)實(shí)例。那么問題來了,如果我們業(yè)務(wù)邏輯需要多個(gè)實(shí)例時(shí),該如何操作呢?請繼續(xù)往下看。
5.按每次請求創(chuàng)建依賴注入(DI)
在中間件的創(chuàng)建過程中,內(nèi)置的IOC容器會為我們創(chuàng)建一個(gè)中間件實(shí)例,并且整個(gè)應(yīng)用程序生命周期中只會創(chuàng)建一個(gè)該中間件的實(shí)例。通常我們的程序不允許這樣的注入邏輯。其實(shí),我們可以把中間件理解成業(yè)務(wù)邏輯的入口,真正的業(yè)務(wù)邏輯是通過Application Service層實(shí)現(xiàn)的,我們只需要把應(yīng)用服務(wù)注入到Invoke方法中即可。ASP.NET Core為我們提供了這種機(jī)制,允許我們按照請求進(jìn)行依賴的注入,也就是每次請求創(chuàng)建一個(gè)服務(wù)。示例:
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);
}
}
public static class CustomMiddlewareExtensions
{
public static IApplicationBuilder UseCustomMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<CustomMiddleware>();
}
}
public interface IMyScopedService
{
void MyProperty(decimal input);
}
public class MyScopedService : IMyScopedService
{
public void MyProperty(decimal input)
{
Console.WriteLine("MyProperty is " + input);
}
}
public void ConfigureServices(IServiceCollection services)
{
//注入DI服務(wù)
services.AddScoped<IMyScopedService, MyScopedService>();
}響應(yīng)結(jié)果:

到此這篇關(guān)于ASP.NET Core中間件的文章就介紹到這了。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
一步步打造漂亮的新聞列表(無刷新分頁、內(nèi)容預(yù)覽)第一步
新聞列表是信息管理系統(tǒng)中最常見的,也是最簡單的,一些簡單的新聞列表就是一個(gè)table,然后里面循環(huán)寫入數(shù)據(jù)2010-07-07
ASP.NET系統(tǒng)關(guān)鍵字及保留字列表整理
ASP.NET系統(tǒng)關(guān)鍵字及保留字列表,大家在寫程序的時(shí)候一定要避免使用,免得引起不需要的麻煩2012-10-10
.NET?6更新使.NET生態(tài)系統(tǒng)蛻變
微軟正式發(fā)布.NET最新長期支持版本.NET?6,這個(gè)版本的更新重點(diǎn),除了C#和F#都有許多語言功能改進(jìn)之外,.NET?6終于集大成,成為跨瀏覽器、云計(jì)算、桌面、物聯(lián)網(wǎng)和移動應(yīng)用程序的統(tǒng)一平臺,性能也獲得大幅提升,并且更完整支持Arm642022-01-01
ASP.Net?Core?MVC基礎(chǔ)系列之獲取配置信息
這篇文章介紹了ASP.Net?Core?MVC獲取配置信息的方法,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-02-02
ASP.NET?Core中的Configuration配置一
這篇文章介紹了ASP.NET?Core中的Configuration配置,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04
ASP.NET中 RadioButtonList 單選按鈕組控件的使用方法
本文主要簡單介紹RadioButtonList控件的常見屬性和使用方法,希望對大家有所幫助。2016-04-04

