ASP.NET Core 3.1 中間件


參考微軟官方文檔 :

https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware/?view=aspnetcore-3.1

一、ASP.NET Core 中間件簡介

1.1 定義:中間件是一種裝配到應用管道以處理請求和響應的軟件。

每個組件:

  • 選擇是否將請求傳遞到管道中的下一個組件。
  • 可在管道中的下一個組件前后執行工作。

請求委托用於生成請求管道。 請求委托處理每個 HTTP 請求。

使用 Run 、Map 和 Use 擴展方法來配置請求委托。 可將一個單獨的請求委托並行指定為匿名方法(稱為並行中間件),或在可重用的類中對其進行定義。 這些可重用的類和並行匿名方法即為中間件,也叫中間件組件。 請求管道中的每個中間件組件負責調用管道中的下一個組件,或使管道短路。 當中間件短路時,它被稱為“終端中間件”,因為它阻止中間件進一步處理請求。

將 HTTP 處理程序和模塊遷移到 ASP.NET Core 中間件介紹了 ASP.NET Core 和 ASP.NET 4.x 中請求管道之間的差異,並提供了更多的中間件示例。

1.2 使用 IApplicationBuilder 創建中間件管道

ASP.NET Core 請求管道包含一系列請求委托,依次調用。 下圖演示了這一概念。 沿黑色箭頭執行。

請求處理模式顯示請求到達、通過三個中間件進行處理以及響應離開應用。

每個委托均可在下一個委托前后執行操作。 應盡早在管道中調用異常處理委托,這樣它們就能捕獲在管道的后期階段發生的異常。

盡可能簡單的 ASP.NET Core 應用設置了處理所有請求的單個請求委托。 這種情況不包括實際請求管道。 調用單個匿名函數以響應每個 HTTP 請求。

 

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

 

用 Use 將多個請求委托鏈接在一起。 next 參數表示管道中的下一個委托 可通過不調用 next 參數使管道短路。 通常可在下一個委托前后執行操作,如以下示例所示:

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            // Do work that doesn't write to the Response.
            await next.Invoke();
            // Do logging or other work that doesn't write to the Response.
        });

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from 2nd delegate.");
        });
    }
}

 

當委托不將請求傳遞給下一個委托時,它被稱為“讓請求管道短路”。 通常需要短路,因為這樣可以避免不必要的工作。 例如,靜態文件中間件可以處理對靜態文件的請求,並讓管道的其余部分短路,從而起到終端中間件的作用。 如果中間件添加到管道中,且位於終止進一步處理的中間件前,它們仍處理 next.Invoke 語句后面的代碼。 不過,請參閱下面有關嘗試對已發送的響應執行寫入操作的警告。

 警告

在向客戶端發送響應后,請勿調用 next.Invoke。 響應啟動后,針對 HttpResponse 的更改將引發異常。 例如,設置標頭和狀態代碼更改將引發異常。 調用 next 后寫入響應正文:

  • 可能導致違反協議。 例如,寫入的長度超過規定的 Content-Length
  • 可能損壞正文格式。 例如,向 CSS 文件中寫入 HTML 頁腳。

HasStarted 是一個有用的提示,指示是否已發送標頭或已寫入正文。

Run 委托不會收到 next 參數。 第一個 Run 委托始終為終端,用於終止管道。 Run 是一種約定。 某些中間件組件可能會公開在管道末尾運行的 Run[Middleware] 方法: 

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            // Do work that doesn't write to the Response.
            await next.Invoke();
            // Do logging or other work that doesn't write to the Response.
        });

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from 2nd delegate.");
        });
    }
}

 

在前面的示例中,Run 委托將 "Hello from 2nd delegate." 寫入響應,然后終止管道。 如果在 Run 委托之后添加了另一個 Use 或 Run 委托,則不會調用該委托。

中間件順序

下圖顯示了 ASP.NET Core MVC 和 Razor Pages 應用的完整請求處理管道。 你可以在典型應用中了解現有中間件的順序,以及在哪里添加自定義中間件。 你可以完全控制如何重新排列現有中間件,或根據場景需要注入新的自定義中間件。

ASP.NET Core 中間件管道

上圖中的“終結點”中間件為相應的應用類型(MVC 或 Razor Pages)執行篩選器管道。

ASP.NET Core 篩選器管道

