ASP.NET Core筆記(5) - 中間件

  • 中間件管道模型
  • 中間件的配置
  • 自定義中間件

中間件是一類裝配在應用管道的代碼,負責處理請求和響應。每一箇中間件均可在管道中的下一個組件先後執行工做,並選擇是否將請求傳遞到管道中的下一個中間件。在Startup.Configure方法中能夠進行中間件的裝配。app

中間件管道模型

中間件管道模型以下圖所示:
中間件管道模型async

ASP.NET Core請求管道包含一系列請求委託,沿黑色箭頭依次被調用執行,每一個委託都可在下一個委託先後執行操做。這種模型也被形象地稱爲「俄羅斯套娃」。函數

一箇中間件能夠是匿名方法的顯示嵌入到管道中,也能夠封裝爲單獨的類便於重用,嵌入式的中間件就像這樣:ui

public void Configure(IApplicationBuilder app)
{
	app.Run(async context =>
	{
		await context.Response.WriteAsync("Hello, World!");
	});
}

中間件的配置

配置中間件會使用到三個擴展方法:this

  • Use
  • Run
  • Map

Use

Use用來將多箇中間件按添加順序連接到一塊兒:code

app.Use(async (context, next) =>
{
	await context.Response.WriteAsync("middleware1 begin\r\n");
	await next.Invoke();
	await context.Response.WriteAsync("middleware1 end\r\n");
});

app.Use(async (context, next) =>
{
	await context.Response.WriteAsync("middleware2 begin\r\n");
	await next.Invoke();
	await context.Response.WriteAsync("middleware2 end\r\n");
});

app.Run(async context =>
{
	await context.Response.WriteAsync("end of pipeline.\r\n");
});

這三個中間件配置到管道後,輸出的結果與管道模型的圖示是一致的:中間件

middleware1 begin
middleware2 begin
end of pipeline.
middleware2 end
middleware1 end

能夠看到除了最後一箇中間件,前面的中間件都調用了await next.Invoke(),next參數表示管道中的下一個委託,若是不調用 next,後面的中間件就不知執行,這稱爲管道的短路。
一般中間件都應該自覺調用下一個中間件,但有的中間件會故意形成短路,好比受權中間件、靜態文件中間件等。blog

Run

Run的委託中沒有next參數,這就意味着它會稱爲最後一箇中間件(終端中間件),此外能夠故意短路的Use委託也可能會成爲終端中間件。ip

Map,提供了建立管道分支的能力

Map擴展用做約定來建立管道分支,會基於給定請求路徑的匹配項來建立請求管道分支,若是請求路徑以給定路徑開頭,則執行分支。字符串

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>");
	});
}

這段代碼爲管道建立了三個分支:

URL Response
localhost:1234 Hello from non-Map delegate.
localhost:1234/map1 Map Test 1
localhost:1234/map2 Map Test 2
Map還支持嵌套
app.Map("/level1", level1App => {
    level1App.Map("/level2a", level2AApp => {
        // "/level1/level2a" processing
    });
    level1App.Map("/level2b", level2BApp => {
        // "/level1/level2b" processing
    });
});
MapWhen,知足條件時建立分支
app.MapWhen(context => context.Request.Query.ContainsKey("branch"),
                               branch => {
    });

此外,UseWhen也能夠根據條件建立分支,它與MapWhen的區別在於,使用UseWhen建立的分支若是不包含終端中間件,則會從新匯入主管道。

自定義中間件

一般使用內置的中間件可知足大多數場合,但若是有須要也能夠建立自定義中間件。
假設有這樣一個嵌入式中間件,能夠經過查詢字符串設置當前請求的區域,好比https://localhost:5000/?culture=zh-cn,會將CurrentCulture設置爲Chinese Simplified。如今要將其封裝爲可重用的獨立的中間件。

public void Configure(IApplicationBuilder app)
{
	app.Use(async (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
		await next();
	});

	app.Run(async (context) =>
	{
		await context.Response.WriteAsync(
			$"Hello {CultureInfo.CurrentCulture.DisplayName}");
	});

}

在開始封裝前,先了解一下對中間件的要求:

  • 中間件必須具備類型爲RequestDelegate的參數的公共構造函數,以前代碼中看到next.Invoke(),其中next就是下一個中間件的RequestDelegate;
  • 名爲 Invoke 或 InvokeAsync 的公共方法。並且這個方法的返回類型爲Task,且第一個參數的必須是HttpContext,其它的參數由DI容器解析。

基於上述要求,編寫的中間件爲:

public class RequestCultureMiddleware
{
    private readonly RequestDelegate _next;

    public RequestCultureMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        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.Invoke(context);
        await _next(context);
    }
}

而後就可使用了:

app.UseMiddleware<RequestCultureMiddleware>();

還能夠進一步封裝爲IApplicationBuilder的擴展方法:

public static class RequestCultureMiddlewareExtensions
{
    public static IApplicationBuilder UseRequestCulture(
        this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<RequestCultureMiddleware>();
    }
}

而後就能夠像內置的中間件同樣了:

app.UseRequestCulture();
相關文章
相關標籤/搜索