How to get a list of all routes in ASP.NET Core?
Asked Answered
P

10

47

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 = ???
    }
}
Papotto answered 10/2, 2015 at 15:42 Comment(0)
H
41

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
  },
]
Hinrichs answered 7/2, 2021 at 10:6 Comment(6)
Very simple and nice solution!Lieselotteliestal
Thank you! It works for me. And i change the sig of the method to public IActionResult ListAllEndpoints() from public async Task<ActionResult> ListAllEndpoints()Suzisuzie
Why inject IEnumerable<EndpointDataSource> instead of just the EndpointDataSource? There's only one EndpointDataSource registered in the DI container (.Net 5/6).Indium
@Indium 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<EndpointDataSource>. See source.dot.net/#Microsoft.AspNetCore.Routing/…Hinrichs
I wonder if this will work with minimal apis?Polypetalous
Is there a solution pre-endpoint routing?Catenoid
C
32

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>();
    }
}
Consent answered 2/3, 2016 at 14:40 Comment(5)
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!Headache
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.Erde
I'm afraid that's a question for the .NET team. This was the method that was recommended to me a year ago.Consent
var apiDescription = actionGroup.First().Items; makes apiDescription of type IReadOnlyList<ApiDescription> so apiDescription.RelativePath is not valid.Erde
How does this get all the routes? It looks like this only gets the route of the current request.Dc
J
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

Jacklin answered 19/5, 2018 at 2:57 Comment(1)
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.Housework
H
8

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

Housework answered 8/3, 2019 at 22:25 Comment(1)
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.Tabernacle
P
4

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); 
    }
}
Putnem answered 30/11, 2020 at 20:59 Comment(0)
C
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

Chalkstone answered 10/2, 2015 at 18:17 Comment(2)
Ok - that is an empty dictionary in my case. And RouteData appears to hold data pertaining to the current route, not a list of all routes.Papotto
actionContext.RequestContext.Configuration.Routes does not exist anymore.Erde
D
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.

Diptych answered 7/10, 2019 at 18:44 Comment(4)
I downvoted because this answer is for ASP.NET (System.Web), but the OP asked about ASP.NET Core (Microsoft.AspNetCore).Predict
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.Diptych
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.Predict
Yes. It is using the types. Thank you.Diptych
G
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.

Gunpaper answered 15/6, 2022 at 9:2 Comment(0)
L
0

The answer of @Formalist (https://mcmap.net/q/296922/-how-to-get-a-list-of-all-routes-in-asp-net-core) worked like a charm for me. I wanted to additionally get the [Description("")] attribute of every method. To do this, I added some code:

Original code of @Formalist:

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

Additional code to get the description attribute:

allRoutesOfEndpoint = new();
var appProperties = app.Properties["__EndpointRouteBuilder"];
var endpointDataSources = (List<Microsoft.AspNetCore.Routing.EndpointDataSource>)(appProperties.GetType().GetProperty("DataSources").GetValue(appProperties, null));
foreach (var endpointDataSource in endpointDataSources)
{
    foreach (var endpoint in endpointDataSource.Endpoints)
    {
        var routePattern = (Microsoft.AspNetCore.Routing.Patterns.RoutePattern)(endpoint.GetType().GetProperty("RoutePattern").GetValue(endpoint, null));
        allRoutesOfEndpoint.Add(routePattern.RawText);

        // Get the Description attribute of the method
        string methodDescription = "";
        var controllerActionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
        
        if (controllerActionDescriptor != null)
        {
            string fullMethodName = controllerActionDescriptor.ControllerTypeInfo.FullName + "." + controllerActionDescriptor.ActionName;
            string typeName = fullMethodName.Substring(0, fullMethodName.LastIndexOf('.'));
            string methodName = fullMethodName.Substring(fullMethodName.LastIndexOf('.') + 1);
            Type type = Type.GetType(typeName);
            var methodInfo = type.GetMethod(methodName);

            if (methodInfo != null)
            {
                var attributes = methodInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);

                if (attributes.Length > 0)
                    methodDescription = ((DescriptionAttribute)attributes[0]).Description;
            }
        }
        
    }
}
Liggins answered 13/11, 2023 at 7:28 Comment(0)
S
0

For .Net 8, I had to update the solution from @devbf and @Formalist

I changed this section

var v1 = app.Properties["__EndpointRouteBuilder"];
var v2 = (System.Collections.Generic.List<Microsoft.AspNetCore.Routing.EndpointDataSource>)(v1.GetType().GetProperty("DataSources").GetValue(v1, null));

to

TypeInfo typeInfo = typeof(WebApplication).GetTypeInfo();
var v1 = typeInfo.DeclaredFields.Where(e => e.Name == "_dataSources").FirstOrDefault();
var v2 = (System.Collections.Generic.List<Microsoft.AspNetCore.Routing.EndpointDataSource>)v1.GetValue(app);
Shrub answered 20/3, 2024 at 18:41 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.