自动挡换手动挡:在 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

原文地址:https://www.cnblogs.com/dudu/p/10885094.html

时间: 2025-01-08 22:27:02

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

ASP.NET Core 3.0 WebApi中使用Swagger生成API文档简介

参考地址,官网:https://docs.microsoft.com/zh-cn/aspnet/core/tutorials/getting-started-with-swashbuckle?view=aspnetcore-2.2&tabs=visual-studio 与https://www.jianshu.com/p/349e130e40d5 当一个WebApi完成之后,书写API文档是一件非常头疼的事,因为不仅要写得清楚,能让调用接口的人看懂,又是非常耗时耗力的一件事.在之前的一篇随笔中(

初识ASP.NET Core 1.0

本文将对微软下一代ASP.NET框架做个概括性介绍,方便大家进一步熟悉该框架. 在介绍ASP.NET Core 1.0之前有必要澄清一些产品名称及版本号.ASP.NET Core1.0是微软下一代ASP.NET 框架,在这之前ASP.NET版本稳定在ASP.NET  4.6,对应的.NET Framework版本为.net 4.6.1. 曾经一段时间微软将下一代ASP.NET 命名为ASP.NET 5和MVC 6,在ASP.NET 5 is dead – Introducing ASP.NET

[转]Writing Custom Middleware in ASP.NET Core 1.0

本文转自:https://www.exceptionnotfound.net/writing-custom-middleware-in-asp-net-core-1-0/ One of the new features from ASP.NET Core 1.0 is the idea of Middleware. Middleware are components of an application that examine the requests responses coming in t

[译]ASP.NET Core 2.0 布局页面

问题 如何在ASP.NET Core 2.0项目中共享可见元素.代码块和指令? 答案 新建一个空项目,首先添加GreetingService服务和UserViewModel模型: public interface IGreetingService { string Greet(string firstname, string surname); } public class GreetingService : IGreetingService { public string Greet(stri

从ASP.NET Core 3.0 preview 特性,了解CLR的Garbage Collection

前言 在阅读这篇文章:Announcing Net Core 3 Preview3的时候,我看到了这样一个特性: Docker and cgroup memory Limits We concluded that the primary fix is to set a GC heap maximum significantly lower than the overall memory limit as a default behavior. In retrospect, this choice

asp.net core 2.0 查缺补漏

asp.net core 2.0 一些有用有趣的设置. 面向(targeting)不同的.net版本: 打开asp.net core 2.0的项目文件: xxx.csproj, 这部分: <Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>netcoreapp2.0</TargetFramework> </PropertyGroup> T

【转载】从头编写 asp.net core 2.0 web api 基础框架 (1)

工具: 1.Visual Studio 2017 V15.3.5+ 2.Postman (Chrome的App) 3.Chrome (最好是) 关于.net core或者.net core 2.0的相关知识就不介绍了, 这里主要是从头编写一个asp.net core 2.0 web api的基础框架. 我一直在关注asp.net core 和 angular 2/4, 并在用这对开发了一些比较小的项目. 现在我感觉是时候使用这两个技术去为企业开发大一点的项目了, 由于企业有时候需要SSO(单点登

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

Asp.net core 2.0.1 Razor 的使用学习笔记——基本页面的建立 VS这版(vs版本:15.5.6  .net版本:4.7.02558)的Razor页面自动生成就是坑爹货,它自动生成的页面真是能坑死你不带商量的.到处错误,到处bug.不过相信微软最终是会修复的. 以下是Bug的具体情况,不关心的朋友可以直接跳过. 正常来说,创建的快捷方式是: 1.Pages右键>添加>新建文件夹>xxxx 2.xxxx右键>添加>Razor页面>使用实体框架生成Raz

Centos7 &amp; Docker &amp; Jenkins &amp; ASP.NET Core 2.0 自动化发布和部署

写在前面 Docker一直很火热,一直想把原本的Jenkins自动部署工具搬到Docker上面,无奈今年一直忙于各种事情,迟迟未实施这个事情,正好迎来了dotnet core 2.0 的正式发布,升级项目的同时,顺便直接将Jenkins搬到Docker上.为什么要写这篇文章呢?因为找过相关的资料,大多数文章都是基于Ubuntu 安装.net core 又或者 GitLab 进行持续集成 自动部署等等等,并未有人尝试过Centos7.3 上部署 Jenkins 并且 构建 ASP.NET CORE