vlambda博客
学习文章列表

自制 .NET Core 路由调试中间件


导语

本文教大家如何在 .NET Core 应用中使用中间件输出路由信息以便调试程序。

背景


在 .NET Framework 的上古时代,有个叫做 RouteDebugger 的神器,可以在 MVC 或 Web API 应用中输出当前页面的路由信息,也可查看应用中注册的所有路由信息。它的 NuGet 包(routedebugger)最新版是 2.1.5,更新于 2016年,源于 Phil Haack 大神12年前的文章 https://haacked.com/archive/2008/03/13/url-routing-debugger.aspx 

这个包可以非常直观的在浏览器访问应用的时候,直接在页面最下方输出当前的路由信息以及全部的路由表。以便于在复杂的应用中帮助程序员摆脱 996。

自制 .NET Core 路由调试中间件

.NET Core 怎么办


12年后,已经是 .NET Core 的天下了,显然由于运行机制的不同,.NET Core 无法使用 RouteDebugger 或者改造它的代码,只能重写。

虽然社区已经有人写了一份 AspNetCoreRouteDebugger 但是这个项目有几个明显的问题:

https://github.com/ardalis/AspNetCoreRouteDebugger

  1. 使用不方便

    项目需要用户手工拷贝它的两个文件 Routes.cshtml,Routes.cshtml.cs 到自己的工程。并且要自己修改命名空间、做访问限制等。你以为是 996 的结束,其实是 007 的开始。

    另外,项目默认提供的是 Razor Page 方案,在不使用 Razor Page 的项目里,还需要继续手工拷它的 Routes2Controller 去使用。 

  2. 只能输出全部路由

    原版 RouteDebugger 解决的最重要的问题之一就是输出当前页面的路由,因为不是每个公司都按照 MVC 的默认 convention 做项目,很可能URL和 Controller / View 对应不起来,程序员接到需求要改代码很可能找不到改哪个 Action,所以才用 RouteDebugger。而该 .NET Core 项目只能输出全部路由表而不是当前页面的路由,使用场景很有限。

  3. 没有 NuGet 包

    一旦项目有更新,用户必须时刻关注作者 GitHub 才行,并需要手工更新代码,非常不方便。

综上所述,我决定自己再写一个 RouteDebugger。它需要做到以下几点:

.NET Corelish


既然用了 .NET Core,就要用出精髓。.NET Core 的精髓之一在于中间件(Middleware),而获取路由信息并输出,显然最适合用中间件去做,以尽可能的对业务代码实现 0 侵入。

不要输出到页面末尾


在用户的页面末尾输出debug信息,看上去很方便,但实际项目中在极端场景下,可能会破坏页面的功能和显示样式,尤其是页面加载了三方统计、样式修改插件等。更别说遇到SPA项目了,页面呈现的原理都不一样,所以输出到页面这套方法已经过时。

目前我觉得,输出到 HTTP Response Header 是更好的做法,不仅不会影响页面功能及显示效果,对工具(CURL、浏览器F12)等支持也更加友好。

JSON over HTML Table


Json 是当今世界的政治正确,它比起 HTML Table,更面向程序员,有更好的工具适配。因此不管是输出当前路由还是全部路由表,我都选择了 JSON 格式。

自主研发成功


如果你并不想了解怎么写,只想拿来就用的话,我已将工程打包成 NuGet 包,并开源:

https://github.com/EdiWang/AspNetCore-RouteDebuggerMiddleware

dotnet add package Edi.RouteDebugger

只要添加到自己应用的请求管线即可,推荐仅用于开发环境:

if (env.IsDevelopment())

{

    app.UseRouteDebugger();

}

这里要注意顺序,ASP.NET Core 的中间件顺序有讲究,得写在 app.UseEndpoints() 及 app.UseRouting() 的上面。具体原因可参考微软官方文档对中间件的介绍:

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/

接下来,打开应用中任意一个页面,只要它有路由信息,就能在 Header 里看见:

自制 .NET Core 路由调试中间件

若要查看全部路由表,访问 "/route-debugger" 即可:

自制 .NET Core 路由调试中间件

代码解析


想要获取当前请求的路由信息,只要调用 HttpContext 对象的 GetRouteData() 方法即可。然后序列化为 Json 输出到 Response Header。

由于我们得先执行 _next(context) 才能得到非空的路由信息,而这部操作会导致 HTTP Reponse已经开始输出从而导致 Header 只读,所以需要一些 workaround 来设置 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 的构造函数可以直接无脑注入。

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 功能还非常基础,非常欢迎大家提建议或PR。

自制 .NET Core 路由调试中间件
自制 .NET Core 路由调试中间件

汪宇杰博客

.NET | Azure | 微软MVP