如何在.NET Core 3.1 MVC中通过URI获取Controller/Action上下文?
解决方案:利用ASP.NET Core路由组件解析URL到Controller/Action
我完全理解你的需求——在基于特性路由的MVC应用中,通过URL反向解析对应的Controller、Action以及自定义属性来构建面包屑,确实是很常见的场景,但官方没有直接提供一个开箱即用的GetRouteContextForUri方法,不过我们可以利用ASP.NET Core路由系统的内部组件来实现这个功能。
核心思路
ASP.NET Core的路由系统通过ActionDescriptor存储所有Controller/Action的路由信息,我们可以借助RouteMatcher(路由匹配器)来模拟请求路由的过程,传入目标URL后就能得到对应的路由上下文和Action信息。
步骤1:创建路由解析服务
首先,我们需要封装一个服务类,注入必要的依赖来获取路由信息并执行匹配:
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Options; using System; using System.Linq; using System.Reflection; public interface IRouteResolver { // 返回控制器名称、动作名称,以及指定类型的自定义属性 (string ControllerName, string ActionName, T[] CustomAttributes) ResolveUrl<T>(string url) where T : Attribute; } public class RouteResolver : IRouteResolver { private readonly IActionDescriptorCollectionProvider _actionDescriptorProvider; private readonly RouteOptions _routeOptions; private readonly IHttpContextAccessor _httpContextAccessor; public RouteResolver(IActionDescriptorCollectionProvider actionDescriptorProvider, IOptions<RouteOptions> routeOptions, IHttpContextAccessor httpContextAccessor) { _actionDescriptorProvider = actionDescriptorProvider; _routeOptions = routeOptions.Value; _httpContextAccessor = httpContextAccessor; } public (string ControllerName, string ActionName, T[] CustomAttributes) ResolveUrl<T>(string url) where T : Attribute { // 处理相对URL,转换为绝对路径(如果需要) var uri = new Uri(url, UriKind.RelativeOrAbsolute); string path; if (!uri.IsAbsoluteUri) { var baseUri = new Uri($"{_httpContextAccessor.HttpContext.Request.Scheme}://{_httpContextAccessor.HttpContext.Request.Host}"); uri = new Uri(baseUri, url); } path = uri.AbsolutePath; // 构建模拟的HttpContext和RouteContext var httpContext = new DefaultHttpContext(); httpContext.Request.Path = path; httpContext.Request.Method = HttpMethods.Get; // 可根据实际请求方法调整 var routeContext = new RouteContext(httpContext); // 将所有ActionDescriptor转换为路由端点 var endpoints = _actionDescriptorProvider.ActionDescriptors.Items .Select(ad => new RouteEndpoint(ad, ad.RoutePattern, 0, null)) .ToList(); // 使用RouteMatcher执行路由匹配 var matcher = new RouteMatcher(_routeOptions); var matchResult = matcher.Match(routeContext, endpoints); if (matchResult == null || !matchResult.EndpointMetadata.OfType<ControllerActionDescriptor>().Any()) { return (null, null, Array.Empty<T>()); } // 提取Controller和Action信息 var actionDescriptor = matchResult.EndpointMetadata.OfType<ControllerActionDescriptor>().First(); var controllerName = actionDescriptor.ControllerName; var actionName = actionDescriptor.ActionName; // 获取方法和控制器上的自定义属性 var actionAttributes = actionDescriptor.MethodInfo.GetCustomAttributes<T>(inherit: true); var controllerAttributes = actionDescriptor.ControllerTypeInfo.GetCustomAttributes<T>(inherit: true); var customAttributes = actionAttributes.Concat(controllerAttributes).ToArray(); return (controllerName, actionName, customAttributes); } }
步骤2:注册服务
在Startup.cs的ConfigureServices方法中注册我们的服务和必要的依赖:
public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(); services.AddHttpContextAccessor(); // 用于获取当前请求的主机信息 services.AddScoped<IRouteResolver, RouteResolver>(); }
步骤3:构建面包屑逻辑
接下来,我们可以基于这个路由解析服务,实现URL层级拆分和面包屑生成的逻辑。首先定义一个自定义的面包屑属性:
public class BreadcrumbAttribute : Attribute { public string Name { get; set; } public string Description { get; set; } public BreadcrumbAttribute(string name) { Name = name; } }
然后在Controller/Action上标记这个属性:
[Breadcrumb("首页", Description = "网站首页")] [Route("")] public class HomeController : Controller { public IActionResult Index() => View(); } [Breadcrumb("产品列表", Description = "所有产品分类")] [Route("products")] public class ProductsController : Controller { [Breadcrumb("电子产品", Description = "数码电子产品")] [Route("electronics")] public IActionResult Electronics() => View(); [Breadcrumb("智能手机", Description = "最新智能手机")] [Route("electronics/smartphones")] public IActionResult Smartphones() => View(); }
最后,编写面包屑生成方法:
public class BreadcrumbGenerator { private readonly IRouteResolver _routeResolver; public BreadcrumbGenerator(IRouteResolver routeResolver) { _routeResolver = routeResolver; } public List<BreadcrumbItem> GenerateFromUrl(string currentUrl) { var breadcrumbs = new List<BreadcrumbItem>(); var segments = currentUrl.Trim('/').Split('/'); var currentPath = ""; // 逐个层级解析URL foreach (var segment in segments) { currentPath += $"/{segment}"; var (_, _, attributes) = _routeResolver.ResolveUrl<BreadcrumbAttribute>(currentPath); var breadcrumbAttr = attributes.FirstOrDefault(); if (breadcrumbAttr != null) { breadcrumbs.Add(new BreadcrumbItem { Name = breadcrumbAttr.Name, Url = currentPath, Description = breadcrumbAttr.Description }); } } // 可以手动添加首页面包屑(如果根路径没匹配到) if (!breadcrumbs.Any() || breadcrumbs.First().Url != "/") { breadcrumbs.Insert(0, new BreadcrumbItem { Name = "首页", Url = "/" }); } return breadcrumbs; } } public class BreadcrumbItem { public string Name { get; set; } public string Url { get; set; } public string Description { get; set; } }
注意事项
- 如果你的应用使用了区域(Area),可以从
ControllerActionDescriptor的AreaName属性中获取区域信息,完善面包屑层级。 - 对于同一个路径匹配多个Action的情况(比如GET和POST方法),需要调整
httpContext.Request.Method来匹配对应的Action。 - 查询参数不会影响路由匹配,
RouteMatcher只会解析URL的路径部分。
内容的提问来源于stack exchange,提问作者Jonesopolis
相关产品推荐
相关产品推荐

