如何在ASP.NET Core中获取所有路由的列表?

47
在ASP.NET Core中,是否有一种方法可以查看在启动时定义的所有路由的列表?我们使用IRouteBuilderMapRoute扩展方法来定义路由。
我们正在迁移一个旧的WebAPI项目。在那里,我们可以使用GlobalConfiguration.Configuration.Routes来获取所有路由。
更具体地说,我们是在操作筛选器中执行此操作。
public class MyFilter : ActionFilterAttribute
{      
    public override void OnActionExecuting(ActionExecutingContext actionContext)
    {
        base.OnActionExecuting(actionContext);

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

        // var allRoutes = ???
    }
}
9个回答

39

如果您使用的是ASP.NET Core 3.0+,那么这意味着您正在使用终结点路由,然后您可以使用EndpointDataSource列出所有路由。

IEnumerable<EndpointDataSource>注入到您的控制器/终结点,然后提取您需要的任何内容。它适用于控制器操作、终结点以及部分Razor页面(Razor页面似乎不会公开可用的HTTP方法)。

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

当您访问/-/info/endpoints时,您将获得一个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
  },
]

1
非常简单而且不错的解决方案! - vhugo
1
谢谢!它对我有效。我把方法的签名从public async Task<ActionResult> ListAllEndpoints() 改成了public IActionResult ListAllEndpoints() - scil
为什么要注入IEnumerable<EndpointDataSource>而不只是EndpointDataSource?DI容器(.Net 5/6)中只注册了一个EndpointDataSource - Granger
3
@Granger 这是真的。但是ASP.NET Core旨在支持多个端点源。您/库可以提供另一个源,该源从数据库或上游服务生成端点。最好使用IEnumerable<EndpointDataSource>注入所有实现。请参见https://source.dot.net/#Microsoft.AspNetCore.Routing/EndpointDataSource.cs,e430965f5ce4b2ff,references - abdusco
我想知道这个是否能在最小的API上运行? - undefined

31

要获取所有路由,您需要使用MVC的ApiExplorer部分。您可以将所有操作标记为属性,也可以使用类似于以下约定:

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

在 Startup.cs 中,在 ConfigureServices(...) 方法中添加您的新内容。

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

在您的 ActionFilter 中,您可以使用构造函数注入来获取 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 拥有 RelativePath,这是该路由的路由模板:

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

1
我希望我能给这个点赞100次。在AspNetCore中找到关于ApiExplorer的文档非常困难,而来自Microsoft的Github示例都已经过时了。谢谢! - rrreee
1
仅为澄清,这是否也替代了在MVC 5中使用全局的RouteTable.Routes?这似乎需要经过很多步骤才能枚举应用程序的路由列表;特别是因为路由可以在启动代码中轻松地连续添加。 - James Wilkins
很抱歉,这是一个需要向.NET团队提问的问题。这是一年前推荐给我的方法。 - Dr Rob Lang
1
var apiDescription = actionGroup.First().Items; 使 apiDescription 成为 IReadOnlyList<ApiDescription> 类型,因此 apiDescription.RelativePath 是无效的。 - James Wilkins
这个怎么获取所有的路由?看起来它只获取当前请求的路由。 - Jeremy Holovacs

9

你可以看一下这个很棒的GitHub项目:

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

项目中的自述文件

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

AspNetCore.RouteAnalyzer

查看 ASP.NET Core 项目的所有路由信息。

拾取的截图

screenshot

在你的ASP.NET Core项目中使用

安装NuGet包

PM> Install-Package AspNetCore.RouteAnalyzer

编辑Startup.cs

将以下代码services.AddRouteAnalyzer();和所需的using指令插入到Startup.cs中。

using AspNetCore.RouteAnalyzer; // Add

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

案例1:在浏览器上查看路由信息

将以下代码routes.MapRouteAnalyzer("/routes");插入到Startup.cs中。

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

然后,您可以访问http://..../routes的URL,在浏览器上查看所有路由信息。(此URL /routes可以通过MapRouteAnalyzer()进行自定义。)

screenshot

案例2:在VS输出面板上打印路由

