45

In ASP.NET Core, is there a way to see a list of all the routes defined in Startup? We are using the MapRoute extension method of IRouteBuilder to define the routes.

We are migrating an older project WebAPI project. There we could use GlobalConfiguration.Configuration.Routes to get all the routes.

More specifically, we are doing this within an action filter.

public class MyFilter : ActionFilterAttribute
{      
    public override void OnActionExecuting(ActionExecutingContext actionContext)
    {
        base.OnActionExecuting(actionContext);

        // This no longer works
        // var allRoutes = GlobalConfiguration.Configuration.Routes;

        // var allRoutes = ???
    }
}
TylerH
  • 20,799
  • 66
  • 75
  • 101
clhereistian
  • 1,261
  • 1
  • 11
  • 19

8 Answers8

38

If you're on ASP.NET Core 3.0+, that means you're using endpoint routing, then you can list all routes with EndpointDataSources.

Inject IEnumerable<EndpointDataSource> to your controller/endpoint then extract anything you need. It works with both controller actions, endpoints, and partially with razor pages (razor pages don't seem to expose available HTTP methods).

[Route("/-/{controller}")]
public class InfoController : Controller
{
    private readonly IEnumerable<EndpointDataSource> _endpointSources;

    public InfoController(
        IEnumerable<EndpointDataSource> endpointSources
    )
    {
        _endpointSources = endpointSources;
    }

    [HttpGet("endpoints")]
    public async Task<ActionResult> ListAllEndpoints()
    {
        var endpoints = _endpointSources
            .SelectMany(es => es.Endpoints)
            .OfType<RouteEndpoint>();
        var output = endpoints.Select(
            e =>
            {
                var controller = e.Metadata
                    .OfType<ControllerActionDescriptor>()
                    .FirstOrDefault();
                var action = controller != null
                    ? $"{controller.ControllerName}.{controller.ActionName}"
                    : null;
                var controllerMethod = controller != null
                    ? $"{controller.ControllerTypeInfo.FullName}:{controller.MethodInfo.Name}"
                    : null;
                return new
                {
                    Method = e.Metadata.OfType<HttpMethodMetadata>().FirstOrDefault()?.HttpMethods?[0],
                    Route = $"/{e.RoutePattern.RawText.TrimStart('/')}",
                    Action = action,
                    ControllerMethod = controllerMethod
                };
            }
        );
        
        return Json(output);
    }
}

when you visit /-/info/endpoints, you'll get a list of routes as JSON:

[
  {
    "method": "GET",
    "route": "/-/info/endpoints", // <-- controller action
    "action": "Info.ListAllEndpoints",
    "controllerMethod": "Playground.Controllers.InfoController:ListAllEndpoints"
  },
  {
    "method": "GET",
    "route": "/WeatherForecast", // <-- controller action
    "action": "WeatherForecast.Get",
    "controllerMethod": "Playground.Controllers.WeatherForecastController:Get"
  },
  {
    "method": "GET",
    "route": "/hello", // <-- endpoint route
    "action": null,
    "controllerMethod": null
  },
  {
    "method": null,
    "route": "/about", // <-- razor page
    "action": null,
    "controllerMethod": null
  },
]
abdusco
  • 9,700
  • 2
  • 27
  • 44
  • 1
    Very simple and nice solution! – vhugo Sep 07 '21 at 19:06
  • 1
    Thank you! It works for me. And i change the sig of the method to `public IActionResult ListAllEndpoints()` from `public async Task ListAllEndpoints()` – scil Oct 03 '21 at 11:24
  • Why inject `IEnumerable` instead of just the `EndpointDataSource`? There's only one `EndpointDataSource` registered in the DI container (.Net 5/6). – Granger Nov 21 '21 at 19:18
  • 3
    @Granger That's true. But ASP.NET Core is designed to support multiple endpoint sources. You / a library can provide another source that generates endpoints from a database, or a service upstream, for instance. It's safer to inject all implementations with `IEnumerable`. See https://source.dot.net/#Microsoft.AspNetCore.Routing/EndpointDataSource.cs,e430965f5ce4b2ff,references – abdusco Nov 21 '21 at 19:51
31

To get at all the routes, you need to use the ApiExplorer part of MVC. You can either mark all your actions with an attribute or use a convention like this one:

public class ApiExplorerVisibilityEnabledConvention : IApplicationModelConvention
{
    public void Apply(ApplicationModel application)
    {
        foreach (var controller in application.Controllers)
        {
            if (controller.ApiExplorer.IsVisible == null)
            {
                controller.ApiExplorer.IsVisible = true;
                controller.ApiExplorer.GroupName = controller.ControllerName;
            }
        }
    }
}

In Startup.cs, add your new in ConfigureServices(...)

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(
        options => 
        {
            options.Conventions.Add(new ApiExplorerVisibilityEnabledConvention());
            options.
        }
}

In your ActionFilter you can then use constructor injection to get the ApiExplorer:

public class MyFilter : ActionFilterAttribute
{      
    private readonly IApiDescriptionGroupCollectionProvider descriptionProvider;

    public MyFilter(IApiDescriptionGroupCollectionProvider descriptionProvider) 
    {
        this.descriptionProvider = descriptionProvider;
    }

    public override void OnActionExecuting(ActionExecutingContext actionContext)
    {
        base.OnActionExecuting(actionContext);

        // The convention groups all actions for a controller into a description group
        var actionGroups = descriptionProvider.ApiDescriptionGroups.Items;

        // All the actions in the controller are given by
        var apiDescription = actionGroup.First().Items.First();

        // A route template for this action is
        var routeTemplate = apiDescription.RelativePath
    }
}

ApiDescription, which has the RelativePath, which is the route template for that route:

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace Microsoft.AspNetCore.Mvc.ApiExplorer
{
    public class ApiDescription
    {
        public string GroupName { get; set; }
        public string HttpMethod { get; set; }
        public IList<ApiParameterDescription> ParameterDescriptions { get; } = new List<ApiParameterDescription>();
        public IDictionary<object, object> Properties { get; } = new Dictionary<object, object>();
        public string RelativePath { get; set; }
        public ModelMetadata ResponseModelMetadata { get; set; }
        public Type ResponseType { get; set; }
        public IList<ApiRequestFormat> SupportedRequestFormats { get; } = new List<ApiRequestFormat>();
        public IList<ApiResponseFormat> SupportedResponseFormats { get; } = new List<ApiResponseFormat>();
    }
}
James Wilkins
  • 6,836
  • 3
  • 48
  • 73
Dr Rob Lang
  • 6,659
  • 5
  • 40
  • 60
  • 1
    I wish I could upvote this 100 times. It's so hard to find docs about the ApiExplorer in AspNetCore, and the samples on github from Microsoft are all out of date. Thank you! – rrreee Aug 18 '16 at 19:33
  • 1
    Just for clarity, is this also the replacement for using the global `RouteTable.Routes` in MVC 5? This seems an awful lot to go through just to enumerate a list of routes for an application; especially since the routes are easily added consecutively in the startup code. – James Wilkins Mar 21 '17 at 03:29
  • I'm afraid that's a question for the .NET team. This was the method that was recommended to me a year ago. – Dr Rob Lang Mar 21 '17 at 09:25
  • 1
    `var apiDescription = actionGroup.First().Items;` makes `apiDescription` of type `IReadOnlyList` so `apiDescription.RelativePath` is not valid. – James Wilkins Mar 22 '17 at 00:45
  • How does this get all the routes? It looks like this only gets the route of the current request. – Jeremy Holovacs Aug 01 '18 at 01:16
9

You can take a look at this awesome GitHub project:

https://github.com/kobake/AspNetCore.RouteAnalyzer

Readme from the project

=======================

AspNetCore.RouteAnalyzer

View all route information for ASP.NET Core project.

Pickuped screenshot

screenshot

Usage on your ASP.NET Core project

Install NuGet package

PM> Install-Package AspNetCore.RouteAnalyzer

Edit Startup.cs

Insert code services.AddRouteAnalyzer(); and required using directive into Startup.cs as follows.

using AspNetCore.RouteAnalyzer; // Add

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddRouteAnalyzer(); // Add
}

