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
相关产品推荐
相关产品推荐

