自动挡换手动挡:在 ASP.NET Core 3.0 Middleware 中手动执行 Controller Action
Posted dudu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自动挡换手动挡:在 ASP.NET Core 3.0 Middleware 中手动执行 Controller Action相关的知识,希望对你有一定的参考价值。
由于遭遇 System.Data.SqlClient 的性能问题(详见之前的博文),向 .NET Core 3.0 的升级工作被迫提前了。在升级过程中遇到了一个问题,我们在 Razor Class Library 中实现的自定义错误页面无法在 ASP.NET Core 3.0 Preview 5 中正常工作,问题原因详见博问 Razor Class Library 中的属性路由在 ASP.NET Core 3.0 中不起作用 。
由于属性路由不起作用的问题没找到解决方法,于是被迫采用了另外一种解决方法 —— 在中间件中调用 Razor Class Library 中的 Controller Action 显示自定义错误页面。这就需要将原先由 ASP.NET Core Runtime 自动执行的 Controller Action (自动挡)改为手工执行(手动挡),之前没玩过,借此机会试一试。
不试不知道,一试吓一跳,手动操作好麻烦,这不是自动挡换手动挡,这是自动挡换拖拉机。
开始寸步难行,挂挡都不知道在哪挂,后来在 ASP.NET Core 3.0 的源码中找到了 ControllerActionDescriptorBuilder.cs 中的 CreateActionDescriptor 方法,才有了参考。
private static ControllerActionDescriptor CreateActionDescriptor(...) { var actionDescriptor = new ControllerActionDescriptor { ActionName = action.ActionName, MethodInfo = action.ActionMethod, }; actionDescriptor.ControllerName = controller.ControllerName; actionDescriptor.ControllerTypeInfo = controller.ControllerType; AddControllerPropertyDescriptors(actionDescriptor, controller); AddActionConstraints(actionDescriptor, selector); AddEndpointMetadata(actionDescriptor, selector); AddAttributeRoute(actionDescriptor, selector); AddParameterDescriptors(actionDescriptor, action); AddActionFilters(actionDescriptor, action.Filters, controller.Filters, application.Filters); AddApiExplorerInfo(actionDescriptor, application, controller, action); AddRouteValues(actionDescriptor, controller, action); AddProperties(actionDescriptor, action, controller, application); return actionDescriptor; }
带上参考小手册,开始试驾。。。经过无数次熄火(NullReferenceException) 后,总算用手动挡开车上路,于是就有了这篇随笔分享手动挡驾驶小经验。
手动挡的操作杆主要有:RouteData, ActionDescriptor, ActionContext, ActionInvokerFactory, ControllerActionInvoker
其中最难操作的也是最重要的是 ActionDescriptor ,绝大多数的熄火都是在操作它时发生的,它有8个属性需要赋值,有些属性即使没用到也要进行初始化赋值,不然立马熄火(null引用异常)。
ActionDescriptor 的操作方法如下
var actionDesciptor = new ControllerActionDescriptor() { ControllerName = controllerType.Name, ActionName = actionName, FilterDescriptors = new List<FilterDescriptor>(), MethodInfo = typeof(HomeController).GetMethod(actionName, BindingFlags.Public | BindingFlags.Instance), ControllerTypeInfo = controllerType.GetTypeInfo(), Parameters = new List<ParameterDescriptor>(), Properties = new Dictionary<object, object>(), BoundProperties = new List<ParameterDescriptor>() };
ControllerActionDescriptor 继承自 ActionDescriptor ,上面的赋值操作中真正传递有价值数据的是 ControllerName, ActionName, MethodInfo, ControllerTypeInfo 。一开始不知道要对哪些属性赋值,只能一步一步试,根据熄火情况一个一个添加,最终得到了上面的最少赋值操作。
第二重要的是 RouteData ,它是数据传输带,不仅要通过它向 ActionDescriptor 传送 BindingInfo 以及 Action 方法通过它获取参数值,而且要向视图引擎(比如ViewEngineResult,ViewResultExecutor)传送 controller 与 action 的名称,不然视图引擎找不到视图文件。
RouteData 的操作方法如下
//For searching View routeData.Values.Add("controller", actionDesciptor.ControllerName.Replace("Controller", "")); routeData.Values.Add("action", actionDesciptor.ActionName); //For binding action parameters foreach (var routeValue in routeData.Values) { var parameter = new ParameterDescriptor(); parameter.Name = routeValue.Key; var attributes = new object[] { new FromRouteAttribute { Name = parameter.Name }, }; parameter.BindingInfo = BindingInfo.GetBindingInfo(attributes); parameter.ParameterType = routeValue.Value.GetType(); actionDesciptor.Parameters.Add(parameter); }
有了 ActionDescriptor 与 RouteData 之后,只需4步操作,可以把车开起来了。
var actionContext = new ActionContext(context, routeData, actionDesciptor); var actionInvokerFactory = app.ApplicationServices.GetRequiredService<IActionInvokerFactory>(); //ActionInvokerFactory var invoker = actionInvokerFactory.CreateInvoker(actionContext); //ControllerActionInvoker await invoker.InvokeAsync();
但车没有跑在高速上,而是通过 ASP.NET Core 3.0 的 EndpointRouting 跑在了 middleware 中。
app.UseEndpoints(endpoints => { endpoints.MapGet("/", async context => { var routeData = new RouteData(); routeData.Values.Add("message", "Hello World!"); await DriveControllerAction(context, routeData, app); }); });
看看手动挡开车的效果,Contorller 的示例代码如下
public class HomeController : Controller { public IActionResult Index(string message) { ViewBag.Message = message; return View(); } }
运行结果
手动挡驾驶 ASP.NET Core 3.0 Preview 5 版 Contoller Action 型新车成功!
完整代码见 github 上的 Startup.cs
以上是关于自动挡换手动挡:在 ASP.NET Core 3.0 Middleware 中手动执行 Controller Action的主要内容,如果未能解决你的问题,请参考以下文章
为啥我们不再需要在更高版本的 ASP.NET Core 中手动验证模型?