将以下代码块插入到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("");
    });
}

然后你可以在VS输出面板上查看所有路由信息。

screenshot


4
使用 services.AddMvc(options => { options.EnableEndpointRouting = false; }); 禁用 options.EnableEndpointRouting 是解决 aspnet core 2.2 中的一个问题。虽然项目看起来没问题,但如果有自定义路由属性,可能会出现问题。 - Andez

8

以上方法并不成功,因为我想要的是一个完整的URL,我不想手动构建URL,而是让框架来处理解析。所以在使用了 AspNetCore.RouteAnalyzer 和进行了大量谷歌搜索之后,我没有找到一个明确的答案。

以下内容适用于典型的主页控制器和区域控制器:

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

这将在我的简单解决方案中输出以下内容:
/
/Home/Error
/RouteInfo
/RouteInfo/Links
/Area51/SecureArea

上面的操作使用了dotnetcore 3预览版,但我认为它也适用于dotnetcore 2.2。此外,通过这种方式获取URL将考虑任何已经实施的约定,包括在Scott Hanselman的博客中介绍的优秀slugify。

这是我首选的解决方案。我首先尝试了abdusco的答案,它可以正常工作,但由于具有内部构造函数(即;没有CreateInstance诡计)的密封类,我无法进行单元测试。模拟IActionDescriptorCollectionProvider.ActionDescriptors属性要简单得多。 - Jason Slocomb

4
使用 iApplicationBuilder 'app' 对象,可以在 Startup 类的 Configure 方法末尾添加此简单代码片段。在 ASP.NET Core 3.1 中,它可以检索可用的注册路由,并将它们存储在 'theRoutes' 列表中,你可以在调试会话中检查它(就像我一样,因为对我来说足够了),或者记录它等等。
// 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); 
    }
}

3
您可以通过 HttpActionContext 获取 HttpRouteCollection:
actionContext.RequestContext.Configuration.Routes

RequestContext

HttpConfiguration

HttpRouteCollection

-- 在问题更新后 --

ActionExecutingContext具有继承自ControllerContext的RouteData属性,该属性公开了DataTokens属性(这是一个路由值字典)。可能不是您习惯使用的同一集合,但它确实提供了访问该集合的方式:

actionContext.RouteData.DataTokens

数据令牌


2
好的 - 在我的情况下,那是一个空字典。而且RouteData似乎保存与当前路由相关的数据,而不是所有路由的列表。 - clhereistian
actionContext.RequestContext.Configuration.Routes 已经不存在了。 - James Wilkins

0

你可以将它注入到你的控制器中。

IEnumerable<EndpointDataSource> endpointSources

然后它包含了所有映射路由。

_actionDescriptorCollectionProvider仅适用于使用路由属性映射的路由。但在我的情况下,我正在现代化一个旧的mvc应用程序。我使用app.MapControllerRoute映射了所有控制器。


0

这仅用于调试:

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将包含一个路由和类型的字符串数组。

我给这个回答点了踩,因为它是针对ASP.NET(System.Web)的,但是提问者问的是关于ASP.NET Core(Microsoft.AspNetCore)的。 - Dai
这在.Net Core中运行得很好。System.Web是一个适用于.Net Core的有效包。我已经有7年没有碰过.Net Framework了。System.Web是.Net Core库的一部分。 - N-ate
System.Web.Http.GlobalConfiguration.Configuration.Routes类仅适用于.NET Framework的“Web API”,当目标为.NET Core或ASP.NET Core时,它根本无法编译通过。其次,ASP.NET Core不使用静态路由集合。最后,System.Web命名空间不是ASP.NET Core的一部分,除了一些用于HTML编码的类型被保留了下来。亲自查看 - Dai
是的。它正在使用这些类型。谢谢。 - N-ate

0
@Formalist的回答(https://dev59.com/FV4c5IYBdhLWcg3wAWJE#65081126)对我非常有效。我还想额外获取每个方法的[Description("")]属性。为此,我添加了一些代码:
@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); 
        }
    }

获取描述属性的附加代码:
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;
            }
        }
        
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接