溫馨提示×

溫馨提示×

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

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

ASP.NET?Core中間件如何使用

發(fā)布時間:2022-04-06 15:13:53 來源:億速云 閱讀:344 作者:iii 欄目:開發(fā)技術

今天小編給大家分享一下ASP.NET Core中間件如何使用的相關知識點,內(nèi)容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

    1.前言

    整個HTTP Request請求跟HTTP Response返回結果之間的處理流程是一個請求管道(request pipeline)。而中間件(middleware)則是一種裝配到請求管道以處理請求和響應的組件。每個組件:

    • 可選擇是否將請求傳遞到管道中的下一個組件。

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

    中間件(middleware)處理流程如下圖所示:

    ASP.NET?Core中間件如何使用

    2.使用中間件

    ASP.NET Core請求管道中每個中間件都包含一系列的請求委托(request delegates)來處理每個HTTP請求,依次調(diào)用。請求委托通過使用IApplicationBuilder類型的Run、Use和Map擴展方法在Strartup.Configure方法中配置。下面我們通過配置Run、Use和Map擴展方法示例來了解下中間件。

    2.1 Run

    public class Startup
    {
        public void Configure(IApplicationBuilder app)
        {
        //第一個請求委托Run
        app.Run(async context =>//內(nèi)嵌匿名方法
        {
            await context.Response.WriteAsync("Hello, World!");
        });
        //第二個請求委托Run
        app.Run(async context =>//內(nèi)嵌匿名方法
        {
            await context.Response.WriteAsync("Hey, World!");
        });
        }
    }

    響應結果:

    ASP.NET?Core中間件如何使用

    由上述代碼可知,Run方法指定為一個內(nèi)嵌匿名方法(稱為并行中間件,in-line middleware),而內(nèi)嵌匿名方法中并沒有指定執(zhí)行下一個請求委托,這一個過程叫管道短路,而該中間件又叫“終端中間件”(terminal middleware),因為它阻止中間件下一步處理請求。所以在Run第一個請求委托的時候就已經(jīng)終止請求,并沒有執(zhí)行第二個請求委托直接返回Hello, World!輸出文本。而根據(jù)官網(wǎng)解釋,Run是一種約定,有些中間件組件可能會暴露他們自己的Run方法,而這些方法只能在管道末尾處運行(也就是說Run方法只在中間件執(zhí)行最后一個請求委托時才使用)。

    2.2 Use

    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            context.Response.ContentType = "text/plain; charset=utf-8";
            await context.Response.WriteAsync("進入第一個委托 執(zhí)行下一個委托之前\r\n");
            //調(diào)用管道中的下一個委托
            await next.Invoke();
            await context.Response.WriteAsync("結束第一個委托 執(zhí)行下一個委托之后\r\n");
        });
        app.Run(async context =>
        {
            await context.Response.WriteAsync("進入第二個委托\(zhòng)r\n");
            await context.Response.WriteAsync("Hello from 2nd delegate.\r\n");
            await context.Response.WriteAsync("結束第二個委托\(zhòng)r\n");
        });
    }

    響應結果:

    ASP.NET?Core中間件如何使用

    由上述代碼可知,Use方法將多個請求委托鏈接在一起。而next參數(shù)表示管道中的下一個委托。如果不調(diào)用next參數(shù)調(diào)用下一個請求委托則會使管道短路。比如,一個授權(authorization)中間件只有通過身份驗證之后才能調(diào)用下一個委托,否則它就會被短路,并返回“Not Authorized”的響應。所以應盡早在管道中調(diào)用異常處理委托,這樣它們就能捕獲在管道的后期階段發(fā)生的異常。

    2.3 Map和MapWhen

    • Map:Map擴展基于請求路徑創(chuàng)建管道分支。

    • MapWhen:MapWhen擴展基于請求條件創(chuàng)建管道分支。

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

    請求

    響應

    localhost:5001

    Hello from non-Map delegate.

    localhost:5001/map1

    Map Test 1

    localhost:5001/map2

    Map Test 2

    localhost:5001/map3

    Hello from non-Map delegate.

    由上述代碼可知,Map方法將從HttpRequest.Path中刪除匹配的路徑段,并針對每個請求將該路徑追加到HttpRequest.PathBase。也就是說當我們在瀏覽器上輸入map1請求地址的時候,系統(tǒng)會執(zhí)行map1分支管道輸出其請求委托信息,同理執(zhí)行map2就會輸出對應請求委托信息。
    MapWhen示例:

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

    請求

    響應

    http://localhost:5001

    Hello from non-Map delegate. <p>

    https://localhost:5001/?branch=master

    Branch used = master

    由上述代碼可知,MapWhen是基于branch條件而創(chuàng)建管道分支的,我們在branch條件上輸入master就會創(chuàng)建其對應管道分支。也就是說,branch條件上輸入任何一個字符串條件,都會創(chuàng)建一個新的管理分支。
    而且還Map支持嵌套,例如:

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

    還可同時匹配多個段:

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

    3.順序

    向Startup.Configure方法添加中間件組件的順序定義了在請求上調(diào)用它們的順序,以及響應的相反順序。此排序?qū)τ诎踩浴⑿阅芎凸δ苤陵P重要。
    以下Startup.Configure方法將為常見應用方案添加中間件組件:

    • 異常/錯誤處理(Exception/error handling)

    • HTTP嚴格傳輸安全協(xié)議(HTTP Strict Transport Security Protocol)

    • HTTPS重定向(HTTPS redirection)

    • 靜態(tài)文件服務器(Static file server)

    • Cookie策略實施(Cookie policy enforcement)

    • 身份驗證(Authentication)

    • 會話(Session)

    • MVC

    請看如下代碼:

    public void Configure(IApplicationBuilder app)
    {
        if (env.IsDevelopment())
        {
            // When the app runs in the Development environment:
            //   Use the Developer Exception Page to report app runtime errors.
            //   Use the Database Error Page to report database runtime errors.
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();
        }
        else
        {
            // When the app doesn't run in the Development environment:
            //   Enable the Exception Handler Middleware to catch exceptions
            //     thrown in the following middlewares.
            //   Use the HTTP Strict Transport Security Protocol (HSTS)
            //     Middleware.
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }
        // Return static files and end the pipeline.
        app.UseStaticFiles();
        // Authenticate before the user accesses secure resources.
        app.UseAuthentication();
    }

    從上述示例代碼中,每個中間件擴展方法都通過Microsoft.AspNetCore.Builder命名空間在 IApplicationBuilder上公開。但是為什么我們要按照這個順序去添加中間件組件呢?下面我們挑幾個中間件來了解下。

    • UseExceptionHandler(異常/錯誤處理)是添加到管道的第一個中間件組件。因此我們可以捕獲在應用程序調(diào)用中發(fā)生的任何異常。那為什么要將異常/錯誤處理放在第一位呢?那是因為這樣我們就不用擔心因前面中間件短路而導致捕獲不到整個應用程序所有異常信息。

    • UseStaticFiles(靜態(tài)文件)中間件在管道中提前調(diào)用,方便它可以處理請求和短路,而無需通過剩余中間組件。也就是說靜態(tài)文件中間件不用經(jīng)過UseAuthentication(身份驗證)檢查就可以直接訪問,即可公開訪問由靜態(tài)文件中間件服務的任何文件,包括wwwroot下的文件。

    • UseAuthentication(身份驗證)僅在MVC選擇特定的Razor頁面或Controller和Action之后才會發(fā)生。

    經(jīng)過上面描述,大家都了解中間件順序的重要性了吧。

    4.編寫中間件(重點)

    雖然ASP.NET Core為我們提供了一組豐富的內(nèi)置中間件組件,但在某些情況下,你可能需要寫入自定義中間件。

    4.1中間件類

    下面我們自定義一個查詢當前區(qū)域性的中間件:

    public class Startup
    {
        public void Configure(IApplicationBuilder app)
        {
            app.Use((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
                return next();
            });
            app.Run(async (context) =>
            {
                await context.Response.WriteAsync(
                    $"Hello {CultureInfo.CurrentCulture.DisplayName}");
            });
        }
    }

    可通過傳入?yún)^(qū)域性參數(shù)條件進行測試。例如http://localhost:7997/?culture=zh、http://localhost:7997/?culture=en。
    但是如果每個自定義中間件都在Startup.Configure方法中編寫如上一大堆代碼,那么對于程序來說,將是災難性的(不利于維護和調(diào)用)。為了更好管理代碼,我們應該把內(nèi)嵌匿名方法封裝到新建的自定義類(示例自定義RequestCultureMiddleware類)里面去:

    public class RequestCultureMiddleware
    {
        private readonly RequestDelegate _next;
        public RequestCultureMiddleware(RequestDelegate next)
        {
            _next = next;
        }
        public async Task InvokeAsync(HttpContext context)
        {
            context.Response.ContentType = "text/plain; charset=utf-8";
            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(context);
        }
    }

    通過Startup.Configure方法調(diào)用中間件:

    public class Startup
    {
        public void Configure(IApplicationBuilder app)
        {
            app.UseMiddleware<RequestCultureMiddleware>();
            app.Run(async (context) =>
            {
                await context.Response.WriteAsync(
                    $"Hello {CultureInfo.CurrentCulture.DisplayName}");
            });
        }
    }

    4.2中間件擴展方法

    Startup.Configure方法調(diào)用中間件設置可以通過自定義的擴展方法將其公開(調(diào)用內(nèi)置IApplicationBuilder公開中間件)。示例創(chuàng)建一個RequestCultureMiddlewareExtensions擴展類并通過IApplicationBuilder公開:

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

    再通過Startup.Configure方法調(diào)用中間件:

    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中間件如何使用

    通過委托構造中間件,應用程序在運行時創(chuàng)建這個中間件,并將它添加到管道中。這里需要注意的是,中間件的創(chuàng)建是單例的,每個中間件在應用程序生命周期內(nèi)只有一個實例。那么問題來了,如果我們業(yè)務邏輯需要多個實例時,該如何操作呢?請繼續(xù)往下看。

    5.按每次請求創(chuàng)建依賴注入(DI)

    在中間件的創(chuàng)建過程中,內(nèi)置的IOC容器會為我們創(chuàng)建一個中間件實例,并且整個應用程序生命周期中只會創(chuàng)建一個該中間件的實例。通常我們的程序不允許這樣的注入邏輯。其實,我們可以把中間件理解成業(yè)務邏輯的入口,真正的業(yè)務邏輯是通過Application Service層實現(xiàn)的,我們只需要把應用服務注入到Invoke方法中即可。ASP.NET Core為我們提供了這種機制,允許我們按照請求進行依賴的注入,也就是每次請求創(chuàng)建一個服務。示例:

    public class CustomMiddleware
    {
        private readonly RequestDelegate _next;
        public CustomMiddleware(RequestDelegate next)
        {
            _next = next;
        }
        // IMyScopedService is injected into Invoke
        public async Task Invoke(HttpContext httpContext, IMyScopedService svc)
        {
            svc.MyProperty(1000);
            await _next(httpContext);
        }
    }
    public static class CustomMiddlewareExtensions
    {
        public static IApplicationBuilder UseCustomMiddleware(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<CustomMiddleware>();
        }
    }
    public interface IMyScopedService
    {
        void MyProperty(decimal input);
    }
    public class MyScopedService : IMyScopedService
    {
        public void MyProperty(decimal input)
        {
            Console.WriteLine("MyProperty is " + input);
        }
    }
    public void ConfigureServices(IServiceCollection services)
    {
        //注入DI服務
        services.AddScoped<IMyScopedService, MyScopedService>();
    }

    響應結果:

    ASP.NET?Core中間件如何使用

    以上就是“ASP.NET Core中間件如何使用”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業(yè)資訊頻道。

    向AI問一下細節(jié)

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

    AI