Case1: View route information on browser

Insert code routes.MapRouteAnalyzer("/routes"); into Startup.cs as follows.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    ....
    app.UseMvc(routes =>
    {
        routes.MapRouteAnalyzer("/routes"); // Add
        routes.MapRoute(
            name: "default",
            template: "{controller}/{action=Index}/{id?}");
    });
}

Then you can access url of http://..../routes to view all route informations on your browser. (This url /routes can be customized by MapRouteAnalyzer().)

screenshot

Case2: Print routes on VS output panel

Insert a code block as below into Startup.cs.

public void Configure(
    IApplicationBuilder app,
    IHostingEnvironment env,
    IApplicationLifetime applicationLifetime, // Add
    IRouteAnalyzer routeAnalyzer // Add
)
{
    ...

    // Add this block
    applicationLifetime.ApplicationStarted.Register(() =>
    {
        var infos = routeAnalyzer.GetAllRouteInformations();
        Debug.WriteLine("======== ALL ROUTE INFORMATION ========");
        foreach (var info in infos)
        {
            Debug.WriteLine(info.ToString());
        }
        Debug.WriteLine("");
        Debug.WriteLine("");
    });
}

Then you can view all route informations on VS output panel.

screenshot

Rob Streeting
  • 1,675
  • 3
  • 16
  • 27
Leniel Maccaferri
  • 100,159
  • 46
  • 371
  • 480
  • 4
    There is an issue with aspnet core 2.2 which is you will need to disable `options.EnableEndpointRouting` using `services.AddMvc(options => { options.EnableEndpointRouting = false; });`. The project seems ok but doesn't understand if you have custom route attributes. – Andez Mar 05 '19 at 20:56
7

Not been successful with the above, as I wanted a full url which I didn't have to mess around with things to construct the url, but instead let the framework handle the resolution. So following on from AspNetCore.RouteAnalyzer and countless googling and searching, I didn't find a definitive answer.

The following works for me for typical home controller and area controller:

public class RouteInfoController : Controller
{
    // for accessing conventional routes...
    private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider;

