微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

自动挡换手动挡:在 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 方法,才有了参考。

@H_502_14@private @H_502_14@static ControllerActionDescriptor CreateActionDescriptor(...)
{
    @H_502_14@var actionDescriptor = @H_502_14@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,action);
    AddProperties(actionDescriptor,action,application);

    @H_502_14@return actionDescriptor;
}

带上参考小手册,开始试驾。。。经过无数次熄火(NullReferenceException) 后,总算用手动挡开车上路,于是就有了这篇随笔分享手动挡驾驶小经验。

手动挡的操作杆主要有:RouteData, ActionDescriptor, ActionContext, ActionInvokerFactory, ControllerActionInvoker

其中最难操作的也是最重要的是 ActionDescriptor ,绝大多数的熄火都是在操作它时发生的,它有8个属性需要赋值,有些属性即使没用到也要进行初始化赋值,不然立马熄火(null引用异常)。

ActionDescriptor 的操作方法如下

@H_502_14@var actionDesciptor = @H_502_14@new ControllerActionDescriptor()
{
    ControllerName = controllerType.Name,ActionName = actionName,FilterDescriptors = @H_502_14@new List<FilterDescriptor>(),MethodInfo = @H_502_14@typeof(HomeController).getmethod(actionName,BindingFlags.Public | BindingFlags.Instance),ControllerTypeInfo = controllerType.GetTypeInfo(),Parameters = @H_502_14@new List<ParameterDescriptor>(),Properties = @H_502_14@new Dictionary<@H_502_14@object,@H_502_14@object>(),BoundProperties = @H_502_14@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
@H_502_14@foreach (@H_502_14@var routeValue @H_502_14@in routeData.Values)
{
    @H_502_14@var parameter = @H_502_14@new ParameterDescriptor();
    parameter.Name = routeValue.Key;
    @H_502_14@var attributes = @H_502_14@new @H_502_14@object[]
    {
        @H_502_14@new FromrouteAttribute { Name = parameter.Name },};
    parameter.BindingInfo = BindingInfo.GetBindingInfo(attributes);
    parameter.ParameterType = routeValue.Value.GetType();
    actionDesciptor.Parameters.Add(parameter);
}

有了 ActionDescriptor 与 RouteData 之后,只需4步操作,可以把车开起来了。

@H_502_14@var actionContext = @H_502_14@new ActionContext(context,routeData,actionDesciptor);                    
@H_502_14@var actionInvokerFactory = app.applicationservices.GetrequiredService<IActionInvokerFactory>(); //ActionInvokerFactory
@H_502_14@var invoker = actionInvokerFactory.CreateInvoker(actionContext); //ControllerActionInvoker
@H_502_14@await invoker.InvokeAsync();

但车没有跑在高速上,而是通过 ASP.NET Core 3.0 的 EndpointRouting 跑在了 middleware 中。

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

看看手动挡开车的效果,Contorller 的示例代码如下

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

运行结果

分享图片

手动挡驾驶 ASP.NET Core 3.0 Preview 5 版 Contoller Action 型新车成功!

完整代码见 github 上的 Startup.cs 

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