自制 .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 怎么办

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 里看见:

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

代码解析

想要获取当前请求的路由信息,只要调用 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 | Azure | 微软MVP

长按扫码关注我们

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/310260.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

玉柴spn码故障对照表_后处理的故障不总是尿素泵故障,也有可能是这些原因

之前说到后处理故障,解决的都是柴油机尿素泵、喷嘴的,而SCR箱同样是一个重要的部件,它的作用就是将尿素液与尾气中的氮氧化物充分混合并发生化学反应的场所,目前重卡SCR箱集SCR催化器和发动机排气消声器与一体。整体材料为不锈钢&…

SpringBoot中Bean按条件装配

Conditional条件装配 Conditional是Spring Framework提供的一个核心功能注解,这个注解的作用是提供自动装配的条件限制,一般我们在用Configuration,Bean的时候使用它。也就是我们在自定义Bean的注入的时候,我们可以通过Condition…

定义一个手表_华米Amazfit Pop评测:一款功能全面的“性价比”手表

目前的智能手表虽然品牌、型号众多,但基本可以分为二种类型:第一种为入门级智能手表,其功能单一与智能手环差不多,但胜在屏幕大、能够带来更好的观感且价格便宜;第二种为旗舰级智能手表,功能全面、硬件水准…

[Java基础]复制文件的异常处理try...catch...finally的做法

代码如下: package ErrorOperatorPack;import java.io.FileReader; import java.io.FileWriter; import java.io.IOException;public class CopyFileDemo01 {public static void main(String[] args){}private static void method() {FileReader fr null;FileWriter fw null…

有哪些你踏入社会才明白的道理

不知不觉已经工作10多年,从一个懵懂的大学生到被社会无情毒打,终于成长一个职场老鸟。最近几天在胡思乱想,这10多年不少认知和感悟,如果10年前有人能告诉我,我会不会少走很多很多弯路?读书的时候&#xff0…

手把手教你 git revert merge

开发中git分支管理 研发流程 从develop分支切出一个新分支,根据是功能还是bug,命名为id-xxx 或 id-fixbug-*。开发者完成开发,提交分支到远程仓库。开发者发起merge请求,将新分支请求merge到develop分支,并提醒code r…

如何把自己的经历写成小说_古天乐的经历教会我们:如何在被欺骗以后改善自己的心理状态...

众所周知,这个只有太阳能黑他的男人,早年未发迹时曾干过泊车小弟等工作,后来作为模特经纪人接触娱乐圈,传闻某次模特迟到,古爷临时救场,算是正式踏入娱圈,出现在无数大牌歌星的MV里,…

从GC的SuppressFinalize方法带你深刻认识Finalize底层运行机制

如果你经常看开源项目的源码,你会发现很多Dispose方法中都有这么一句代码: GC.SuppressFinalize(this); ,看过一两次可能无所谓,看多了就来了兴趣,这篇就跟大家聊一聊。一:背景1. 在哪发现的相信现在Mysql在…

NIO工作方式浅析

java Socket 工作机制 Socket是描述计算机之前相互通信的一种抽象功能。通过基于TCP/IP的流套接字协议建立连接A机器B机器通信—建立Socket连接—通过TCP连接(端口号指定唯一应用)----IP寻址(寻找唯一主机)----最终找到唯一主机上…

bufferedimage生成的图片模糊_Kaptcha图片验证码工具

阅读文本大概需要3分钟。验证码的作用图片验证码自从诞生以来从未被抛弃,依然发出属于它所应有的光。验证码经常验证如下一些场景。1、用户登录,防止机器人登录2、论坛留言,防止恶意灌水3、短信验证码发送,防止盗刷短信Kaptcha 简…

[Java基础]对象(反)序列化流

对象序列化流: 代码如下: package ObjectOutputStreamPack;import java.io.Serializable;public class Student implements Serializable {private String name;private int age;public Student() {}public Student(String name, int age) {this.name name;this.age age;}pu…

C# 9 新特性:代码生成器、编译时反射

前言今天 .NET 官方博客宣布 C# 9 Source Generators 第一个预览版发布,这是一个用户已经喊了快 5 年特性,今天终于发布了。简介Source Generators 顾名思义代码生成器,它允许开发者在代码编译过程中获取查看用户代码并且生成新的 C# 代码参与…

I/O性能与可靠性

I/O调优 磁盘I/O优化 性能检测: 压力测试应用程序,观察系统I/O wait指标是否正常,例如有n个CPU,利息情况下I/O wait参数不超过25%,如果超过,就是这个程序的瓶颈就是在IO操作上了可以用iostat命令查看另外…

.NET开源工具类库:Masuit.Tools

【开源框架】| 通用工具类库这是恰童鞋骚年的第223篇原创文章本文介绍一个我的同事【懒得勤快】(人称勤快哥,我们叫他骚哥)写的一个.NET开源工具类库项目,包含一些常用的操作类,大都是静态类,加密解密&…

[Java基础]字节,字符打印流

代码如下: package PrintWriterPack;import java.io.FileNotFoundException; import java.io.PrintWriter;public class PrintWriterDemo {public static void main(String[] args) throws FileNotFoundException {PrintWriter pw new PrintWriter("D:\\Java…

javaI/O包中的包装模式

设计模式解析–适配器模式 对适配器模式功能比较好理解,就是讲一个类的接口换成客户端所能接受的另外一个接口,从而使两国接口不匹配而无法在一起工作的两个类能在一起工作。 适配器的结构 适配器UML图如下 Target(目标接口)&…

DevOps vs. Agile:它们有什么共同点?

导语DevOps与Agile有很多不同,但它们之间仍可发现很多共同点,这篇文章为读者揭晓。正文DevOps和Agile之间有着明显的关系。Agile是方法论,Scrum是框架,并DevOps随着看板也落在了Agile的“伞”下。精益,大规模的Scrum&a…

[Java基础]Properties

代码如下: package PropertiesPack;import java.util.Properties; import java.util.Set;public class PropertiesDemo01 {public static void main(String[] args){Properties prop new Properties();prop.put("001","Tom");prop.put("002",&…