    public RouteInfoController(
        IActionDescriptorCollectionProvider actionDescriptorCollectionProvider)
    {
        _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider;
    }

    public IActionResult Index()
    {
        StringBuilder sb = new StringBuilder();

        foreach (ActionDescriptor ad in _actionDescriptorCollectionProvider.ActionDescriptors.Items)
        {
            var action = Url.Action(new UrlActionContext()
            {
                Action = ad.RouteValues["action"],
                Controller = ad.RouteValues["controller"],
                Values = ad.RouteValues
            });

            sb.AppendLine(action).AppendLine().AppendLine();
        }

        return Ok(sb.ToString());
    }

This will output the following in my simple solution:

/
/Home/Error
/RouteInfo
/RouteInfo/Links
/Area51/SecureArea

The above was done using dotnetcore 3 preview but I think it should work with dotnetcore 2.2. Additionally getting the url this way will take into consideration any conventions that have been put in place including the excellent slugify as brought to light on Scott Hanselman's Blog

Andez
  • 5,588
  • 20
  • 75
  • 116
  • This was my preferred solution. I first tried abdusco's answer, which works fine, but I was not able to unit test due to sealed classes with internal constructors (ie; without CreateInstance shenanigans). Mocking the IActionDescriptorCollectionProvider.ActionDescriptors property is much more straightforward. – Jason Slocomb May 24 '21 at 15:05
3

You can get an HttpRouteCollection from the HttpActionContext via:

actionContext.RequestContext.Configuration.Routes

RequestContext

HttpConfiguration

HttpRouteCollection

-- After Question Updated --

The ActionExecutingContext has a RouteData property that it inherits from ControllerContext, which exposes the DataTokens property (which is a route value dictionary). It is probably not the same collection you're used to working with, but it does provide access to that collection:

actionContext.RouteData.DataTokens

DataTokens

pwnyexpress
  • 1,016
  • 7
  • 14
3

Playing with the the iApplicationBuilder 'app' object, I wrote this simple code snippet that you can add at the end of the Configure method in the Startup class. It supposedly retrieves (at least in ASP.NET Core 3.1) the available registered routes. It stores them into the 'theRoutes' list, that you can simply inspect on a debug session (like I did, since it was enough for me), or you can log it, etc.

// Put this code at the end of 'Configure' method in 'Startup' class (ASP.Net Core 3.1)
var theRoutes = new List<string>();
var v1 = app.Properties["__EndpointRouteBuilder"];
var v2 = (System.Collections.Generic.List<Microsoft.AspNetCore.Routing.EndpointDataSource>)(v1.GetType().GetProperty("DataSources").GetValue(v1, null));
foreach (var v3 in v2)
{
    foreach (var v4 in v3.Endpoints)
    {
        var v5 = (Microsoft.AspNetCore.Routing.Patterns.RoutePattern) (v4.GetType().GetProperty("RoutePattern").GetValue(v4, null));
        theRoutes.Add(v5.RawText); 
    }
}
Formalist
  • 349
  • 3
  • 12
0

This is useful for debugging only:

var routes = System.Web.Http.GlobalConfiguration.Configuration.Routes;
var field = routes.GetType().GetField("_routeCollection", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
var collection = field.GetValue(routes) as System.Web.Routing.RouteCollection;
var routeList = collection
    .OfType<IEnumerable<System.Web.Routing.RouteBase>>()
    .SelectMany(c => c)
    .Cast<System.Web.Routing.Route>()
    .Concat(collection.OfType<System.Web.Routing.Route>())
    .Select(r => $"{r.Url} ({ r.GetType().Name})")
    .OrderBy(r => r)
    .ToArray();

routeList will contain a string array of routes and types.

N-ate
  • 6,051
  • 2
  • 40
  • 48
  • I downvoted because this answer is for ASP.NET (`System.Web`), but the OP asked about ASP.NET Core (`Microsoft.AspNetCore`). – Dai Aug 12 '23 at 00:41
  • This works fine in .Netcore. System.Web is a valid package for .Netcore. I haven't touched .NetFramework in 7 years now. System.Web is part of .Netcore libraries. – N-ate Aug 15 '23 at 12:29
  • The `System.Web.Http.GlobalConfiguration.Configuration.Routes` class only applies to "Web API" for .NET Framework: it simply won't compile when targeting .NET Core or ASP.NET Core. Secondarily, ASP.NET Core doesn't use a static route-collection. Finally, the `System.Web` namespace is **not** part of ASP.NET Core: excepting a handful of types used for HTML-encoding that were carried over. [See for yourself](https://learn.microsoft.com/en-us/dotnet/api/?view=aspnetcore-6.0&term=system.web). – Dai Aug 17 '23 at 23:45
  • Yes. It is using the types. Thank you. – N-ate Aug 20 '23 at 18:35
0

You can inject that on your controller.

IEnumerable<EndpointDataSource> endpointSources

Then it contains all of mapped routes.

_actionDescriptorCollectionProvider works only for routes mapped with route attribute. But in my case I was modernizing an old mvc application. I mapping all of my controllers with app.MapControllerRoute.