溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

ASP.NET?Core中間件用法與官方常用中間件的示例分析

發(fā)布時間:2022-03-24 11:07:16 來源:億速云 閱讀:186 作者:小新 欄目:開發(fā)技術(shù)

小編給大家分享一下ASP.NET Core中間件用法與官方常用中間件的示例分析,希望大家閱讀完這篇文章之后都有所收獲,下面讓我們一起去探討吧!

一、什么是中間件

我們都知道,任何的一個web框架都是把http請求封裝成一個管道,每一次的請求都是經(jīng)過管道的一系列操作,最終才會到達我們寫的代碼中。而中間件就是用于組成應(yīng)用程序管道來處理請求和響應(yīng)的組件。管道內(nèi)的每一個組件都可以選擇是否將請求轉(zhuǎn)交給下一個組件,并在管道中調(diào)用下一個組件之前和之后執(zhí)行某些操作。請求委托被用來建立請求管道,請求委托處理每一個HTTP請求。

中間件可以認為有兩個基本的職責:

  • 選擇是否將請求傳遞給管道中的下一個中間件。

  • 可以在管道中的下一個中間件前后執(zhí)行一些工作。

請求委托通過使用IApplicationBuilder類型的Run、Map以及Use擴展方法來配置,并在Startup類中傳給Configure方法。每個單獨的請求委托都可以被指定為一個內(nèi)嵌匿名方法,或其定義在一個可重用的類中。這些可以重用的類被稱作“中間件”或“中間件組件”。每個位于請求管道內(nèi)的中間件組件負責調(diào)用管道中下一個組件,或適時短路調(diào)用鏈。中間件是一個典型的AOP應(yīng)用。

ASP.NET Core請求管道由一系列的請求委托所構(gòu)成,它們一個接著一個的被調(diào)用,看下面一張微軟官方的中間件請求管道圖(圖中執(zhí)行線程按黑色箭頭的順序執(zhí)行):

ASP.NET?Core中間件用法與官方常用中間件的示例分析

中間件短路:每一個委托在下一個委托之前和之后都有機會執(zhí)行操作。任何委托都能選擇停止傳遞到下一個委托,而是結(jié)束請求并開始響應(yīng),這就是請求管道的短路,這是一種有意義的設(shè)計,因為它可以避免一些不必要的工作。比如說,一個授權(quán)(authorization)中間件只有在通過身份驗證之后才能調(diào)用下一個委托,否則它就會被短路,并返回“Not Authorized”的響應(yīng)。異常處理委托需要在管道的早期被調(diào)用,這樣它們就能夠捕捉到發(fā)生在管道內(nèi)更深層次出現(xiàn)的異常了。短路可以用下面這張圖來表示:

ASP.NET?Core中間件用法與官方常用中間件的示例分析

在上圖中,我們可以把中間件1認為是身份認證的中間件,HTTP請求發(fā)送過來,首先經(jīng)過身份認證中間件,如果身份認證失敗,那么就直接給出響應(yīng)并返回,不會再把請求傳遞給下面的中間件2和中間件3.

中間件的執(zhí)行跟調(diào)用的順序有關(guān),然后在響應(yīng)時則以相反的順序返回。

請求在每一步都可能被短路,所以我們要以正確的順序添加中間件,如異常處理中間件,我們要添加在最開始的地方,這樣就能第一時間捕獲異常,以及后續(xù)中間可能發(fā)生的異常,然后最終做處理返回。

我們來看看Configure方法里面提供了哪些中間件:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        // 異常中間件
        app.UseDeveloperExceptionPage();
    }

    // 路由中間件
    app.UseRouting();
    // 授權(quán)中間件
    app.UseAuthorization();
    // 終結(jié)點中間件
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

中間件和過濾器的區(qū)別

中間件和過濾器都是一種AOP的思想,他們的功能類似,那么他們有什么區(qū)別呢?

過濾器更加貼合業(yè)務(wù),它關(guān)注于應(yīng)用程序本身,關(guān)注的是如何實現(xiàn)業(yè)務(wù),比如對輸出結(jié)果進行格式化,對請求的ViewModel進行數(shù)據(jù)校驗,這時就肯定要使用過濾器了。過濾器是MVC的一部分,它可以攔截到你Action上下文的一些信息,而中間件是沒有這個能力的。可以認為過濾器是附加性的一種功能,它只是中間件附帶表現(xiàn)出來的特征。中間件是管道模型里重要的組成部分,不可或缺,而過濾器可以沒有。

