最近由于发现奇怪的 System.Data.SqlClient 性能问题(详见之前的博文),被迫提前了向 .NET Core 3.0 的升级工作(3.0 Preview 5 中问题已被修复)。郁闷的是,在刚开始对部分项目进行升级的时候就遇到了一个障碍,我们基于 Razor Class Library 实现的自定义错误页面由于属性路由问题无法在 ASP.NET Core 3.0 Preview 5 中正常工作(详见博问),一番排查后也没找到解决方法。

为了不影响升级进展,我们被迫采用了一种不常用的解决方法 —— 在中间件中直接调用 Controller Action 渲染视图显示自定义错误页面,也就是将原先由 ASP.NET Core Runtime 自动执行的 Controller Action (自动挡)改为手工执行(手动挡)。

原以为不就是比踩油门多了踩离合器和挂挡吗,应该不会很难。哪知点火后,挂挡都不知道在哪挂。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 的操作方法如下

private static ActionDescriptor CreateActionDescriptor<TController>(string actionName, RouteData routeData)
{
var controllerType = typeof(TController);
var actionDesciptor = new ControllerActionDescriptor()
{
ControllerName = controllerType.Name,
ActionName = actionName,
FilterDescriptors = new List<FilterDescriptor>(),
MethodInfo = controllerType.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 之后,只需3步操作:

1)ActionContext 把离合器和挂挡装置组合起来;

2)ActionInvokerFactory 将 ActionContext 安装到车上并提供了挂挡杆 ControllerActionInvoker;

3)拉动 InvokeAsync 异步挂挡。

就可以把车开起来。

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 的 Endpoint Routing 跑在了中间件(middleware)中。

app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
var routeData = new RouteData();
routeData.Values.Add("message", "Hello World!");
await DriveControllerAction(context, routeData, app);
});
});

Contorller Action 的示例代码如下,就是将参数值传递给视图显示出来。

public class HomeController : Controller
{
public IActionResult Index(string message)
{
ViewBag.Message = message;
return View();
}
}

当程序一运行,浏览器请求一发出, DriveControllerAction 就开始手动挡操作,将车开起来,开车效果如下:

虽然开手动挡比自动挡麻烦很多,但驾驶时那种自主把控的感觉还是不错的,更重要的是这样的自主解决了我们的实际问题。虽然大多数情况下都只要开自动挡,但会开手动挡会给你在解决问题时多一种选择。

完整代码见 github 上的 Startup.cs

ASP.NET Core 3.0 自动挡换手动挡:在 Middleware 中执行 Controller Action的更多相关文章

  1. ASP.NET Core 1.0开发Web API程序

    .NET Core版本:1.0.0-rc2Visual Studio版本:Microsoft Visual Studio Community 2015 Update 2开发及运行平台:Windows ...

  2. .Net Core 2.0生态(3):ASP.NET Core 2.0 特性介绍和使用指南

    ASP.NET Core 2.0 发布日期:2017年8月14日 ASP.NET团队宣布ASP.NET Core 2.0正式发布,发布Visual Studio 2017 15.3支持ASP.NET ...

  3. [翻译] ASP.NET Core 3.0 的新增功能

    ASP.NET Core 3.0 的新增功能 全文翻译自微软官方文档英文版 What's new in ASP.NET Core 3.0 本文重点介绍了 ASP.NET Core 3.0 中最重要的更 ...

  4. [译]ASP.NET Core 2.0 中间件

    问题 如何创建一个最简单的ASP.NET Core中间件? 答案 使用VS创建一个ASP.NET Core 2.0的空项目,注意Startup.cs中的Configure()方法: public vo ...

  5. ASP.NET Core 2.0系列学习笔记-NLog日志配置文件

    一.新建ASP.NET Core 2.0 MVC项目,使用NuGet在浏览中搜索:NLog.Web.AspNetCore,如下图所示: 二.在项目的根目录下新建一个xml类型的nlog.config文 ...

  6. asp.net core 1.1 项目升级至 asp.net core 2.0 preview 2 与正式版

    这两天把一个 asp.net core 1.1 的项目迁移到了 asp.net core 2.0 preview 2 ,在这篇随笔中记录一下. 如果项目在有 global.json 文件,需要删除或修 ...

  7. ASP.NET Core 2.0 MVC「远程」验证

    问题 如何 在ASP.NET Core MVC中使用[Remote]属性来实现模型验证 . 解 在 启动时, 为MVC配置中间件和服务. 添加一个模型. 添加一个控制器. 为jQuery添加一个Raz ...

  8. Asp.net core 2.0.1 Razor 的使用学习笔记(五)

    按说这里应该写关于Role角色类的笔记,但是我还没时间实验这块,所以等以后我搞定了再来分享.现在先写其他部分. Asp.net core 2.0.1 Razor 的使用学习笔记——建立模型 按照微软官 ...

  9. 记一次Docker中部署Asp.Net Core 3.0的踩坑过程

    最近公司打算重构目前直销报单系统到微信小程序中,目前的系统只能在PC上面使用,这两年也搞过App端,但是由于人员流动和公司架构调整最后都不了了之,只留下一堆写了一半的接口.以前的接口依然是使用Asp. ...

随机推荐

  1. MaLoc: a practical magnetic fingerprinting approach to indoor localization using smartphones

    https://www.indooratlas.com/ MaLoc: a practical magnetic fingerprinting approach to indoor localizat ...

  2. Gin Web框架简单介绍

    翻译自: https://github.com/gin-gonic/gin/blob/develop/README.md Gin Web框架 branch=master"> Gin是用 ...

  3. sap 图标查看

    showicon这个程序很不错,可以显示SAP里所有的ICON(图标). 用事务码SE38直接运行程序:showicon 即可. 显示列表之后,双击任何一个图标可以显示出每一个图标的详细信息.

  4. Java for LeetCode 121 Best Time to Buy and Sell Stock

    Say you have an array for which the ith element is the price of a given stock on day i. If you were ...

  5. JVM性能分析工具详解--MAT等

    获得堆转储文件 巧妇难为无米之炊,我们首先需要获得一个堆转储文件.为了方便,本文采用的是 Sun JDK 6.通常来说,只要你设置了如下所示的 JVM 参数: -XX:+HeapDumpOnOutOf ...

  6. HTML5/CSS3简易版俄罗斯方块游戏

    在线演示 本地下载

  7. rbx1包里机器人仿真程序的实践

     git clone https://github.com/pirobot/rbx1.git 1.打开一个终端 cd ~/catkin_ws/ catkin_make source ./devel/s ...

  8. hihocoder #1040 矩形判断(计算几何问题 给8个点的坐标,能否成为一个矩形 【模板思路】)

    #1040 : 矩形判断 时间限制:1000ms 单点时限:1000ms 内存限制:256MB 描述 给出平面上4条线段,判断这4条线段是否恰好围成一个面积大于0的矩形. 输入 输入第一行是一个整数T ...

  9. laravel基础课程---16、数据迁移(数据库迁移是什么)

    laravel基础课程---16.数据迁移(数据库迁移是什么) 一.总结 一句话总结: 是什么:数据库迁移就像是[数据库的版本控制],可以让你的团队轻松修改并共享应用程序的数据库结构. 使用场景:解决 ...

  10. 分享知识-快乐自己:JAVA中的 Iterator 和 Iterable 区别

    java.lang.Iterable  java.util.Iterator  Iterator是迭代器类,而Iterable是接口. 好多类都实现了Iterable接口,这样对象就可以调用itera ...