向 Startup.Configure 方法添加中間件組件的順序定義了針對請求調用這些組件的順序,以及響應的相反順序。 此順序對於安全性、性能和功能至關重要。

下面的 Startup.Configure 方法按照典型的建議順序增加與安全相關的中間件組件:

 

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();
    // app.UseCookiePolicy();

    app.UseRouting();
    // app.UseRequestLocalization();
    // app.UseCors();

    app.UseAuthentication();
    app.UseAuthorization();
    // app.UseSession();
    // app.UseResponseCompression();
    // app.UseResponseCaching();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}

 

在上述代碼中:

  • 在使用單個用戶帳戶創建新的 Web 應用時未添加的中間件已被注釋掉。
  • 並非所有中間件都需要准確按照此順序運行,但許多中間件必須遵循這個順序。 例如:
    • UseCorsUseAuthentication 和 UseAuthorization 必須按照上述順序運行。
    • 由於此錯誤UseCors 當前必須在 UseResponseCaching 之前運行。

在某些場景下,中間件將具有不同的排序。 例如,高速緩存和壓縮排序是特定於場景的,存在多個有效的排序。 例如:

app.UseResponseCaching();
app.UseResponseCompression();

使用前面的代碼,可以通過緩存壓縮的響應來保存 CPU,但你可能最終會使用不同的壓縮算法(如 gzip 或 brotli)來緩存資源的多個表示形式。

以下排序結合了靜態文件以允許緩存壓縮的靜態文件:

app.UseResponseCaching();
app.UseResponseCompression();
app.UseStaticFiles();

以下 Startup.Configure 方法將為常見應用方案添加中間件組件:

  1. 異常/錯誤處理
    • 當應用在開發環境中運行時:
      • 開發人員異常頁中間件 (UseDeveloperExceptionPage) 報告應用運行時錯誤。
      • 數據庫錯誤頁中間件報告數據庫運行時錯誤。
    • 當應用在生產環境中運行時:
      • 異常處理程序中間件 (UseExceptionHandler) 捕獲以下中間件中引發的異常。
      • HTTP 嚴格傳輸安全協議 (HSTS) 中間件 (UseHsts) 添加 Strict-Transport-Security 標頭。
  2. HTTPS 重定向中間件 (UseHttpsRedirection) 將 HTTP 請求重定向到 HTTPS。
  3. 靜態文件中間件 (UseStaticFiles) 返回靜態文件,並簡化進一步請求處理。
  4. Cookie 策略中間件 (UseCookiePolicy) 使應用符合歐盟一般數據保護條例 (GDPR) 規定。
  5. 用於路由請求的路由中間件 (UseRouting)。
  6. 身份驗證中間件 (UseAuthentication) 嘗試對用戶進行身份驗證,然后才會允許用戶訪問安全資源。
  7. 用於授權用戶訪問安全資源的授權中間件 (UseAuthorization)。
  8. 會話中間件 (UseSession) 建立和維護會話狀態。 如果應用使用會話狀態,請在 Cookie 策略中間件之后和 MVC 中間件之前調用會話中間件。
  9. 用於將 Razor Pages 終結點添加到請求管道的終結點路由中間件(帶有 MapRazorPages 的 UseEndpoints)。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseCookiePolicy();
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseSession();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

 

在前面的示例代碼中,每個中間件擴展方法都通過 Microsoft.AspNetCore.Builder 命名空間在 IApplicationBuilder 上公開。

UseExceptionHandler 是添加到管道的第一個中間件組件。 因此,異常處理程序中間件可捕獲稍后調用中發生的任何異常。

盡早在管道中調用靜態文件中間件,以便它可以處理請求並使其短路,而無需通過剩余組件。 靜態文件中間件不提供授權檢查。 可公開訪問由靜態文件中間件服務的任何文件,包括 wwwroot 下的文件。 若要了解如何保護靜態文件,請參閱 ASP.NET Core 中的靜態文件

如果靜態文件中間件未處理請求,則請求將被傳遞給執行身份驗證的身份驗證中間件 (UseAuthentication)。 身份驗證不使未經身份驗證的請求短路。 雖然身份驗證中間件對請求進行身份驗證,但僅在 MVC 選擇特定 Razor Page 或 MVC 控制器和操作后,才發生授權(和拒絕)。