二、中間件常用方法

中間件中定義了Run、Use、Map、MapWhen幾種方法,我們下面一一講解這幾種方法。

1、Run方法

我們先來看到Run()方法的定義:

ASP.NET?Core中間件用法與官方常用中間件的示例分析

中定義中可以看出:Run()方法中只有一個RequestDelegate委托類型的參數(shù),沒有Next參數(shù),所以Run()方法也叫終端中間件,不會將請求傳遞給下一個中間件,也就是發(fā)生了“短路”??聪旅娴拇a:

// Run方法向應(yīng)用程序的請求管道中添加一個RequestDelegate委托
// 放在管道最后面,終端中間件
app.Run(handler: async context => 
{
    await context.Response.WriteAsync(text: "Hello World1\r\n");
});
app.Run(handler: async context =>
{
    await context.Response.WriteAsync(text: "Hello World2\r\n");
});

程序運行結(jié)果:

ASP.NET?Core中間件用法與官方常用中間件的示例分析

可以看到:只輸出了中間件1的信息,沒有輸出中間件2的信息,說明發(fā)生了短路。

注意:Run()方法被稱為終端中間件,要放在所有中間件的最后面,否則在Run()方法后面的中間件將不會被執(zhí)行。

2、Use方法

我們先來看看Use()方法的定義:

ASP.NET?Core中間件用法與官方常用中間件的示例分析

可以看出:Use方法的參數(shù)是一個Func委托,輸入?yún)?shù)是一個RequestDelegate類型的委托,返回參數(shù)也是一個RequestDelegate類型的委托,這里表示調(diào)用下一個中間件,我們在來看看RequestDelegate委托的定義:

ASP.NET?Core中間件用法與官方常用中間件的示例分析

可以看出:RequestDelegate是一個委托,有一個HttpContext類型的參數(shù),HttPContext表示Http請求上下文,可以獲取請求信息,返回值是Task類型,明白了Use()方法的參數(shù)以后,我們寫一個自定義的Use()方法:

// 向應(yīng)用程序的請求管道中添加一個Func委托,這個委托其實就是所謂的中間件。
// context參數(shù)是HttpContext,表示HTTP請求的上下文對象
// next參數(shù)表示管道中的下一個中間件委托,如果不調(diào)用next,則會使管道短路
// 用Use可以將多個中間件鏈接在一起
app.Use(async (context, next) =>
{
    await context.Response.WriteAsync(text: "hello Use1\r\n");
    // 調(diào)用下一個委托
    await next();
});
app.Use(async (context, next) =>
{
    await context.Response.WriteAsync(text: "hello Use2\r\n");
    // 調(diào)用下一個委托
    await next();
});

程序運行結(jié)果:

ASP.NET?Core中間件用法與官方常用中間件的示例分析

我們在上面說過,可以在調(diào)用中間件之前和之后做一些工作,看下面的代碼:

// 向應(yīng)用程序的請求管道中添加一個Func委托,這個委托其實就是所謂的中間件。
// context參數(shù)是HttpContext,表示HTTP請求的上下文對象
// next參數(shù)表示管道中的下一個中間件委托,如果不調(diào)用next,則會使管道短路
// 用Use可以將多個中間件鏈接在一起
app.Use(async (context, next) =>
{
    // 解決中文亂碼問題
    context.Response.ContentType = "text/plain; charset=utf-8";
    await context.Response.WriteAsync(text: "中間件1:傳入請求\r\n");
    // 調(diào)用下一個委托
    await next();
    await context.Response.WriteAsync(text: "中間件1:傳出響應(yīng)\r\n");
});
app.Use(async (context, next) =>
{
    await context.Response.WriteAsync(text: "中間件2:傳入請求\r\n");
    // 調(diào)用下一個委托
    await next();
    await context.Response.WriteAsync(text: "中間件2:傳出響應(yīng)\r\n");
});
app.Run(handler:async context =>
{
    await context.Response.WriteAsync(text: "中間件3:處理請求并生成響應(yīng)\r\n");
});

程序運行結(jié)果:

ASP.NET?Core中間件用法與官方常用中間件的示例分析

