ASP.NET Core基本原理(2)-中間件

ASP.NET Core基本原理(2)-中間件

原文鏈接:Application Startup

什么是中間件

中間件是裝配到應用管道中用來處理請求和響應的軟件組件。管道中的每一個組件都可以選擇是否將請求移交給下一個組件,并且可以在管道中調用下一個組件之前或者之后執行指定的操作。請求委托被用于構建請求管道。請求委托會處理每一個HTTP請求。

請求委托通過在傳遞給Startup類中的Configure方法的IApplicationBuilder類型上使用Run,Map,Use擴展方法進行配置。一個單獨的請求委托可以被指定為一個內嵌的匿名方法,或定義在一個可重用的類中。這些可重用的類就是中間件中間件組件。每個請求管道中的中間件負責調用管道中的下一個組件,或者在適當的時候將調用鏈短路。

通過IApplicationBuilder創建中間件管道

ASP.NET請求管道由一系列的請求委托所組成,一個接著一個被調用,如圖所示,執行線程跟隨著黑色箭頭:

每一個委托在下一個委托調用之前或之后都有機會去執行操作。任何委托都可以選擇停止傳遞請求到下一個委托,而自己處理該請求。這就是請求管道的短路,這種設計可以避免一些不必要的工作。例如,一個授權(Authorization)中間件只有在請求通過身份驗證之后才能調用下一個委托;否則它就會被短路并返回"Not Authorized"的響應。異常處理委托必須在管道的早期被調用,這樣它們就可以捕獲到發生在管道內更深層次的異常。

打開默認的網站模板,Configure方法添加了如下中間件組件:

  1. 錯誤處理(針對開發環境和非開發環境)
  2. 靜態文件服務器
  3. 身份驗證
  4. MVC
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
        app.UseBrowserLink();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseStaticFiles();

    app.UseIdentity();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

上述代碼(在非開發環境),UseExceptionHandler是第一個被添加到管道的中間件,因此會捕獲之后調用中出現的任何異常。

Static File Module提供了無需授權檢查的功能,由它服務的任何文件,包含在那些位于wwwroot文件夾中的文件都是可被公開訪問的。如果你想讓基于授權來提供這些文件:

  1. 將它們存放在wwwroot文件夾之外以及靜態文件中間件可以訪問的任何目錄。
  2. 交給控制器方法,通過返回一個FileResult表示授權被應用的地方。

被靜態文件模塊處理的請求會在管道中被短路。如果請求不是通過靜態文件模塊來處理,那么它會被傳給Identity module來執行身份驗證。如果請求未通過驗證,則管道將被短路。否則,將會調用管道的最后一站-MVC框架。

注:你添加中間件的順序通常會影響它們對請求產生影響的順序,然后在響應時會以相反的順序。這對應用程序的安全、性能和功能都非常關鍵。在上面的代碼中,Static File Module在管道的早期被調用,這樣可以及時短路,避免了請求進行到不必要的組件中。身份驗證中間件在任何需要身份驗證的處理請求之前被添加進來。異常處理必須在其它中間件之前被注冊以便捕獲其它組件的異常。

最簡單的ASP.NET應用設置一個簡單的請求委托來處理所有的請求。這樣就不是一個真實的請求管道,通過調用一個簡單的匿名函數來響應每一個HTTP請求。

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

App.Run委托會終止管道,下面的盒子中,只有第一個委托會被運行。

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

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

將多個請求委托鏈接在一起,next參數表示管道內的下一個委托。你可以通過不調用next參數來終止(短路)管道。你通常可以在調用下一個委托之前和之后執行操作:

public void ConfigureLogInline(IApplicationBuilder app, ILoggerFactory loggerfactory)
{
    loggerfactory.AddConsole(minLevel: LogLevel.Information);
    var logger = loggerfactory.CreateLogger(_environment);
    app.Use(async (context, next) =>
    {
        logger.LogInformation("Handling request.");
        await next.Invoke();
        logger.LogInformation("Finished handling request.");
    });

    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello from " + _environment);
    });
}

當應用程序運行的環境設置成LogInline時,ConfigureLogInline方法會被調用。

在上述例子中,調用await next.Invoke()將會調用下一個委托await context.Response.WriteAsync("Hello from " + _environment);。客戶端會收到所期望的響應("Hello from LogInline")。

Run、Map和Use

你可以通過Run,MapUse來配置HTTP管道。Run方法將會短路管道(因為它不會調用next請求委托),因此Run方法應該只在管道結尾被調用。Run方法是一種慣例,有些中間件也會暴露它們自己的Run[Middleware]方法,你也必須在管道的末尾進行運行。下面兩個中間件是相同的,其中一個使用Use版本的沒有使用到next參數:

public void ConfigureEnvironmentOne(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello from " + _environment);
    });
}

public void ConfigureEnvironmentTwo(IApplicationBuilder app)
{
    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("Hello from " + _environment);
    });
}

Map*擴展被用于分支管道,當前的實現支持基于請求路徑或使用謂詞進行分支。Map擴展方法被用于匹配基于請求路徑的請求委托。Map只接受一個路徑和配置了一個單獨中間件的管道的功能。在下面的例子中,任何基于/maptest基本路徑的請求都會被HandleMapTest方法中所配置的管道所處理。

private static void HandleMapTest(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test Successful");
    });
}

public void ConfigureMapping(IApplicationBuilder app)
{
    app.Map("/maptest", HandleMapTest);

}

當使用了Map,每一個請求所匹配的路徑段將從HttpRequest.Path中移除,并附加到HttpRequest.PathBase中。

除了基于路徑的映射外,MapWhen方法還支持基于謂詞的中間件分支,允許以一種非常靈活的方式構造單獨的管道。任何Func<HttpContext, bool>類型的謂詞都可被用于將請求映射到一個新的管道分支。在下面的例子中,一個簡單的謂詞被用來檢測字符變量branch是否存在:

private static void HandleBranch(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Branch used.");
    });
}

public void ConfigureMapWhen(IApplicationBuilder app)
{
    app.MapWhen(context => {
        return context.Request.Query.ContainsKey("branch");
    }, HandleBranch);

    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello from " + _environment);
    });
}

上述配置中,任何包含branch的查詢字符串的請求將使用定義在HandleBranch方法中的管道(將得到"Branch used."的響應)。其它請求(不包含branch的查詢字符串的請求)將被await context.Response.WriteAsync("Hello from " + _environment);所定義的委托所處理。

內置中間件

ASP.NET帶來了下列中間件組件:

中間件 ?描述 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
身份驗證(Authentication) ?提供身份驗證支持
跨域資源共享(CORS) ?配置跨域資源共享
路由(Routing) ?定義和約定請求路由
會話(Session) ?對管理用戶會話提供支持
靜態文件(Static Files) ??對服務靜態文件和目錄瀏覽提供支持

個人博客

我的個人博客

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容