iT邦幫忙

2021 iThome 鐵人賽

DAY 4
0
自我挑戰組

.NET Core MVC網頁應用開發系列 第 4

.NET Core第4天_middleware是捨麼?

中介軟體為組成應用程式管線的軟體,用以處理要求與回應,
.net core中定義的中介則可以說是用來取代以前HTTP Handler(HTTP 處理常式)新技術。
ASP.NET Core 採用Request Pipeline的模型設計,
要求管線由要求委派序列組成,並會一個接著一個呼叫。
下圖說明此概念。 執行緒遵循黑色箭號執行。
https://ithelp.ithome.com.tw/upload/images/20210904/20107452yGkeEowQ22.png

也可以白話說就是用來重應用程式管線來處理請求、回應的元件,用Pipeline 來
比喻Middleware的模型就是強調各項Middleware串聯在一起。
每次的Request跟Response都會像大隊接力一樣,傳送至下一棒。
也像跑電子公文單審核經費超過特定門檻是否往上加簽的概念
(類似設計模式中的Chain of Responsibility Pattern責任鍊模式)
管線內每一元件都可去抉擇是否將request交給下一元件,並且在管線中呼叫下一元件之前
和之後執行某些業務邏輯功能。

和Http Module差異在於
HttpModules 是透過Web.config或global.asax來做配置,無法讓人透過程式來控制,且執行順序是基於Application life cycle events,每次Request、Response都為固定的。
開發者只能在特定Event去寫程式,不可更改違背其本身設計原則。

至於Middleware 則完全是透過Startup.cs code 而非config檔案來配置
開發人員有更大彈性去自行設計想要做的啟動配置流程。

https://ithelp.ithome.com.tw/upload/images/20210904/20107452ghvszkVFZd.png

請求委派被創建來建立請求管線,請求委派負責處裡每個HTTP Request,
主要透過IApplicationBuilder 型別的Run , Map , 和Use擴充method進行設定。

通常Register這些Middleware方式都是於Startup.cs 的 Configure
對 IApplicationBuilder 型別的app使用 Use方法

public static Microsoft.AspNetCore.Builder.IApplicationBuilder Use (this Microsoft.AspNetCore.Builder.IApplicationBuilder app, Func<Microsoft.AspNetCore.Http.HttpContext,Func<System.Threading.Tasks.Task>,System.Threading.Tasks.Task> middleware);

每個單獨的請求委派可以透過匿名方法內嵌方式或者定義一個可以ReUse的Class中
至於這些可被ReUse的Class就是Middleware了

每個位於Request Pipeline中的Middleware負責接力(呼叫下一個元件)或者適時短路(不往下呼叫)。

內建擴充的普遍以 Use 開頭的方法註冊
預設用MVC template建立的.net core 專案
就有用到
app.UseStaticFiles(); //啟用靜態檔案存取
app.UseRouting(); //啟用路由
app.UseAuthorization(); //身分授權
app.UseEndpoints();//啟用路由端點
這麼多內建擴充的Middleware
其他還有像是
app.UseAuthentication();//身分驗證
app.UseSession();//啟用Session
app.UseHttpsRedirect();//http轉向https
都是屬於內建的middleware

https://ithelp.ithome.com.tw/upload/images/20210904/20107452PEekqhzMQp.png

這裡自行寫一段用Use方法的測試
在初始腳本
Test Code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace MyCore0
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {

        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            //註冊 Middleware 的方法
            app.Use(async (context, next) =>
            {
                await context.Response.WriteAsync("1st Middleware in. \r\n");
                await next.Invoke();//跳至下一個Middleware的方法
                await context.Response.WriteAsync("1st Middleware out. \r\n");
            });

            app.Use(async (context, next) =>
            {
                await context.Response.WriteAsync("2nd Middleware in. \r\n");
                await next.Invoke();//跳至下一個Middleware的方法
                await context.Response.WriteAsync("2nd Middleware out. \r\n");
            });

            //註冊 Middleware 的方法,和Use差別在於不會依序執行下一個 Middleware。
            app.Run(async (context) =>
            {
                await context.Response.WriteAsync("Test 1 \r\n");
            });

            app.Use(async (context, next) =>
            {
                await context.Response.WriteAsync("3rd Middleware in. \r\n");
                await next.Invoke();//跳至下一個Middleware的方法
                await context.Response.WriteAsync("3rd Middleware out. \r\n");
            });

            //註冊 Middleware 的方法,和Use差別在於不會依序執行下一個 Middleware。
            //app.Run(async (context) =>
            //{
            //    await context.Response.WriteAsync("Test 1 \r\n");
            //});

            //if (env.IsDevelopment())
            //{
            //    app.UseDeveloperExceptionPage();
            //}

            //app.UseRouting();

            //app.UseEndpoints(endpoints =>
            //{
            //    endpoints.MapGet("/", async context =>
            //    {
            //        await context.Response.WriteAsync("Hello World!");
            //    });
            //});
        }
    }
}

執行結果
https://ithelp.ithome.com.tw/upload/images/20210904/20107452XCQwcYUlaF.png

這裡可以看到第三個Middleware沒有執行到主要原因是前面呼叫到
IApplicationBuilder.Run()會造成管線短路。
此外也可觀察到註冊順序的重要性,資料傳遞順序是先進後出。

IApplicationBuilder.Run():
Adds a terminal middleware delegate to the application's request pipeline.

public static void Run(this IApplicationBuilder app, RequestDelegate handler)

https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.runextensions.run?view=aspnetcore-3.1

RequestDelegate Delegate
A function that can process an HTTP request.

public delegate Task RequestDelegate(HttpContext context);

https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.requestdelegate?view=aspnetcore-3.1

IApplicationBuilder.Run() 跟IApplicationBuilder.Use()最大差異就在於
Run方法會造成管線短路(因為並未需要傳入next的請求委派)
所以通常Run只在管線最底部被調用。

但是Use方法中即便會傳入next但只要在方法裏頭根本沒使用到next也等同於Run!!!

在這裡練習自己寫自訂的Middleware
(PS:我們在Startup.cs 中Configure 註冊的 Middleware
屬於Global範圍註冊可套用到所有的 Request。)

MyCustomMiddleware.cs:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace MyCore0
{
    public class MyCustomMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger _logger;

        public MyCustomMiddleware(RequestDelegate next, ILoggerFactory logFactory)
        {
            _next = next;
            _logger = logFactory.CreateLogger("MyCustomMiddleware");
        }

        public async Task Invoke(HttpContext httpContext)
        {
            _logger.LogInformation("MyCustomMiddleware executing...");
            await _next(httpContext);
        }
    }    
}

MyCustomMiddlewareExtensions.cs:

using Microsoft.AspNetCore.Builder;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace MyCore0
{
    public static class MyCustomMiddlewareExtensions
    {
        public static IApplicationBuilder UseMyCustomMiddleware(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<MyCustomMiddleware>();
        }
    }
}

Startup.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace MyCore0
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {

        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            //app.UseMiddleware<MyCustomMiddleware>();//寫法1.不用Extension method直接用內建的

            app.UseMyCustomMiddleware();//寫法2.用Extension method

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

會看到在終端輸出也有跑我們自訂的內容
https://ithelp.ithome.com.tw/upload/images/20210904/20107452sBZpGiKput.png

在此我想看IP跟port資訊是捨麼
https://ithelp.ithome.com.tw/upload/images/20210904/20107452ZDZ5mHIsea.png

也可以輸出~~自行客製化

在此還要特別注意
中介軟體順序
https://ithelp.ithome.com.tw/upload/images/20210904/201074527TW0tchUxU.png

寫成是順序要特別留意
比方Authentication一定要在Authorization之上。

Ref:
ASP.NET Core 中介軟體
https://docs.microsoft.com/zh-tw/aspnet/core/fundamentals/middleware/?view=aspnetcore-5.0

ASP.NET Core - Middleware
https://www.tutorialsteacher.com/core/aspnet-core-middleware

ASP.NET Core 中介軟體
https://docs.microsoft.com/zh-tw/aspnet/core/fundamentals/middleware/?view=aspnetcore-3.1

ASP.NET Core 基礎 - Middleware
https://blog.darkthread.net/blog/aspnetcore-middleware-lab/

[Day03] ASP.NET Core 2 系列 - Middleware
https://ithelp.ithome.com.tw/articles/10192682

ASP.NET - 使用自訂中介軟體偵測與修正 ASP.NET Core 應用程式中的 404
https://docs.microsoft.com/zh-tw/archive/msdn-magazine/2016/june/asp-net-use-custom-middleware-to-detect-and-fix-404s-in-asp-net-core-apps

[鐵人賽Day04] - 淺談Middleware
https://ithelp.ithome.com.tw/articles/10203041

What is the difference between IApplicationBuilder.Use() and IApplicationBuilder.Run() C# Asp.net Core?
https://www.tutorialspoint.com/what-is-the-difference-between-iapplicationbuilder-use-and-iapplicationbuilder-run-chash-asp-net-core

How C# ASP.NET Core Middleware is different from HttpModule?
https://www.tutorialspoint.com/how-chash-asp-net-core-middleware-is-different-from-httpmodule

Asp.net HttpHandler vs HttpModule 詳細解說
https://isdaniel.github.io/HttpHandler-HttpModule/
https://isdaniel.github.io/Ithelp-day2/

已同步發表至個人部落格
https://coolmandiary.blogspot.com/2020/11/net-coremiddleware.html


上一篇
.NET Core第3天_使用CLI來創建.NET Core專案_專案架構解析
下一篇
.NET Core第5天_IWebHostEnvironment 的用途是捨麼?
系列文
.NET Core MVC網頁應用開發30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言