我們可以總結(jié)上面代碼的執(zhí)行順序:

  • 請求先到達中間件1,然后輸出(中間件1:傳入請求)

  • 然后中間件1調(diào)用next()。next()會調(diào)用管道中的中間件2。

  • 中間件2輸出(中間件2:傳入請求)。

  • 然后中間件2會調(diào)用next()。next()在調(diào)用管道中的中間件3。

  • 中間件3處理請求并生成響應(yīng),不在調(diào)用下一個中間件,所以我們看到輸出(中間件3:處理請求并生成響應(yīng))。

  • 這時管理開始發(fā)生逆轉(zhuǎn)。

  • 此時控制器將交回到中間件2,并將中間件3生成的響應(yīng)傳遞給它。中間件2輸出(中間件2:傳出響應(yīng))。

  • 最后,中間件2在將控制權(quán)交給中間件1。

  • 中間件1最后輸出(中間件1:傳出響應(yīng)),這就是我們最后看的的結(jié)果。

我們知道:Use()方法中有兩個參數(shù),next參數(shù)表示調(diào)用管道中的下一個中間件,如果不調(diào)用next,那么也會使管道發(fā)生短路,相當于Run()方法,看下面的代碼:

// 向應(yīng)用程序的請求管道中添加一個Func委托,這個委托其實就是所謂的中間件。
// context參數(shù)是HttpContext,表示HTTP請求的上下文對象
// next參數(shù)表示管道中的下一個中間件委托,如果不調(diào)用next,則會使管道短路
// 用Use可以將多個中間件鏈接在一起
app.Use(async (context, next) =>
{
    // 解決中文亂碼問題
    context.Response.ContentType = "text/plain; charset=utf-8";
    await context.Response.WriteAsync(text: "中間件1:傳入請求\r\n");
    // 調(diào)用下一個委托
    await next();
    await context.Response.WriteAsync(text: "中間件1:傳出響應(yīng)\r\n");
});
app.Use(async (context, next) =>
{
    await context.Response.WriteAsync(text: "中間件2:傳入請求\r\n");
    // 調(diào)用下一個委托
    await next();
    await context.Response.WriteAsync(text: "中間件2:傳出響應(yīng)\r\n");
});
//app.Run(handler:async context =>
//{
//    await context.Response.WriteAsync(text: "中間件3:處理請求并生成響應(yīng)\r\n");
//});
// Use方法也可以不調(diào)用next,表示發(fā)生短路
app.Use(async (context, next) =>
{
    await context.Response.WriteAsync(text: "中間件3:處理請求并生成響應(yīng)\r\n");
});

程序運行結(jié)果:

ASP.NET?Core中間件用法與官方常用中間件的示例分析

可以看出:如果使用Use()方法,不調(diào)用next,實現(xiàn)的效果跟使用Run()方法一樣,都會使管道發(fā)生短路。

3、Map方法

Map作為慣例,將管道分流。Map根據(jù)給定請求路徑匹配將請求管道分流。如果請求路徑以指定路徑開始,則執(zhí)行分支。看一下Map()方法的定義:

ASP.NET?Core中間件用法與官方常用中間件的示例分析

可以看到Map方法有兩個參數(shù):第一個參數(shù)是匹配規(guī)則,第二個參數(shù)是Action泛型委托,泛型委托參數(shù)是IApplicationBuilder類型,和Configure方法的第一個參數(shù)類型相同。這就表示可以把實現(xiàn)了Action泛型委托的方法添加到中間件管道中執(zhí)行。

我們首先定義一個方法,該方法的參數(shù)是IApplicationBuilder類型:

/// <summary>
/// 自定義方法
/// </summary>
/// <param name="app">IApplicationBuilder</param>
private void HandleMap1(IApplicationBuilder app)
{
    app.Run(handler: async context => 
    {
        await context.Response.WriteAsync(text: "Hello Map1");
    });
}

/// <summary>
/// 自定義方法
/// </summary>
/// <param name="app">IApplicationBuilder</param>
private void HandleMap2(IApplicationBuilder app)
{
    app.Run(handler: async context =>
    {
        await context.Response.WriteAsync(text: "Hello Map2");
    });
}

然后看一下使用Map方法的代碼:

// Map可以根據(jù)匹配的URL來選擇執(zhí)行,簡單來說就是根據(jù)URL進行分支選擇執(zhí)行
// 有點類似于MVC中的路由
// 匹配的URL:http://localhost:5000/Map1
app.Map(pathMatch: "/Map1", configuration: HandleMap1);
// 匹配的URL:http://localhost:5000/Map2
app.Map(pathMatch: "/Map2", configuration: HandleMap2);

