ASP.NET中Controller返回NotFound后如何匹配其他路由或控制器?
问题描述
我配置了两个可处理同一路由的路由映射,一个是来自CMS的动态路由,另一个使用不同的控制器,代码示例如下:
app.MapControllerRoute( name: "Dynamic", pattern: "/{urlPath:regex(urlA|urlB)}/{slug}", defaults: new { controller = "Controller1", action = "Index" }); app.MapControllerRoute( name: "NonDynamic", pattern: "/{slug}/{*path}", defaults: new { controller = "Controller2", action = "Index" });
我的需求是:当Dynamic路由对应的Controller1返回404时,如何让应用尝试使用NonDynamic路由的Controller2来处理请求?
问题在于,只有调用Controller1并检查CMS是否存在对应页面后,才能知道它是否能处理该请求;若无法处理,我希望用完全不同的Controller2来处理。
RedirectTo方案不可行,因为我不想产生301或302重定向;在一个控制器中调用另一个控制器的Action并保留上下文似乎也无法实现;我也不想将Controller2的逻辑复制到Controller1中,避免代码混乱。
请问是否有一种方式,从控制器返回后告知应用继续寻找其他匹配的路由?
解决方案
方法1:自定义ActionResult实现路由回退
这种方式通过自定义ActionResult,直接告知框架放弃当前端点处理,重新触发路由匹配流程。
步骤1:创建自定义ActionResult类
public class RouteFallthroughResult : ActionResult { public override async Task ExecuteResultAsync(ActionContext context) { // 清除当前选中的端点和路由数据 context.HttpContext.SetEndpoint(null); var routingFeature = context.HttpContext.Features.Get<IRoutingFeature>(); if (routingFeature != null) { routingFeature.RouteData = new RouteData(); } // 获取EndpointMiddleware,重新执行路由匹配 var endpointMiddleware = context.HttpContext.RequestServices.GetRequiredService<EndpointMiddleware>(); await endpointMiddleware.Invoke(context.HttpContext); } }
步骤2:在Controller1中使用该结果
当检查到CMS无对应页面时,返回这个自定义结果:
public async Task<IActionResult> Index(string urlPath, string slug) { // 模拟异步检查CMS页面是否存在的逻辑 bool cmsPageExists = await CheckCmsPageExistsAsync(urlPath, slug); if (cmsPageExists) { // 返回CMS页面内容 return View(); } else { // 告知框架继续匹配其他路由 return new RouteFallthroughResult(); } }
注意事项
- 需避免无限循环:如果后续路由也无法处理请求,最终仍会返回404,建议在业务逻辑中做好边界判断。
- 兼容ASP.NET Core 3.1及以上版本。
方法2:使用中间件捕获404并重新路由
如果不想修改Controller的返回类型,可以通过中间件捕获指定的404请求,重置路由上下文后重新匹配。
步骤1:注册中间件
在路由配置之前添加该中间件:
app.Use(async (context, next) => { await next(); // 仅处理来自Controller1的404请求 if (context.Response.StatusCode == StatusCodes.Status404NotFound && context.GetEndpoint()?.DisplayName?.Contains("Controller1") == true) { // 重置路由相关数据 context.SetEndpoint(null); var routingFeature = context.Features.Get<IRoutingFeature>(); if (routingFeature != null) { routingFeature.RouteData = null; } // 重新执行端点匹配流程 var endpointMiddleware = context.RequestServices.GetRequiredService<EndpointMiddleware>(); await endpointMiddleware.Invoke(context); } }); // 原有的路由配置 app.MapControllerRoute( name: "Dynamic", pattern: "/{urlPath:regex(urlA|urlB)}/{slug}", defaults: new { controller = "Controller1", action = "Index" }); app.MapControllerRoute( name: "NonDynamic", pattern: "/{slug}/{*path}", defaults: new { controller = "Controller2", action = "Index" });
步骤2:Controller1正常返回404
public async Task<IActionResult> Index(string urlPath, string slug) { bool cmsPageExists = await CheckCmsPageExistsAsync(urlPath, slug); if (cmsPageExists) { return View(); } else { // 返回标准404,由中间件捕获并触发重新路由 return NotFound(); } }
这种方式无需修改Controller的返回逻辑,通过中间件统一处理,但需要精准判断哪些404需要触发重新路由,避免影响其他请求。
内容的提问来源于stack exchange,提问作者uneasy
相关产品推荐
相关产品推荐

