Background

In the ancient days of the .NET Framework, there was a RouteDebugger that outputs routing information for the current page in an MVC or web API app, or sees all the routing information registered in the app. The latest version of its NuGet package is 2.1.5, updated in 2016, from an article by Phil Haack 12 years ago https://haacked.com/archive/2008/03/13/url-routing-debugger.aspx

This package can be very intuitive when the browser accesses the app, it outputs the current routing information and the entire routing table directly at the bottom of the page. 

What about .NET Core

Twelve years later, it's already the world of .NET Core, and it's clear that because of the mechanism changes, .NET Core can't use RouteDebugger. The only option is to rewrite it.

Although there seems to be already a RouteDebugger for ASP.NET Core here https://github.com/ardalis/AspNetCoreRouteDebugger, the project has several obvious problems:

It's not easy to use

The project requires the user to manually copy its two files, Routes.cshtml, Routes.csshtml.cs to their own projects. And you have to modify the namespace yourself, add access restrictions, and so on. In addition, the project targets Razor Page, on a non-Razor page solution, you also have to manually integrate it's Routes2Controller into your code.

It only outputs the entire route table

One of the most important issues that the original RouteDebugger solved was to output the route of the current page, because not every company is doing ASP.NET according to MVC's default convention, it is likely that the URL format doesn't match the Controller and View structure, and the developer would probably not be able to find where the Action is. This .NET Core project, on the other hand, can only output all route tables instead of route data of the current page. This limited usage scenarios.

No NuGet package

Once the project is updated, the user must keep an eye on the author GitHub and need to update the code manually, which is inconvenient.

Making my own RouteDebugger

Based on the facts above, I decided to write my own RouteDebugger, which would address the following ideas:

.NET Corelish

The most ideal way to get the route data in .NET Core is using middleware instead of creating Razor Pages or Controllers. Middleware won't require you to change your Controller / Action code, you can just leave them as is, it's the most clean way for inserting something into the request/response pipeline.

Response Header over Response Body

The origin RouteDebugger append HTML after response body so that the user can see the route data within the page itself. Although it seems to be very convenient, it can cause a few problems. Sometimes it would conflict with your page's JS or CSS. So I decide to put the route data into HTTP response header, which won't affect the page at all. And, response header are much friendly when integrating with tools like CURL or some automation testing kit. 

Json over HTML Table

The Json format is much more friendly comparing to HTML tables when talk to different tools like Postman. Sometimes you would need to integrate tools and APIs with the RouteDebugger, so I choose Json instead of HTML Table for output. 

Edi.RouteDebugger

From the study above, I made an open source library that you can use: https://github.com/EdiWang/Edi.RouteDebugger 

Install from NuGet
dotnet add package Edi.RouteDebugger
Adding the Middleware 

It is recommended that you only use the route debugger in development environment

if (env.IsDevelopment())
{
    app.UseRouteDebugger();
}

Based on the nature of ASP.NET Core Middleware, this piece of code must be added before calling app.UseRouting() and app.UseEndpoints(). If you want to know why, you can learn more about ASP.NET Core Middleware from official document: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-3.1

View Current Route Data
  • Open any page in your application
  • View response header

View All Routes

Access /route-debugger from browser or Postman

The Code

To get the route data from current request, just simply call HttpContext.GetRouteData(). However, the data won't be there unless executing the request first by await _next(context), and this will make the response header read-only. So I have to use a little workaround to set the response 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);
}

To get all routes registered in the application, we need to use IActionDescriptorCollectionProvider. Luckily the built-in DI can inject this directly to a Middleware's constructor. We don't need to worry about the "next" pipeline executing here because this simply terminates the pipeline.

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);
    }
}

Currently, this library got only a few very basic features, any suggestions or PRs are welcome!