以下示例演示中間件排序,其中靜態文件的請求在響應壓縮中間件前由靜態文件中間件進行處理。 使用此中間件順序不壓縮靜態文件。 可以壓縮 Razor Pages 響應。

 
public void Configure(IApplicationBuilder app)
{
    // Static files aren't compressed by Static File Middleware.
    app.UseStaticFiles();

    app.UseRouting();

    app.UseResponseCompression();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

 

對於單頁應用程序 (SPA),SPA 中間件 UseSpaStaticFiles 通常是中間件管道中的最后一個。 SPA 中間件處於最后的作用是:

  • 允許所有其他中間件首先響應匹配的請求。
  • 允許具有客戶端側路由的 SPA 針對服務器應用無法識別的所有路由運行。

若要詳細了解 SPA,請參閱 React 和 Angular 項目模板的指南。

轉接頭中間件順序

轉接頭中間件應在其他中間件之前運行。 此順序可確保依賴於轉接頭信息的中間件可以使用標頭值進行處理。 若要在診斷和錯誤處理中間件后運行轉接頭中間件,請參閱轉接頭中間件順序

對中間件管道進行分支

Map 擴展用作約定來創建管道分支。 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:1234 的請求和響應。

對中間件管道進行分支
請求 響應
localhost:1234 Hello from non-Map delegate.
localhost:1234/map1 Map Test 1
localhost:1234/map2 Map Test 2
localhost:1234/map3 Hello from non-Map delegate.

使用 Map 時,將從 HttpRequest.Path 中刪除匹配的路徑段,並針對每個請求將該路徑段追加到 HttpRequest.PathBase

Map 支持嵌套,例如:

app.Map("/level1", level1App => {
    level1App.Map("/level2a", level2AApp => {
        // "/level1/level2a" processing
    });
    level1App.Map("/level2b", level2BApp => {
        // "/level1/level2b" processing
    });
});

 

此外,Map 還可同時匹配多個段:

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

 

MapWhen 基於給定謂詞的結果創建請求管道分支。 Func<HttpContext, bool> 類型的任何謂詞均可用於將請求映射到管道的新分支。 在以下示例中,謂詞用於檢測查詢字符串變量 branch 是否存在:

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:1234 的請求和響應:

表 2
請求 響應
localhost:1234 Hello from non-Map delegate.
localhost:1234/?branch=master Branch used = master

UseWhen 也基於給定謂詞的結果創建請求管道分支。 與 MapWhen 不同的是,如果這個分支不發生短路或包含終端中間件,則會重新加入主管道:

 

 
public class Startup
{
    private void HandleBranchAndRejoin(IApplicationBuilder app, ILogger<Startup> logger)
    {
        app.Use(async (context, next) =>
        {
            var branchVer = context.Request.Query["branch"];
            logger.LogInformation("Branch used = {branchVer}", branchVer);

            // Do work that doesn't write to the Response.
            await next();
            // Do other work that doesn't write to the Response.
        });
    }

    public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
    {
        app.UseWhen(context => context.Request.Query.ContainsKey("branch"),
                               appBuilder => HandleBranchAndRejoin(appBuilder, logger));

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

 

在前面的示例中,響應 "Hello from main pipeline." 是為所有請求編寫的。 如果請求中包含查詢字符串變量 branch,則在重新加入主管道之前會記錄其值。

內置中間件

ASP.NET Core 附帶以下中間件組件。 “順序”列提供備注,以說明中間件在請求處理管道中的放置,以及中間件可能會終止請求處理的條件。 如果中間件讓請求處理管道短路,並阻止下游中間件進一步處理請求,它被稱為“終端中間件”。 若要詳細了解短路,請參閱使用 IApplicationBuilder 創建中間件管道部分。

內置中間件
中間件 描述 順序
身份驗證 提供身份驗證支持。 在需要 HttpContext.User 之前。 OAuth 回叫的終端。
授權 提供身份驗證支持。 緊接在身份驗證中間件之后。
Cookie 策略 跟蹤用戶是否同意存儲個人信息,並強制實施 cookie 字段(如 secure 和 SameSite)的最低標准。 在發出 cookie 的中間件之前。 示例:身份驗證、會話、MVC (TempData)。
CORS 配置跨域資源共享。 在使用 CORS 的組件之前。 由於此錯誤UseCors 當前必須在 UseResponseCaching 之前運行。
診斷 提供新應用的開發人員異常頁、異常處理、狀態代碼頁和默認網頁的幾個單獨的中間件。 在生成錯誤的組件之前。 異常終端或為新應用提供默認網頁的終端。
轉接頭 將代理標頭轉發到當前請求。 在使用已更新字段的組件之前。 示例:方案、主機、客戶端 IP、方法。
運行狀況檢查 檢查 ASP.NET Core 應用及其依賴項的運行狀況,如檢查數據庫可用性。 如果請求與運行狀況檢查終結點匹配,則為終端。
標頭傳播 將 HTTP 標頭從傳入的請求傳播到傳出的 HTTP 客戶端請求中。  
HTTP 方法重寫 允許傳入 POST 請求重寫方法。 在使用已更新方法的組件之前。
HTTPS 重定向 將所有 HTTP 請求重定向到 HTTPS。 在使用 URL 的組件之前。
HTTP 嚴格傳輸安全性 (HSTS) 添加特殊響應標頭的安全增強中間件。 在發送響應之前,修改請求的組件之后。 示例:轉接頭、URL 重寫。
MVC 用 MVC/Razor Pages 處理請求。 如果請求與路由匹配,則為終端。
OWIN 與基於 OWIN 的應用、服務器和中間件進行互操作。 如果 OWIN 中間件處理完請求,則為終端。
響應緩存 提供對緩存響應的支持。 在需要緩存的組件之前。 UseCORS 必須在 UseResponseCaching 之前。
響應壓縮 提供對壓縮響應的支持。 在需要壓縮的組件之前。
請求本地化 提供本地化支持。 在對本地化敏感的組件之前。
終結點路由 定義和約束請求路由。 用於匹配路由的終端。
SPA 通過返回單頁應用程序 (SPA) 的默認頁面,在中間件鏈中處理來自這個點的所有請求 在鏈中處於靠后位置,因此其他服務於靜態文件、MVC 操作等內容的中間件占據優先位置。
會話 提供對管理用戶會話的支持。 在需要會話的組件之前。
靜態文件 為提供靜態文件和目錄瀏覽提供支持。 如果請求與文件匹配,則為終端。
URL 重寫 提供對重寫 URL 和重定向請求的支持。 在使用 URL 的組件之前。
WebSockets 啟用 WebSockets 協議。 在接受 WebSocket 請求所需的組件之前。

 

二、寫入自定義 ASP.NET Core 中間件

首先創建一個ASP.NET Core 3.1 的WebApi 應用程序 MiddlewareDemo的項目


中間件類

 

通常,中間件封裝在類中,並且通過擴展方法公開,在Startup類中Configure方法如下: 如果中間件比較簡單可以直接下面使用 app.Use 或者app.Run 

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseHttpsRedirection();

    app.UseRouting();

    app.UseAuthorization();
    app.Use(async (context, next) =>
    {
       var cultureQuery = context.Request.Query["culture"];
       if (!string.IsNullOrWhiteSpace(cultureQuery))
       {
            await context.Response.WriteAsync($"{cultureQuery} \t ");
       }//調用管道中的下一個委托/中間件
       await next();
   });
   app.Run(async(context)=> {
          await context.Response.WriteAsync("Hello, World!");
   });


   app.UseEndpoints(endpoints =>
   {
        endpoints.MapControllers();
   });
}

運行項目首先會在頁面顯示 Hello, World! ,然后在當前路徑后面加入?culture=Dean  頁面會顯示 Dean    Hello, World! 

當中間件的業務比較復雜,代碼量很大的時候,通常要中間件將委托移動到類:

 

1、新建一個類RequestCultureMiddleware 代碼如下:

using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;

namespace MiddlewareDemo
{
    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))
            {
                await context.Response.WriteAsync($"{cultureQuery} \t ");
                //var culture = new CultureInfo(cultureQuery);
                //CultureInfo.CurrentCulture = culture;
                //CultureInfo.CurrentUICulture = culture;
            }

            // Call the next delegate/middleware in the pipeline
            await _next(context);
        }
    }
}

