.NET Core自定义ControllerActivator返回非请求控制器引发转换错误求助
自定义ControllerActivator在.NET Core中的转换错误解决方法
我实现了一个自定义ControllerActivator,希望返回与请求目标不同的控制器。这段代码在.NET Framework中能正常运行,但在.NET Core里出现了转换错误,错误调用栈如下:
Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor+SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, object controller, object[] arguments) Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync() Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()
自定义激活器代码如下:
public object Create(ControllerContext context) { // Always return the TestController, var controller = new TestController(); MethodInfo method = controller.GetType().GetMethod(context.ActionDescriptor.MethodInfo.Name); context.ActionDescriptor.ControllerName = "Test"; context.ActionDescriptor.DisplayName = context.ActionDescriptor.DisplayName.Replace("HomeController", "TestController"); context.ActionDescriptor.ControllerTypeInfo = typeof(TestController).GetTypeInfo(); context.ActionDescriptor.MethodInfo = method; controller.ControllerContext = context; return controller; }
问题原因
.NET Core MVC的执行流程中,ActionMethodExecutor是在路由匹配完成后提前根据原始ActionDescriptor创建的,你在控制器激活阶段修改ActionDescriptor的信息已经滞后,导致执行时控制器实例与预先绑定的方法元数据不匹配,触发类型转换错误。
可行实现方案
推荐在路由匹配阶段就替换目标控制器与Action,这符合.NET Core MVC的扩展设计规范,具体步骤如下:
- 实现自定义
IControllerModelConvention,将所有控制器的Action映射到TestController的对应方法:
public class RedirectToTestControllerConvention : IControllerModelConvention { public void Apply(ControllerModel controller) { // 跳过TestController本身,避免循环处理 if (controller.ControllerType == typeof(TestController)) return; // 缓存TestController的公开实例方法,按方法名匹配(忽略大小写) var testControllerMethods = typeof(TestController) .GetMethods(BindingFlags.Public | BindingFlags.Instance) .ToDictionary(m => m.Name, StringComparer.OrdinalIgnoreCase); // 遍历当前控制器的所有Action,替换为TestController的对应方法 foreach (var action in controller.Actions) { if (testControllerMethods.TryGetValue(action.ActionName, out var targetMethod)) { // 关联到TestController的模型 action.Controller = controller.Controllers.First(c => c.ControllerType == typeof(TestController)); // 替换为TestController的目标方法 action.MethodInfo = targetMethod; action.ActionName = targetMethod.Name; } } } }
- 在服务配置中注册该约定:
- .NET 6+(Program.cs):
builder.Services.AddControllersWithViews(options => { options.Conventions.Add(new RedirectToTestControllerConvention()); });
- .NET Core 3.1(Startup.cs):
services.AddControllersWithViews(options => { options.Conventions.Add(new RedirectToTestControllerConvention()); });
- 移除原自定义ControllerActivator,因为路由阶段已直接指向
TestController的Action,无需再在激活阶段替换控制器实例。
如果必须保留激活器方式,可通过替换ActionDescriptorCollectionProvider的缓存实现,但这种方式侵入性强,不推荐使用。
内容的提问来源于stack exchange,提问作者Tsk
相关产品推荐
相关产品推荐

