我可以从 AspNetCore FilterContext 获取 RouteTemplate 吗?

Dan*_*ach 5 asp.net-core-mvc asp.net-core asp.net-core-routing

在 AspNetCore 中,给定 FilterContext,我希望获得一个路由模板,例如 {controller}/{action}/{id?}

在 Microsoft.AspNet.WebApi 中,我可以从以下位置获取路由模板: HttpControllerContext.RouteData.Route.RouteTemplate

在 System.Web.Mvc 中我可以从以下位置得到这个: ControllerContext.RouteData.Route as RouteBase

在AspNetCore中有: FilterContext.ActionDescriptor.AttributeRouteInfo.Template

然而,并非所有路由都是属性路由。

根据检查属性是否不可用,可以从以下位置组装默认路由和/或映射路由: FilterContext.RouteData.Routers.OfType<Microsoft.AspNetCore.Routing.RouteBase>().First() 但我正在寻找记录的或简单的更好的方法。

gli*_*ire 5

更新(2021 年 1 月 24 日)
有一种更简单的方法可以RoutePattern直接通过HttpContext.

    FilterContext filterContext;
    var endpoint = filterContext.HttpContext.GetEndpoint() as RouteEndpoint;
    var template = endpoint?.RoutePattern?.RawText;
    if (template is null)
        throw new Exception("No route template found, that's absurd");
    Console.WriteLine(template);
Run Code Online (Sandbox Code Playgroud)

GetEndpoint()是命名空间EndpointHttpContextExtensions内的类中提供的扩展方法Microsoft.AspNetCore.Http

旧答案(工作太多)

ASP.NET Core 应用程序的所有路由构建器(至少对于 3.1)都是通过 公开和注册的IEndpointRouteBuilder,但不幸的是,这没有在 DI 容器中注册,因此您无法直接获取它。我唯一拥有的地方看到这个接口被暴露,是在中间件中。因此,您可以使用这些中间件之一构建集合或字典,然后将其用于您的目的。
例如

程序.cs

用于构建端点集合/字典的扩展类

    internal static class IEndpointRouteBuilderExtensions
    {
        internal static void BuildMap(this IEndpointRouteBuilder endpoints)
        {
            foreach (var item in endpoints.DataSources)
                foreach (RouteEndpoint endpoint in item.Endpoints)
                {
                    /* This is needed for controllers with overloaded actions
                     * Use the RoutePattern.Parameters here 
                     * to generate a unique display name for the route 
                     * instead of this list hack 
                     */
                    
                    if (Program.RouteTemplateMap.TryGetValue(endpoint.DisplayName, out var overloadedRoutes))
                        overloadedRoutes.Add(endpoint.RoutePattern.RawText);
                    else
                        Program.RouteTemplateMap.Add(endpoint.DisplayName, new List<string>() { endpoint.RoutePattern.RawText });
                }
        }
    }

    public class Program
    {
        internal static readonly Dictionary<string, List<string>> RouteTemplateMap = new Dictionary<string, List<string>>();
        /* Rest of things */
    }
Run Code Online (Sandbox Code Playgroud)

启动.cs

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            /* all other middlewares */
            app.UseEndpoints(endpoints =>
            {
                
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");

                //Use this at the last middlware exposing IEndpointRouteBuilder so that all the routes are built by this point
                endpoints.BuildMap();
            });
        }
Run Code Online (Sandbox Code Playgroud)

然后您可以使用该字典或集合从 FilterContext 中检索路由模板。

    FilterContext filterContext;
    Program.RouteTemplateMap.TryGetValue(filterContext.ActionDescriptor.DisplayName, out var template);
    if (template is null)
        throw new Exception("No route template found, that's absurd");

    /* Use the ActionDescriptor.Parameters here 
     * to figure out which overloaded action was called exactly */
    Console.WriteLine(string.Join('\n', template));
Run Code Online (Sandbox Code Playgroud)

为了解决重载操作的情况,将字符串列表用于路由模板(而不仅仅是字典中的字符串)。
您可以ActionDescriptor.Parameters结合RoutePattern.Parameters使用 来为该路由生成唯一的显示名称。


Dan*_*ach 0

这些是组装版本,但仍在寻找更好的答案。

ASPNetCore 2.0

    FilterContext context;

    string routeTemplate = context.ActionDescriptor.AttributeRouteInfo?.Template;

    if (routeTemplate == null) 
    {
        // manually mapped routes or default routes
        // todo is there a better way, not 100% sure that this is correct either
        // https://github.com/aspnet/Routing/blob/1b0258ab8fccff1306e350fd036d05c3110bbc8e/src/Microsoft.AspNetCore.Routing/Template/TemplatePart.cs
        IEnumerable<string> segments = context.RouteData.Routers.OfType<Microsoft.AspNetCore.Routing.RouteBase>()
            .FirstOrDefault()?.ParsedTemplate.Segments.Select(s => string.Join(string.Empty, s.Parts
                .Select(p => p.IsParameter ? $"{{{(p.IsCatchAll ? "*" : string.Empty)}{p.Name}{(p.IsOptional ? "?" : string.Empty)}}}" : p.Text)));

        if (segments != null)
        {
            routeTemplate = string.Join("/", segments);
        }
    }
Run Code Online (Sandbox Code Playgroud)

具有端点路由的 AspNetCore 3.0

            RoutePattern routePattern = null;

            var endpointFeature = context.HttpContext.Features[typeof(Microsoft.AspNetCore.Http.Features.IEndpointFeature)]
                                           as Microsoft.AspNetCore.Http.Features.IEndpointFeature;
            var endpoint = endpointFeature?.Endpoint;

            if (endpoint != null)
            {
                routePattern = (endpoint as RouteEndpoint)?.RoutePattern;
            }

            string formatRoutePart(RoutePatternPart part)
            {
                if (part.IsParameter)
                {
                    RoutePatternParameterPart p = (RoutePatternParameterPart)part;
                    return $"{{{(p.IsCatchAll ? "*" : string.Empty)}{p.Name}{(p.IsSeparator ? " ? " : string.Empty)}}}";
                }
                else if (part.IsLiteral)
                {
                    RoutePatternLiteralPart p = (RoutePatternLiteralPart)part;
                    return p.Content;
                }
                else if(part.IsSeparator)
                {
                    RoutePatternSeparatorPart p = (RoutePatternSeparatorPart)part;
                    return p.Content;
                }
                else
                {
                    throw new NotSupportedException("Unknown Route PatterPart");
                }
            }

            if (routePattern != null)
            {
                // https://github.com/aspnet/Routing/blob/1b0258ab8fccff1306e350fd036d05c3110bbc8e/src/Microsoft.AspNetCore.Routing/Template/TemplatePart.cs
                routeString = string.Join("/", routePattern.PathSegments.SelectMany(s => s.Parts).Select(p => formatRoutePart(p)));
            }
Run Code Online (Sandbox Code Playgroud)