注意必須包括中間件類:

  • 具有類型為 RequestDelegate 的參數的公共構造函數。
  • 名為 Invoke 或 InvokeAsync 的公共方法。 此方法必須:
    • 返回 Task
    • 接受類型 HttpContext 的第一個參數。

構造函數和 Invoke/InvokeAsync 的其他參數由依賴關系注入 (DI) 填充。

2、 把在上面的Configure方法里的app.Use() 里的中間件注釋掉 然后改成 

 app.UseMiddleware<RequestCultureMiddleware>();

運行后,結果不變

中間件依賴項

中間件應通過在其構造函數中公開其依賴項來遵循顯式依賴項原則。 在每個應用程序生存期構造一次中間件。

中間件組件可通過構造函數參數從依賴關系注入 (DI) 解析其依賴項。 UseMiddleware<T> 也可直接接受其他參數。

按請求中間件依賴項

由於中間件是在應用啟動時構造的,而不是按請求構造的,因此在每個請求過程中,中間件構造函數使用的范圍內生存期服務不與其他依賴關系注入類型共享。 如果必須在中間件和其他類型之間共享范圍內服務,請將這些服務添加到 Invoke 方法的簽名。 Invoke 方法可接受由 DI 填充的其他參數:

1、新建一個類代碼如下:

 1 #region 按請求中間件依賴項
 2 
 3     public class GetValue
 4     {
 5         public static int Value { get; set; }
 6     }
 7 
 8     public interface IMyScopedService
 9     {
10 
11         void SetMyProperty(int value);
12 
13     }
14 
15     public class MyScopedService : IMyScopedService
16     {
17         public void SetMyProperty(int value)
18         {
19             GetValue.Value = value;
20         }
21     }
22 
23     public class CustomMiddleware
24     {
25         private readonly RequestDelegate _next;
26 
27         public CustomMiddleware(RequestDelegate next)
28         {
29             _next = next;
30         }
31 
32         // IMyScopedService is injected into Invoke
33         public async Task Invoke(HttpContext httpContext, IMyScopedService svc)
34         {
35             svc.SetMyProperty(1000);
36             string str = $" GetValue.Value : { GetValue.Value } \t";
37             await httpContext.Response.WriteAsync(str, Encoding.UTF8);
38             await _next(httpContext);
39         }
40     }
41 
42 
43     #endregion
View Code

