You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 23:17:40