什麼是中介軟體?中介軟體簡單的說就是可以讓你方便的插入你想要的邏輯在一系列http的流程中。而Dotnet Core所指的中介軟體(Middleware),就是針對Request pipeline插入客製化邏輯的相關動作。所以有了這樣的設計模式,可以設計出非常健壯的網路程式應用,可以隨時抽換各式各樣的邏輯。

中介軟體的原理

不知道大家還記不記得第二篇有暗藏玄機,就是在GenericWebHostBuilder裡面注入了一個重要的東西,就是ApplicationBuilderFactory,它裡面new了一個ApplicationBuilder,其中的Build方法就是創建Request pipeline的核心。可以參考源碼

private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();

public RequestDelegate Build()
{
    RequestDelegate app = context =>
    {
        ...
        context.Response.StatusCode = 404;
        return Task.CompletedTask;
    };

    foreach (var component in _components.Reverse())
    {
        app = component(app);
    }
    return app;
}

public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
{
    _components.Add(middleware);
    return this;
}
  • _components是一個委派方法的列表變數,input output都是RequestDelegate。先新增一個404的委派方法,再根據注入的中介軟體依序加入。
  • 第二篇有提到的GenericWebHostService啟動實作裡可以發現StartAsync使用了Build方法初始化,也就是放入第一個中介軟體,就是404回應。
  • 最後可以看到有Use方法,可以往_components裡面加入你想要的Request pipeline。

所以我們可以總結的說,中介軟體就是一個處理http請求與響應的元件,並由多個中介軟體構成Request pipeline;每一請求的路徑都可以自己定義,這也就是Dotnet Core的核心,例如:MVC,Websocket等,都是藉由中介軟體掛載上去的。有沒有一種醍醐灌頂,迎刃而解的感覺。


中介軟體的使用

要使用中介軟體很簡單,只要在第三篇中提到的Startup裡面,Configure方法裡面發現其中一個參數是IApplicationBuilder(就是下面程式的app),因此就可以使用Use方法來把中介軟體新增進去,範例如下面程式碼。

app.Use(async (context, next) =>
{
    Console.WriteLine("Use");
    await next();
});

我想大家應該多多少少也看過其他使用方法Map MapWhen…。其實這些都是用Use方法的延伸,我們直接來看看差異。

RunMapMapWhenUseUseWhen
創建新管道不會不會
可否回到主管道不會不會不會
可否嵌套使用不可以可以可以不可以可以

簡單的小結一下:
你只要簡單的想成Map MapWhen UseWhen都是去開分支。只是差異在UseWhen會回到主管道就行了。每個分支裡面都需要用Use再新增你想要的功能。若要使用Run,就必須放在每個分支的最後面。是不是很簡單呢~
最後提供一個嵌套的範例給大家參考參考:第一層是/get,裡面一層是/user,所以call /get/user 就可以跑進去。

app.MapWhen(context => context.Request.Path.StartsWithSegments("/get"), app =>
{
    app.MapWhen(context => context.Request.Path.ToString().Contains("user"), app =>
    {
        app.Use(async (context, next) =>
        {
            Console.WriteLine("MapWhen get user"); 
            await next();
        });
    });
});

最後再介紹一個用class新增中介軟體的方法,就是UseMiddleware。這class有一個InvokeAsync方法,以及一個next RequestDelegate用於調用下一個Taks用。詳細使用方法與範例我會放在最後自行開發中介軟體的Section。


中介軟體的注意事項

  • 如果要將請求發送到管道中的下一個中介軟體,一定要記得呼叫next(),否則會導致管道短路,後續的中介軟體將不會被執行。
  • 如果已經開始給客戶端發送Response,請千萬不要調用next(),也不要對Response進行任何更改,否則將拋出異常。
  • 可以通過context.Response.HasStarted來判斷Response是否已回傳。
  • 管道是嵌套式新增,所以要注意順序問題,以及callback回來的影響。下圖為經典示例圖。

總之,每一個箭頭以及每一個logic都需要注意,這樣才能確保中介軟體的正確性喔。


開發自己的中介軟體

最後就來展示一下,如何自己開發一個中介軟體,我們就使用UseMiddleware這擴展方法,實現一個接收所有http的Exception的中介軟體吧!
直接上程式:

public class ExceptionHandleMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;
    public ExceptionHandleMiddleware(RequestDelegate next, ILogger<ExceptionHandleMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception exception)
        {
            _logger.LogError($"Something went wrong: {exception}");
            if (context.Response.HasStarted)
            {
                _logger.LogWarning("The response has already started, the Exception Middleware will not be executed");
                throw;
            }
            await HandleExceptionAsync(context, exception);
        }
    }

    private static Task HandleExceptionAsync(HttpContext context, Exception exception)
    {
        context.Response.ContentType = "application/json";
        context.Response.StatusCode = StatusCodes.Status500InternalServerError;

        return context.Response.WriteAsync(
            $"{context.Response.StatusCode} Internal Server Error."
        );
    }
}
  • 你可以發現這個class一定要有一個_next作為呼叫下一個Task之用。
  • 其中要有一個方法InvokeAsync來寫邏輯。
  • 邏輯相當簡單,就是直接呼叫next跑下一個Task,並用try catch接住所有Exception。(也可以客製化接住你自己所定義的Exception)
  • 這邊有個例外狀況,就是如果Response使已經有執行的,那就跳出去,返回response即可,但是要印出warning。
  • 最後就是HandleExceptionAsync方法,主要就是隱藏錯誤細節,只回覆500錯誤即可。

如何加入Dotnet core

要把這個class注入Dotnet core主要有兩個方法,第一個就是用DI,先把ExceptionHandleMiddleware注入,然後再Startup裡面使用UseMiddleware方法即可。但這是比較不推薦的做法,以下使用Extensions擴展方法來使用比較好些。

// 擴展方法
public static class ExceptionHandleMiddlewareExtensions 
{ 
    public static IApplicationBuilder UseExceptionHandleMiddleware(this IApplicationBuilder app)
    {
        app.UseMiddleware<ExceptionHandleMiddleware>(); 
    } 
}
// Startup.cs
public class Startup 
{ 
    public void Configure(IApplicationBuilder app) 
    { 
        app.UseExceptionHandleMiddleware(); 
    } 
}

以上把中介軟體大致上介紹完畢,中介軟體可說是Dotnet core的另一個核心,如此就可以輕鬆把玩Dotnet core摟~


.Net系列文章