中介層會組成應用程式的 pipeline,用來處理 Web 的請求和回應。每個中介層元件都可以:
請求委任 (request delegates) 會用來建立請求 pipeline,而且會處理每個 HTTP 請求。
請求委任是透過 IApplicationBuilder
類別的 Run
、Map
和 Use
三個擴充方法來設定。每個請求委任都可以寫成匿名方法或者獨立成一個類別。
請求 pipeline 是由請求委任的序列所組成,會一個接著一個被呼叫。
每一個委任都可以在呼叫下一個委任之前或之後執行某些工作。在上圖中可以看到,呼叫下個委任前的邏輯是針對 Request 處理;呼叫後則是針對 Response 處理。也可以不把請求傳到下個委任中,讓請求 pipeline 發生短路(short-circuiting),如此可以讓應用程式不需要執行多餘的工作。例如用來做授權驗證 (authorization) 的中介層中,應該只有通過驗證後才需要呼叫下一個委任,否則就要發生短路,傳回 403 狀態。
新增一個空白的 Web 應用程式,可以看到最簡單的請求委任設定範例。起始類別中,只設定一個委任來處理所有請求。第一個 Run
就會終止請求 pipeline。
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Hello, World!");
});
}
}
在下面的範例中,只有第一個委任會被執行,因為在這個委任中沒有呼叫下一個委任。
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Hello, World!");
});
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from 2nd delegate.");
});
}
}
如果要把多個委任串在一起,需要使用 Use
方法。next
參數表示 pipeline 中的下個委任。如果不呼叫下一個委任,便會發生短路。
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
// 呼叫委任前
await next.Invoke();
// 呼叫委任後
});
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from 2nd delegate.");
});
}
}
Startup.Configure
方法中新增中介層元件的順序代表請求時呼叫中介層元件的順序及回應的相反順序。對安全性、效能與商業邏輯而言,此順序相當重要。
由 MVC 範本建立的專案中,在 Startup.Configure
方法中新增中介層元件的順序如下:
public void Configure(IApplicationBuilder app)
{
if (_env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
通常會第一個新增例外處理的中介層,才能讓此中介層攔截到所有後續委任中發生的例外狀況。
我們會用 Run
、Map
和 Use
三個擴充方法來設定請求 pipeline。Run
方法會讓 pipeline 發生短路,因為此方法中沒有提供 next
參數,有些中介層元件會將 Run{Middleware}
方法曝露出來。Use
方法如果沒有呼叫下一個元件,也會發生短路。
在下面兩個範例中,Run
跟 Use
的效果是一樣的,因為在 Use
中沒有使用 next
參數來呼叫下一個中介層。
public void ConfigureWithRun(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from Run");
});
}
public void ConfigureWithUse(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("Hello from Use");
});
}
Map
方法則是用來為請求 pipeline 建立分支。有 Map
跟 MapWhen
兩個分法可以使用。
Map
方法第一個參數是指定請求路徑的開頭,如果符合就會執行第二個參數傳入的動作。
public class Startup
{
private static void HandleMapIronman(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Ironman");
});
}
public void Configure(IApplicationBuilder app)
{
app.Map("/ironman", HandleMapIronman);
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from non-Map delegate");
});
}
}
使用上面的起始類別後,來自 http://localhost:44315
的請求及回應的對應表格如下:
Request | Response |
---|---|
/ | Hello from non-Map delegate |
/ironman | Map Ironman |
/another-branch | Hello from non-Map delegate |
除了用路徑來判斷分支外,也有一個 MapWhen
方法可以自定判斷邏輯。此方法的第一個參數是 Func<HttpContext,Boolean>
,回傳的 bool
用來判斷 pipeline 是否要走這個分支。
public class Startup
{
private static void HandleBranch(IApplicationBuilder app)
{
app.Run(async context =>
{
var branchName = context.Request.Query["branch"];
await context.Response.WriteAsync($"Branch Name = {branchName}");
});
}
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");
});
}
}
使用上面的起始類別後,來自 http://localhost:44315
的請求及回應的對應表格如下:
Request | Response |
---|---|
/ | Hello from non-Map delegate |
/?branch=ironman | Branch Name = ironman |
Map
也支援巢狀設定,例如:
app.Map("/level1", level1App => {
level1App.Map("/level2a", level2AApp => {
// "/level1/level2a" processing
});
level1App.Map("/level2b", level2BApp => {
// "/level1/level2b" processing
});
});
今天說明 ASP.NET Core 中介層的觀念,明天我們再來實作中介層~