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

ASP.NET Core中URL路由匹配与重构的问题求助

解决方案:ASP.NET Core 动态路由重构与二次匹配

问题根源

你之前两次调用UseRouting()的方式会导致第二次路由中间件覆盖首次匹配的IEndpointFeature,无论首次是否匹配成功,endpoint都会被重置为null,从而让所有请求都进入路径重构逻辑,这是核心问题。

可行实现方案

移除多余的UseRouting(),通过自定义中间件在首次路由匹配失败后重构路径,并手动触发二次路由匹配,而非依赖重复添加路由中间件。

步骤1:自定义路由重构中间件

注入路由匹配所需的核心服务,预解析备用路由模板以提升性能,在首次匹配失败时重构路径并手动执行二次匹配:

using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Patterns;

public class RoutingRewriterMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IEnumerable<EndpointDataSource> _endpointDataSources;
    private readonly MatcherFactory _matcherFactory;
    private readonly List<RoutePattern> _parsedTemplates;

    // 构造函数注入依赖与备用路由模板
    public RoutingRewriterMiddleware(RequestDelegate next,
        IEnumerable<EndpointDataSource> endpointDataSources,
        MatcherFactory matcherFactory,
        IEnumerable<string> routeTemplates)
    {
        _next = next;
        _endpointDataSources = endpointDataSources;
        _matcherFactory = matcherFactory;
        // 预解析备用模板,避免每次请求重复解析
        _parsedTemplates = routeTemplates.Select(RoutePatternFactory.Parse).ToList();
    }

    public async Task Invoke(HttpContext context)
    {
        var endpointFeature = context.Features.Get<IEndpointFeature>();
        var endpoint = endpointFeature?.Endpoint;

        // 首次路由已匹配,直接进入后续管道
        if (endpoint != null)
        {
            await _next(context);
            return;
        }

        var originalPath = context.Request.Path;
        bool pathRewritten = false;

        // 遍历备用模板尝试匹配
        foreach (var template in _parsedTemplates)
        {
            var matcher = new TemplateMatcher(template, null);
            var values = new RouteValueDictionary();
            if (matcher.TryMatch(originalPath, values))
            {
                // 按默认路由模板重构路径(示例:将{area}/api/xxx转为api/{area}/xxx)
                context.Request.Path = ReBuildDefaultPath(values);
                pathRewritten = true;
                break;
            }
        }

        if (pathRewritten)
        {
            // 手动执行二次路由匹配,更新上下文特征
            var routeValuesFeature = context.Features.Get<IRouteValuesFeature>() ?? new RouteValuesFeature();
            context.Features.Set(routeValuesFeature);

            var matcher = await _matcherFactory.CreateMatcherAsync(_endpointDataSources);
            var matchResult = await matcher.MatchAsync(context);

            if (matchResult.HasMatched)
            {
                // 更新匹配到的端点与路由值
                endpointFeature = context.Features.Get<IEndpointFeature>() ?? new EndpointFeature();
                endpointFeature.Endpoint = matchResult.Endpoint;
                context.Features.Set(endpointFeature);
                routeValuesFeature.RouteValues = matchResult.RouteValues;
            }
            else
            {
                // 重构后仍无匹配,恢复原路径
                context.Request.Path = originalPath;
            }
        }

        await _next(context);
    }

    // 自定义路径重构逻辑,按默认路由模板生成新路径
    private PathString ReBuildDefaultPath(RouteValueDictionary values)
    {
        var segments = new List<string>
        {
            "api",
            values["area"]?.ToString() ?? string.Empty,
            values["controller"]?.ToString() ?? string.Empty
        };

        if (!string.IsNullOrEmpty(values["action"]?.ToString()))
            segments.Add(values["action"].ToString());
        if (!string.IsNullOrEmpty(values["id"]?.ToString()))
            segments.Add(values["id"].ToString());

        // 过滤空段,避免生成无效路径
        segments = segments.Where(s => !string.IsNullOrEmpty(s)).ToList();
        return new PathString("/" + string.Join("/", segments));
    }
}

步骤2:配置管道

在Startup.cs中仅保留一次UseRouting(),注入备用路由模板并添加中间件:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // 其他中间件配置...

    app.UseRouting();

    // 添加自定义路由重构中间件,传入备用路由模板
    app.UseMiddleware<RoutingRewriterMiddleware>(new List<string>
    {
        "{area}/api/{controller}/{action?}/{id?}"
        // 可添加更多备用模板
    });

    app.UseEndpoints(endpoints =>
    {
        // 配置默认路由模板
        endpoints.MapControllerRoute(
            name: "defaultApi",
            pattern: "api/{area}/{controller}/{action?}/{id?}");
    });
}

关键优化点

  • 预解析模板:在构造函数中预先解析备用路由模板,避免每次请求重复解析带来的性能损耗。
  • 手动触发匹配:利用ASP.NET Core原生的MatcherFactory执行二次匹配,而非重复添加路由中间件,避免上下文特征被覆盖。
  • 路径恢复:若重构后仍无匹配,恢复原路径,确保后续管道能正常处理404逻辑。

内容的提问来源于stack exchange,提问作者Emre Kabaoglu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 02:34:54