指定控制器不存在时,如何路由至BaseAppController?
实现不存在控制器时回退到BaseAppController的解决方案
这是个挺实用的需求,刚好我之前在ASP.NET Core项目里处理过类似场景。要实现当请求指向不存在的控制器时自动用BaseAppController处理,我们有两种靠谱的实现方式,下面给你一步步拆解:
方法一:自定义控制器选择器(推荐,更贴合框架机制)
ASP.NET Core默认的控制器选择器在找不到对应控制器时会抛出异常并返回404,我们可以通过继承DefaultControllerSelector并重写其逻辑,让它在找不到控制器时回退到BaseAppController。
步骤1:创建自定义控制器选择器
using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.Logging; using System.Linq; public class FallbackControllerSelector : DefaultControllerSelector { private readonly ILogger<FallbackControllerSelector> _logger; private ControllerModel _fallbackController; public FallbackControllerSelector( IControllerTypeProvider controllerTypeProvider, ApplicationPartManager applicationPartManager, ILogger<DefaultControllerSelector> logger) : base(controllerTypeProvider, applicationPartManager, logger) { _logger = logger; } public override ControllerModel SelectController(RouteContext context) { try { // 先尝试用默认逻辑查找控制器 return base.SelectController(context); } catch (InvalidOperationException ex) { // 捕获"找不到控制器"的异常(默认逻辑会抛出此异常) if (ex.Message.Contains("No controller found")) { var requestedController = context.RouteData.Values["controller"].ToString(); _logger.LogInformation($"控制器 '{requestedController}' 不存在,将回退到BaseAppController处理"); // 懒加载获取BaseAppController的模型 if (_fallbackController == null) { var allControllerTypes = ControllerTypeProvider.GetControllerTypes(); var fallbackType = allControllerTypes.FirstOrDefault(t => t.Name == nameof(BaseAppController)); if (fallbackType == null) { throw new InvalidOperationException("应用中未找到BaseAppController,请确认该控制器已正确定义"); } _fallbackController = new ControllerModel(fallbackType, System.Array.Empty<object>()) { ControllerName = fallbackType.Name.Replace("Controller", string.Empty) }; } // 更新路由数据中的控制器名称,确保后续动作选择逻辑正常工作 context.RouteData.Values["controller"] = _fallbackController.ControllerName; return _fallbackController; } // 非找不到控制器的异常,重新抛出 throw; } } }
步骤2:注册自定义选择器
在Program.cs(.NET 6+)中,替换默认的控制器选择器服务:
var builder = WebApplication.CreateBuilder(args); // 添加控制器服务,并替换默认的控制器选择器 builder.Services.AddControllers() .AddControllersAsServices() // 必须添加此配置,确保控制器以服务方式注册 .ReplaceService<IControllerSelector, FallbackControllerSelector>(); var app = builder.Build(); app.UseRouting(); app.UseAuthorization(); app.MapControllers(); app.Run();
步骤3:调整BaseAppController的路由
为了让BaseAppController能匹配任意控制器名称的请求,需要修改它的路由属性:
// 同时保留原有路由(供子类继承)和通用路由(处理不存在的控制器请求) [Route("api/[controller]")] [Route("api/{controller}")] public class BaseAppController : Controller { // 示例动作方法 [HttpGet("MyAction")] public IActionResult MyAction() { return Ok($"请求由BaseAppController处理,原请求控制器名称:{RouteData.Values["controller"]}"); } }
方法二:中间件修改路由数据(更简洁)
另一种思路是在请求管道中提前检查控制器是否存在,如果不存在就修改路由数据中的控制器名称为BaseApp,让默认的控制器选择器能找到BaseAppController。
步骤1:创建回退控制器中间件
using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Infrastructure; using System.Linq; public class FallbackControllerMiddleware { private readonly RequestDelegate _next; private readonly IControllerTypeProvider _controllerTypeProvider; public FallbackControllerMiddleware(RequestDelegate next, IControllerTypeProvider controllerTypeProvider) { _next = next; _controllerTypeProvider = controllerTypeProvider; } public async Task InvokeAsync(HttpContext context) { if (context.GetRouteData().Values.TryGetValue("controller", out var controllerObj) && controllerObj is string requestedController) { // 检查是否存在对应的控制器(注意控制器名称需要加上Controller后缀) bool controllerExists = _controllerTypeProvider.GetControllerTypes() .Any(t => t.Name.Equals($"{requestedController}Controller", StringComparison.OrdinalIgnoreCase)); if (!controllerExists) { // 修改路由数据中的控制器名称为BaseApp context.GetRouteData().Values["controller"] = "BaseApp"; } } await _next(context); } } // 扩展方法,方便注册中间件 public static class FallbackControllerMiddlewareExtensions { public static IApplicationBuilder UseFallbackController(this IApplicationBuilder app) { return app.UseMiddleware<FallbackControllerMiddleware>(); } }
步骤2:注册中间件
在Program.cs中,将中间件添加到路由之后、端点之前:
var app = builder.Build(); app.UseRouting(); // 添加回退控制器中间件 app.UseFallbackController(); app.UseAuthorization(); app.MapControllers(); app.Run();
步骤3:调整BaseAppController的路由
和方法一一样,需要给BaseAppController添加通用路由:
[Route("api/[controller]")] [Route("api/{controller}")] public class BaseAppController : Controller { // ... 你的动作方法 }
注意事项
- 动作方法存在性:回退到
BaseAppController后,请求的动作方法必须存在于该控制器中,否则仍会返回404,这符合你的需求逻辑。 - 路由优先级:子类控制器的路由(如
api/Some)会比BaseAppController的通用路由(api/{controller})更具体,所以正常请求仍会优先匹配子类控制器,不会受影响。 - 控制器命名规范:确保所有控制器都遵循ASP.NET Core的命名规范(以
Controller结尾),否则自定义选择器或中间件的检查逻辑需要调整。
内容的提问来源于stack exchange,提问作者cristi71000
相关产品推荐
相关产品推荐