2、在Startup類Configure方法注釋掉  app.UseMiddleware<RequestCultureMiddleware>(); 

3、在注釋掉  app.UseMiddleware<RequestCultureMiddleware>();  下添加下面的

app.UseMiddleware<CustomMiddleware>();

4、運行后頁面打印  

 GetValue.Value : 1000     Hello, World!

中間件擴展方法通過 IApplicationBuilder 公開中間件:

using Microsoft.AspNetCore.Builder;

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

以下代碼通過 Startup.Configure 調用中間件:

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.UseRequestCulture();

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

三、基於工廠的中間件(ASP.NET Core 中基於工廠的中間件激活

參考文檔:https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware/extensibility?view=aspnetcore-3.1

IMiddlewareFactory/IMiddleware 是中間件激活的擴展點。

UseMiddleware 擴展方法檢查中間件的已注冊類型是否實現 IMiddleware。 如果是,則使用在容器中注冊的 IMiddlewareFactory 實例來解析 IMiddleware 實現,而不使用基於約定的中間件激活邏輯。 中間件在應用的服務容器中注冊為作用域或瞬態服務。

優點:

  • 按客戶端請求(作用域服務的注入)激活
  • 讓中間件強類型化

IMiddleware 按客戶端請求(連接)激活,因此作用域服務可以注入到中間件的構造函數中。

IMiddleware

IMiddleware 定義應用的請求管道的中間件。 InvokeAsync(HttpContext, RequestDelegate) 方法處理請求,並返回代表中間件執行的 Task

3.1使用約定激活的中間件:

 
 1 using Microsoft.AspNetCore.Http;
 2 using System;
 3 using System.Collections.Generic;
 4 using System.Linq;
 5 using System.Threading.Tasks;
 6 
 7 namespace MiddlewareDemo
 8 {
 9     public class ConventionalMiddleware
10     {
11         private readonly RequestDelegate _next;
12 
13         public ConventionalMiddleware(RequestDelegate next)
14         {
15             _next = next;
16         }
17 
18         public async Task InvokeAsync(HttpContext context, AppDbContext db)
19         {
20             var keyValue = context.Request.Query["key"];
21 
22             if (!string.IsNullOrWhiteSpace(keyValue))
23             {
24                 db.Add(new Request()
25                 {
26                     DT = DateTime.UtcNow,
27                     MiddlewareActivation = "ConventionalMiddleware",
28                     Value = keyValue
29                 });
30                 await context.Response.WriteAsync($"ConventionalMiddleware count : {DB.DbList.Count.ToString()} \t");
31                 //await db.SaveChangesAsync();
32             }
33 
34             await _next(context);
35         }
36     }
37    
38 
39 }
View Code

 

3.2使用 MiddlewareFactory 激活的中間件:  

 

 1 using Microsoft.AspNetCore.Http;
 2 using System;
 3 using System.Collections.Generic;
 4 using System.Linq;
 5 using System.Threading.Tasks;
 6 
 7 namespace MiddlewareDemo
 8 {
 9     public class FactoryActivatedMiddleware : IMiddleware
10     {
11         private readonly AppDbContext _db;
12 
13         public FactoryActivatedMiddleware(AppDbContext db)
14         {
15             _db = db;
16         }
17 
18         public async Task InvokeAsync(HttpContext context, RequestDelegate next)
19         {
20             var keyValue = context.Request.Query["key"];
21 
22             if (!string.IsNullOrWhiteSpace(keyValue))
23             {
24                 _db.Add(new Request()
25                 {
26                     DT = DateTime.UtcNow,
27                     MiddlewareActivation = "FactoryActivatedMiddleware",
28                     Value = keyValue
29                 });
30 
31                await context.Response.WriteAsync($"FactoryActivatedMiddleware count : {DB.DbList.Count.ToString()} \t");
32                 //await _db.SaveChangesAsync();
33             }
34 
35             await next(context);
36         }
37     }
38 }
View Code

3.3創建AppDbContext類,代碼如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace MiddlewareDemo
{
    public class AppDbContext
    {
        
        public void Add(Request request)
        {
            DB.DbList.Add(request);
        }
    }

    public class DB
    {
        public static List<Request> DbList = new List<Request>();
    }
}

添加Request類

  public class Request
    {
        public DateTime DT { get; set; }
        public string MiddlewareActivation { get; set; }

        public string Value { get; set; }

    }

 

3.4創建一個IApplicationBuilder的擴展方法MiddlewareExtensions,代碼如下:

 1 using Microsoft.AspNetCore.Builder;
 2 using System;
 3 using System.Collections.Generic;
 4 using System.Linq;
 5 using System.Threading.Tasks;
 6 
 7 namespace MiddlewareDemo
 8 {
 9     public static class MiddlewareExtensions
10     {
11         public static IApplicationBuilder UseConventionalMiddleware(
12        this IApplicationBuilder builder)
13         {
14             return builder.UseMiddleware<ConventionalMiddleware>();
15         }
16 
17         public static IApplicationBuilder UseFactoryActivatedMiddleware(
18             this IApplicationBuilder builder)
19         {
20             return builder.UseMiddleware<FactoryActivatedMiddleware>();
21         }
22 
23         //    public static IApplicationBuilder UseFactoryActivatedMiddleware(
24         //this IApplicationBuilder builder, bool option)
25         //    {
26         //        // Passing 'option' as an argument throws a NotSupportedException at runtime.
27         //        return builder.UseMiddleware<FactoryActivatedMiddleware>(option);
28         //    }
29     }
30 }
View Code

 

 

3.5在Startup類ConfigureServices方法中注入下面的

  services.AddScoped<AppDbContext>();
  services.AddTransient<FactoryActivatedMiddleware>();

3.6在Startup類Configure方法中添加下面的

 #region 基於工廠的中間件
            app.UseConventionalMiddleware();
            app.UseFactoryActivatedMiddleware();
            //app.UseFactoryActivatedMiddleware(false);
 #endregion

運行程序,在程序后面加入參數 例如:https://localhost:44349/weatherforecast?key=p

結果為: web頁面顯示 

ConventionalMiddleware count : 1     FactoryActivatedMiddleware count : 2     Hello, World!

 代碼地址:https://github.com/hudean/MiddlewareDemo.git

四、使用 ASP.NET Core 中的第三方容器激活中間件(待續)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM