什麼是中介軟體?中介軟體簡單的說就是可以讓你方便的插入你想要的邏輯在一系列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方法的延伸,我們直接來看看差異。
Run | Map | MapWhen | Use | UseWhen | |
---|---|---|---|---|---|
創建新管道 | 不會 | 會 | 會 | 不會 | 會 |
可否回到主管道 | 不會 | 不會 | 不會 | 會 | 會 |
可否嵌套使用 | 不可以 | 可以 | 可以 | 不可以 | 可以 |
簡單的小結一下:
你只要簡單的想成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系列文章