自制 .NET Core 路由调试中间件
點擊上方藍字關(guān)注“汪宇杰博客”
導(dǎo)語
本文教大家如何在 .NET Core 應(yīng)用中使用中間件輸出路由信息以便調(diào)試程序。
背景
在 .NET Framework 的上古時代,有個叫做 RouteDebugger 的神器,可以在 MVC 或 Web API 應(yīng)用中輸出當(dāng)前頁面的路由信息,也可查看應(yīng)用中注冊的所有路由信息。它的 NuGet 包(routedebugger)最新版是 2.1.5,更新于 2016年,源于?Phil Haack 大神12年前的文章 https://haacked.com/archive/2008/03/13/url-routing-debugger.aspx?
這個包可以非常直觀的在瀏覽器訪問應(yīng)用的時候,直接在頁面最下方輸出當(dāng)前的路由信息以及全部的路由表。以便于在復(fù)雜的應(yīng)用中幫助程序員擺脫 996。
.NET Core 怎么辦
12年后,已經(jīng)是 .NET Core 的天下了,顯然由于運行機制的不同,.NET Core 無法使用 RouteDebugger 或者改造它的代碼,只能重寫。
雖然社區(qū)已經(jīng)有人寫了一份?AspNetCoreRouteDebugger?但是這個項目有幾個明顯的問題:
https://github.com/ardalis/AspNetCoreRouteDebugger
使用不方便
項目需要用戶手工拷貝它的兩個文件 Routes.cshtml,Routes.cshtml.cs 到自己的工程。并且要自己修改命名空間、做訪問限制等。你以為是 996 的結(jié)束,其實是 007 的開始。
另外,項目默認提供的是 Razor Page 方案,在不使用 Razor Page 的項目里,還需要繼續(xù)手工拷它的 Routes2Controller 去使用。?
只能輸出全部路由
原版 RouteDebugger 解決的最重要的問題之一就是輸出當(dāng)前頁面的路由,因為不是每個公司都按照 MVC 的默認 convention 做項目,很可能URL和 Controller / View 對應(yīng)不起來,程序員接到需求要改代碼很可能找不到改哪個 Action,所以才用 RouteDebugger。而該 .NET Core 項目只能輸出全部路由表而不是當(dāng)前頁面的路由,使用場景很有限。
沒有 NuGet 包
一旦項目有更新,用戶必須時刻關(guān)注作者 GitHub 才行,并需要手工更新代碼,非常不方便。
綜上所述,我決定自己再寫一個 RouteDebugger。它需要做到以下幾點:
.NET Corelish
既然用了 .NET Core,就要用出精髓。.NET Core 的精髓之一在于中間件(Middleware),而獲取路由信息并輸出,顯然最適合用中間件去做,以盡可能的對業(yè)務(wù)代碼實現(xiàn) 0 侵入。
不要輸出到頁面末尾
在用戶的頁面末尾輸出debug信息,看上去很方便,但實際項目中在極端場景下,可能會破壞頁面的功能和顯示樣式,尤其是頁面加載了三方統(tǒng)計、樣式修改插件等。更別說遇到SPA項目了,頁面呈現(xiàn)的原理都不一樣,所以輸出到頁面這套方法已經(jīng)過時。
目前我覺得,輸出到 HTTP Response Header 是更好的做法,不僅不會影響頁面功能及顯示效果,對工具(CURL、瀏覽器F12)等支持也更加友好。
JSON over HTML Table
Json 是當(dāng)今世界的政治正確,它比起 HTML Table,更面向程序員,有更好的工具適配。因此不管是輸出當(dāng)前路由還是全部路由表,我都選擇了 JSON 格式。
自主研發(fā)成功
如果你并不想了解怎么寫,只想拿來就用的話,我已將工程打包成 NuGet 包,并開源:
https://github.com/EdiWang/AspNetCore-RouteDebuggerMiddleware
dotnet add package Edi.RouteDebugger
只要添加到自己應(yīng)用的請求管線即可,推薦僅用于開發(fā)環(huán)境:
if (env.IsDevelopment())
{
? ? app.UseRouteDebugger();
}
這里要注意順序,ASP.NET Core 的中間件順序有講究,得寫在?app.UseEndpoints() 及?app.UseRouting() 的上面。具體原因可參考微軟官方文檔對中間件的介紹:
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/
接下來,打開應(yīng)用中任意一個頁面,只要它有路由信息,就能在 Header 里看見:
若要查看全部路由表,訪問 "/route-debugger" 即可:
代碼解析
想要獲取當(dāng)前請求的路由信息,只要調(diào)用 HttpContext 對象的 GetRouteData() 方法即可。然后序列化為 Json 輸出到 Response Header。
由于我們得先執(zhí)行 _next(context) 才能得到非空的路由信息,而這部操作會導(dǎo)致 HTTP Reponse已經(jīng)開始輸出從而導(dǎo)致 Header 只讀,所以需要一些 workaround 來設(shè)置 Header。最終代碼如下:
private async Task SetCurrentRouteInfo(HttpContext context)
{
? ? var originalBodyStream = context.Response.Body;
? ? await using var responseBody = new MemoryStream();
? ? context.Response.Body = responseBody;
? ? await _next(context);
? ? context.Response.Body.Seek(0, SeekOrigin.Begin);
? ? await new StreamReader(context.Response.Body).ReadToEndAsync();
? ? context.Response.Body.Seek(0, SeekOrigin.Begin);
? ? var rd = context.GetRouteData();
? ? if (null != rd && rd.Values.Any())
? ? {
? ? ? ? var rdJson = JsonSerializer.Serialize(rd.Values);
? ? ? ? context.Response.Headers["current-route"] = rdJson;
? ? }
? ? await responseBody.CopyToAsync(originalBodyStream);
}
獲取全部路由信息得注入一個?IActionDescriptorCollectionProvider?對象,好在 Middleware class 的構(gòu)造函數(shù)可以直接無腦注入。
public async Task Invoke(HttpContext context, IActionDescriptorCollectionProvider provider = null)
{
? ? if (context.Request.Path == "/route-debugger")
? ? {
? ? ? ? if (null != provider)
? ? ? ? {
? ? ? ? ? ? var routes = provider.ActionDescriptors.Items.Select(x => new {
? ? ? ? ? ? ? ? Action = x.RouteValues["Action"],
? ? ? ? ? ? ? ? Controller = x.RouteValues["Controller"],
? ? ? ? ? ? ? ? Name = x.AttributeRouteInfo?.Name,
? ? ? ? ? ? ? ? Template = x.AttributeRouteInfo?.Template,
? ? ? ? ? ? ? ? Contraint = x.ActionConstraints
? ? ? ? ? ? }).ToList();
? ? ? ? ? ? var routesJson = JsonSerializer.Serialize(routes);
? ? ? ? ? ? context.Response.ContentType = "application/json";
? ? ? ? ? ? await context.Response.WriteAsync(routesJson, Encoding.UTF8);
? ? ? ? }
? ? ? ? else
? ? ? ? {
? ? ? ? ? ? await context.Response.WriteAsync("IActionDescriptorCollectionProvider is null", Encoding.UTF8);
? ? ? ? }
? ? }
? ? else
? ? {
? ? ? ? await SetCurrentRouteInfo(context);
? ? }
}
目前這個 .NET Core 版的 RouteDebugger 功能還非?;A(chǔ),非常歡迎大家提建議或PR。
汪宇杰博客
.NET | Azure | 微軟MVP
長按掃碼關(guān)注我們
總結(jié)
以上是生活随笔為你收集整理的自制 .NET Core 路由调试中间件的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在C#中使用RESTful API的几种
- 下一篇: 有哪些你踏入社会才明白的道理