運行程序,然后在瀏覽器地址欄里面輸入:http://localhost:5000/Map1,輸出結(jié)果:

ASP.NET?Core中間件用法與官方常用中間件的示例分析

在地址欄里面在輸入:http://localhost:5000/Map2,輸出結(jié)果:

ASP.NET?Core中間件用法與官方常用中間件的示例分析

Map還支持嵌套,看下面的代碼:

// 嵌套Map
app.Map(pathMatch: "/Map1", configuration: App1 => 
{
    //
    App1.Map("/Map2",action=> 
    {
        action.Run(async context => 
        {
            await context.Response.WriteAsync("This is /Map1/Map2");
        });
    });
    App1.Run(async context => 
    {
        await context.Response.WriteAsync("This is no-map");
    });
});

訪問http://localhost:5000/Map1/123輸出結(jié)果:

ASP.NET?Core中間件用法與官方常用中間件的示例分析

訪問http://localhost:5000/Map1輸出結(jié)果:

ASP.NET?Core中間件用法與官方常用中間件的示例分析

訪問http://localhost:5000/Map1/Map2輸出結(jié)果:

ASP.NET?Core中間件用法與官方常用中間件的示例分析

Map也可以同時匹配多個段,看下面的代碼:

運行程序,輸出結(jié)果:

ASP.NET?Core中間件用法與官方常用中間件的示例分析

訪問http://localhost:5000/Map1/Map2輸出結(jié)果:

ASP.NET?Core中間件用法與官方常用中間件的示例分析

4、Mapwhen方法

MapWhen是基于給定的謂詞分支請求管道。任何使Func<HttpContext,bool>返回true的謂詞的請求都被映射到新的管道分支。

我們先來看看Mapwhen方法的定義:

ASP.NET?Core中間件用法與官方常用中間件的示例分析

可以看出:MapWhen方法有兩個參數(shù):第一個參數(shù)是Func類型的委托,輸入?yún)?shù)是HttpContext,輸出參數(shù)是bool類型。第二個參數(shù)是Action委托,參數(shù)是IApplicationBuilder類型,表示也可以把實現(xiàn)Action委托的方法添加到中間件管道中執(zhí)行。

看下面的例子,如果url中包括name查詢參數(shù),則執(zhí)行HandleName方法,如果包含age查詢參數(shù),則執(zhí)行HandleAge方法,否則執(zhí)行Run()方法。

HandleName和HandleAge方法定義如下:

private void HandleName(IApplicationBuilder app)
{
    app.Run(handler: async context =>
    {
        await context.Response.WriteAsync(text: $"This name is: {context.Request.Query["name"]}");
    });
}

private void HandleAge(IApplicationBuilder app)
{
    app.Run(handler: async context =>
    {
        await context.Response.WriteAsync(text: $"This age is: {context.Request.Query["age"]}");
    });
}

對應(yīng)的MapWhen方法定義如下:

// 如果訪問的url參數(shù)中包含name,則執(zhí)行HandleName
app.MapWhen(
// Func委托,輸入?yún)?shù)是HttpContext,返回bool值    
predicate: context =>
{
    // 判斷url參數(shù)中是否包含name
    return context.Request.Query.ContainsKey("name");
}, configuration: HandleName);

// 如果訪問的url參數(shù)中包含name,則執(zhí)行HandleAge
app.MapWhen(
// Func委托,輸入?yún)?shù)是HttpContext,返回bool值
predicate: context =>
{
    // 判斷url參數(shù)中是否包含age
    return context.Request.Query.ContainsKey("age");
}, configuration: HandleAge);

app.Run(async context => 
{
    await context.Response.WriteAsync("There is non-Map delegate \r\n");
});

運行程序,輸出結(jié)果:

ASP.NET?Core中間件用法與官方常用中間件的示例分析

在url里面添加name查詢參數(shù)輸出結(jié)果:

ASP.NET?Core中間件用法與官方常用中間件的示例分析

在url里面添加age查詢參數(shù)輸出結(jié)果:

ASP.NET?Core中間件用法與官方常用中間件的示例分析

三、自定義中間件

在上面的例子中,我們都是使用的官方中間件自動的方法,其實我們也可以自己編寫一個中間件。

