【问题标题】:How to get a list of all routes in ASP.NET Core?如何获取 ASP.NET Core 中所有路由的列表?
【发布时间】:2021-12-28 11:52:21
【问题描述】:

在 ASP.NET Core 中,有没有办法查看 Startup 中定义的所有路由的列表?我们使用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 = ???
    }
}

【问题讨论】:

    标签: c# asp.net-core routes asp.net-core-mvc


    【解决方案1】:

    您可以通过以下方式从 HttpActionContext 获取 HttpRouteCollection:

    actionContext.RequestContext.Configuration.Routes
    

    RequestContext

    HttpConfiguration

    HttpRouteCollection

    -- 问题更新后--

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

    actionContext.RouteData.DataTokens
    

    DataTokens

    【讨论】:

    • 好的 - 在我的情况下这是一个空字典。 RouteData 似乎保存与当前路线有关的数据,而不是所有路线的列表。
    • actionContext.RequestContext.Configuration.Routes 不存在了。
    【解决方案2】:

    要获取所有路由,您需要使用 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>();
        }
    }
    

    【讨论】:

    • 我希望我能投票 100 次。在 AspNetCore 中很难找到有关 ApiExplorer 的文档,而且微软在 github 上的示例都已过时。谢谢!
    • 为了清楚起见,这也是在 MVC 5 中使用全局 RouteTable.Routes 的替代品吗?仅仅为了枚举应用程序的路由列表,这似乎是一件非常困难的事情。特别是因为路由很容易在启动代码中连续添加。
    • 恐怕这是 .NET 团队的问题。这是一年前推荐给我的方法。
    • var apiDescription = actionGroup.First().Items; 使 apiDescription 类型为 IReadOnlyList&lt;ApiDescription&gt;,因此 apiDescription.RelativePath 无效。
    • 这是如何获取所有路由的?看起来这只是获取当前请求的路由。
    【解决方案3】:

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

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

    项目自述文件

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

    AspNetCore.RouteAnalyzer

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

    截取的截图

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

    安装 NuGet 包

    PM&gt; Install-Package AspNetCore.RouteAnalyzer

    编辑 Startup.cs

    在 Startup.cs 中插入代码 services.AddRouteAnalyzer(); 和所需的 using 指令,如下所示。

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

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

    在 Startup.cs 中插入代码routes.MapRouteAnalyzer("/routes");,如下所示。

    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,在你的浏览器上查看所有的路由信息​​。 (此网址/routes可通过MapRouteAnalyzer()自定义。)

    案例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输出面板查看所有路由信息了。

    【讨论】:

    • aspnet core 2.2 存在一个问题,您需要使用services.AddMvc(options =&gt; { options.EnableEndpointRouting = false; }); 禁用options.EnableEndpointRouting。该项目似乎还可以,但不明白您是否有自定义路由属性。
    【解决方案4】:

    上面没有成功,因为我想要一个完整的 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 preview 完成的,但我认为它应该适用于 dotnetcore 2.2。此外,以这种方式获取 url 将考虑任何已实施的约定,包括 Scott Hanselman's Blog

    【讨论】:

    • 这是我的首选解决方案。我首先尝试了 abdusco 的答案,效果很好,但由于具有内部构造函数的密封类(即没有 CreateInstance 恶作剧),我无法进行单元测试。模拟 IActionDescriptorCollectionProvider.ActionDescriptors 属性要简单得多。
    【解决方案5】:

    这仅对调试有用:

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

    【讨论】:

      【解决方案6】:

      使用 iApplicationBuilder 'app' 对象,我编写了这个简单的代码 sn-p,您可以将其添加到 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); 
          }
      }
      

      【讨论】:

        【解决方案7】:

        如果您使用的是 ASP.NET Core 3.0+,这意味着您使用的是endpoint routing,那么您可以使用EndpointDataSources 列出所有路由。

        IEnumerable&lt;EndpointDataSource&gt; 注入您的控制器/端点,然后提取您需要的任何内容。它适用于控制器操作、端点,部分适用于 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
          },
        ]
        

        【讨论】:

        • 非常简单又不错的解决方案!
        • 谢谢!这个对我有用。我将方法的信号从public async Task&lt;ActionResult&gt; ListAllEndpoints()更改为public IActionResult ListAllEndpoints()
        • 为什么注入IEnumerable&lt;EndpointDataSource&gt; 而不仅仅是EndpointDataSource? DI 容器(.Net 5/6)中只有一个EndpointDataSource 注册。
        • @Granger 是的。但 ASP.NET Core 旨在支持多个端点源。例如,您/一个库可以提供从数据库或上游服务生成端点的另一个源。使用IEnumerable&lt;EndpointDataSource&gt; 注入所有实现会更安全。见source.dot.net/#Microsoft.AspNetCore.Routing/…
        猜你喜欢
        • 2017-06-14
        • 2020-10-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-04-03
        • 2021-10-14
        • 2016-11-19
        相关资源
        最近更新 更多