我可以从AspNetCore FilterContext获取RouteTemplate吗?

时间:2017-11-30 15:14:41

标签: asp.net-core asp.net-core-mvc 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() 但我正在寻找有记录或更好的方法。

2 个答案:

答案 0 :(得分:3)

更新(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);

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 */
    }

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

然后您可以使用该字典或集合,从 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));

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

答案 1 :(得分: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);
    }
}