中間件遵循顯示依賴原則,并在其構(gòu)造函數(shù)中暴露所有依賴項。中間件能夠利用UseMiddleware<T>擴展方法的優(yōu)勢,直接通過它們的構(gòu)造函數(shù)注入服務(wù)。依賴注入服務(wù)是自動完成填充的。

ASP.NET Core約定中間件類必須包括以下內(nèi)容:

  • 具有類型為RequestDelegate參數(shù)的公共構(gòu)造函數(shù)。

  • 必須有名為Invoke或InvokeAsync的公共方法,此方法必須滿足兩個條件:方法返回類型是Task、方法的第一個參數(shù)必須是HttpContext類型。

我們自定義一個記錄IP的中間件,新建一個類RequestIPMiddleware,代碼如下:

using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;

namespace MiddlewareDemo.Middleware
{
    /// <summary>
    /// 記錄IP地址的中間件
    /// </summary>
    public class RequestIPMiddleware
    {
        // 私有字段
        private readonly RequestDelegate _next;

        /// <summary>
        /// 公共構(gòu)造函數(shù),參數(shù)是RequestDelegate類型
        /// 通過構(gòu)造函數(shù)進行注入,依賴注入服務(wù)會自動完成注入
        /// </summary>
        /// <param name="next"></param>
        public RequestIPMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        /// <summary>
        /// Invoke方法
        /// 返回值是Task,參數(shù)類型是HttpContext
        /// </summary>
        /// <param name="context">Http上下文</param>
        /// <returns></returns>
        public async Task Invoke(HttpContext context)
        {
            await context.Response.WriteAsync($"User IP:{context.Connection.RemoteIpAddress.ToString()}\r\n");
            // 調(diào)用管道中的下一個委托
            await _next.Invoke(context);
        }
    }
}

然后創(chuàng)建一個擴展方法,對IApplicationBuilder進行擴展:

using Microsoft.AspNetCore.Builder;

namespace MiddlewareDemo.Middleware
{
    public static class RequestIPExtensions
    {
        /// <summary>
        /// 擴展方法,對IApplicationBuilder進行擴展
        /// </summary>
        /// <param name="builder"></param>
        /// <returns></returns>
        public static IApplicationBuilder UseRequestIP(this IApplicationBuilder builder)
        {
            // UseMiddleware<T>
            return builder.UseMiddleware<RequestIPMiddleware>();
        }
    }
}

最后在Startup類的Configure方法中使用自定義中間件:

// 使用自定義中間件
app.UseRequestIP();

運行程序,查看結(jié)果:

ASP.NET?Core中間件用法與官方常用中間件的示例分析

這樣就完成了一個自定義中間件。

四、官方常用中間件

1、異常處理中間件

當應(yīng)用程序在開發(fā)環(huán)境中運行時,開發(fā)人員異常頁中間件( UseDeveloperExceptionPage )報告應(yīng)用程序運行時的錯誤。

當應(yīng)用程序在生產(chǎn)環(huán)境中運行時,異常處理中間件( UseExceptionHandler )捕獲下面中間件中引發(fā)的異常。

2、HTTPS重定向中間件

HTTPS重定向中間件( UseHttpsRedirection )會將HTTP請求重定向到HTTPS。

3、靜態(tài)文件中間件

靜態(tài)文件中間件( UseStaticFiles )返回靜態(tài)文件,并簡化進一步請求處理。

4、Cookie中間件

Cookie策略中間件( UseCookiePolicy )使應(yīng)用符合歐盟一般數(shù)據(jù)保護條例的規(guī)定。

5、路由中間件

路由中間件( UseRouting )用于路由的請求。

6、身份認證中間件

身份認證中間件( UseAuthentication )嘗試對用戶進行身份驗證,驗證通過之后才會允許用戶訪問安全資源。

7、授權(quán)中間件

授權(quán)中間件( UseAuthorization )用于授權(quán)驗證通過的用戶可以訪問哪些資源。

8、會話中間件

會話中間件( UseSession )建立和維護會話狀態(tài)。如果應(yīng)用程序使用會話狀態(tài),請在Cookie策略中間件之后和MVC中間件之前調(diào)用會話中間件。

9、終結(jié)點路由中間件

終結(jié)點路由中間件( UseEndpoints )用于將 Razor Pages 終結(jié)點添加到請求管道。

看完了這篇文章,相信你對“ASP.NET Core中間件用法與官方常用中間件的示例分析”有了一定的了解,如果想了解更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI