[ASP.NET Core] Middleware介紹

  • 489
  • 0

Middleware是一種配置到應用水管(Pipeline)以處理請求和響應的軟件,所有的 Request(過程是由上往下) 及 Response(過程是由下往上) 都會層層經過這些水管。每個組件:
1. 可選擇是否要將要求傳送到管線中的下一個元件。
2. 可以下一個元件的前後執行工作。

OWIN(Open Web Interface for .NET)介紹

ASP.NET Core 是依據 OWIN規格所實作的網站架構
OWIN 是一個怎麼樣的規格,為什麼要改用OWIN的規格呢?
簡單來說,OWIN就是為了解除應用程式與特定伺服器間的依賴與耦合所設計出來的架構。

由下到上,OWIN架構區分為四層:
Host
最底層,負責載入、啟動及關閉OWIN元件
Server
負責連上TCP Port,建構OWIN Pipeline環境以處理Request
Middleware
所有在OWIN Pipeline中Request處理模組的統稱,小到很簡單的資料壓縮模組,大至ASP.NET Web API都算。
Application
依專案需求所開發的應用程式碼

定義Middleware

Middleware是一種配置到應用水管(Pipeline)以處理請求和響應的軟件,所有的 Request(過程是由上往下) 及 Response(過程是由下往上) 都會層層經過這些水管。每個組件:
1. 可選擇是否要將要求傳送到管線中的下一個元件。
2. 可以下一個元件的前後執行工作。

要定義Middleware需要再 Program.cs 中去做設定
ASP.NET Core 請求管道包含一系列請求委託,依次調用。下圖演示了這一概念。沿黑色箭頭執行。

 

 

 

 

 

 

 

 

 

 

Run

Run 是最後一個Middleware,不會收到 next 參數,後面的Middleware也不會再執行,一般來說不會直接使用。

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();


// Creat customize [Run] example
app.Run(async context => { await context.Response.WriteAsync("Creat customize [Run] example");});
app.Run();

Use

Use 可以用自訂的程式碼邏輯來設定一層Middleware
可以透過呼叫 next(); 來決定是否進入下一個 Middleware

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.Use(async (context, next) => { 
    await context.Response.WriteAsync("Use No1\r\n");
    await next.Invoke();
    await context.Response.WriteAsync("Use No4\r\n");
});
app.Use(async (context, next) => { 
    await context.Response.WriteAsync("Use No2\r\n");
    await next.Invoke();
    await context.Response.WriteAsync("Use No3\r\n");
});
app.Run(async context => {await context.Response.WriteAsync("Run\r\n"); });
app.Run();

如果 Middleware 全部都寫在 Program.cs,程式碼應該難維護,應該把自製 Middleware 邏輯獨立出來。
大部分擴充的 Middleware 都會用一個靜態方法包裝,如:UseMvc()、UseRewriter()等。
自製的 Middleware 當然也可以透過靜態方法包
建立 Middleware 類別不需要額外繼承其它類別或介面,一般的類別即可,範例如下:

Middlewares/CustomMiddleware.cs

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


// 建立一個目錄Middlewares並創建一個CustomMiddleware.cs檔案
namespace webapi.Middlewares
{
    public static class CustomMiddlewareExtetion
    {
        public static void UseCustom(this IApplicationBuilder app)
        {
            app.UseMiddleware<CustomMiddleware>();
        }
    }

    public class CustomMiddleware
    {
        private readonly RequestDelegate _next;


        public CustomMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            await context.Response.WriteAsync("Use No1\r\n");
            await _next.Invoke(context);
            await context.Response.WriteAsync("Use No2\r\n");
        }
    }
}

Program.cs

using webapi.Middlewares;
var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();
app.UseCustom();
app.Run(async context => { await context.Response.WriteAsync("Run\r\n"); });
app.Run();

Map

Map 擴充方法則是用來分支管線的慣例, Map 會依據指定要求路徑的相符項目將要求管線分支。
如果要求路徑以指定路徑為開頭,則會執行分支。
Map可以針對自訂的路由設定獨立的管線(pipeline)

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();
app.Map("/map1", mapApp =>
{
    mapApp.Use(async (context, next) => 
    {
        await context.Response.WriteAsync("Second Middleware in. \r\n");
        await next.Invoke();
        await context.Response.WriteAsync("Second Middleware out. \r\n");
    });
    mapApp.Run(async context =>
    {
        await context.Response.WriteAsync("Second. \r\n");
    });
});

app.Map("/map2", Map2);
app.Map("/map3", Map3);
app.Run();


// Map支援巢狀項目,同時也能一次比對多個線段 /map2/nextMap2
static void Map2(IApplicationBuilder app)
{
    app.Map("/nextMap2", mapApp =>
 {
     mapApp.Use(async (context, next) =>
     {
         await context.Response.WriteAsync("nextMap2 IN. \r\n");
         await next.Invoke();
         await context.Response.WriteAsync("nextMap2 out. \r\n");
     });
     mapApp.Run(async context =>
     {
         await context.Response.WriteAsync("Second. \r\n");
     });
 });
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map 1");
    });
}

static void Map3(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map 2");
    });
}

Middleware順序

下圖顯示ASP.NET Core MVC 和 RazorPage 應用程式的完整要求處理管線,可以查看一般應用程式常用官方提供的Middleware,以及新增自定義Middleware的位置該如何排列。
ASP.NET Core 5之前都會用app.UseEndpoints作為結束端點而ASP.NET Core 6之後則明確定義builder.Services.AddEndpointsApiExplorer();

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();


// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

參考資料

https://ithelp.ithome.com.tw/articles/10238649

https://blog.darkthread.net/blog/aspnetcore-middleware-lab/

https://blog.johnwu.cc/article/ironman-day03-asp-net-core-middleware.html

https://docs.microsoft.com/aspnet/core/fundamentals/routing?view=aspnetcore-6.0