ASP.NET Core MVC 源码学习:详解 Action 的激活

前言

上一篇文章中,我们已经知道了在 ASP.NET Core MVC 中如何发现一个 Action,那么在发现了Action之后,就是 Action 的一个调用过程,也就是整个 Action 执行的生命周期,那么本文我们就来一起看一下Action是怎么激活并且执行的吧。

Getting Started

还是从 MvcRouteHandlerRouteAsync()开始说起,在上一篇的结尾中,我们已经拿到了 actionDescriptor 这个对象,接着,MVC 会把 actionDescriptorrouteData 已经 HttpContext 对象一起包装成为一个 ActionContext 上下文对象。

ActionContext:

public class ActionContext
{
    public ActionDescriptor ActionDescriptor {get; set;}
    public HttpContext HttpContext {get; set;}
    public ModelStateDictionary ModelState {get;}
    public RouteData RouteData {get; set;}
}

在有了 ActionContext 之后,接下来就是创建 ControllerActionInvoker 的过程,MVC 会通过一个工厂(ActionInvokerFactory)来创建 Action 对应的 Invoker 对象。

创建 ActionInvoker 的过程如下:

代码如下:

context.Handler = (c) =>
{
    var routeData = c.GetRouteData();

    var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor);
   
    var invoker = _actionInvokerFactory.CreateInvoker(actionContext);
   
    return invoker.InvokeAsync();
};

开始调用 invoker.InvokerAsync()

InvokerAsync 中的过程其实就是一个管线过滤器执行的过程,在这个管线中,每一步 Action的流转都具有当前管线执行的一个状态,这个过程有点像中间件的执行过程,我们一起来看下。

在 MVC Core 中,过滤器分为 5 大类,分别是:

  • 授权过滤器(Authorization Filters)
  • 资源过滤器(Resource Filters)
  • Action过滤器(Action Filters)
  • 异常过滤器(Exception Filters)
  • Result 过滤器(Result Filters)

关于过滤器的管道执行主要包含两个类,分别是 ResourceInvokerControllerActionInvoker。 其中 ControllerActionInvoker 继承自ResourceInvoker 并且实现了 IActionInvoker 接口。

下面,我们就来看一下 源码 中关于这一部分是如何实现的。

ResourceInvoker

ResourceInvoker 中,拥有管线的入口函数,即 InvokeAsync(),在这个函数中又调用了 InvokeFilterPipelineAsync()

以下这个函数是调用过程的核心函数:

private async Task InvokeFilterPipelineAsync()
{
    var next = State.InvokeBegin;
    
    // scope 用来标明下一个调用的方法是哪个,
    // 以及初始化的状态是哪种。 
    // 最外面的“scope”是“Scope.Invoker”,不需要任何类型的“context”或“result”。
    var scope = Scope.Invoker;
    
    // 'state'用于状态之间的转换期间的内部状态处理。
    // 实际上,也就是说将过滤器实例存储在“state”中,然后在下一个状态下检索它。
    var state = (object)null;
    
    // 当到达终点 state 时, `isCompleted` 会被设置为true
    var isCompleted = false;
    
    while (!isCompleted)
    {
        await Next(ref next, ref scope, ref state, ref isCompleted);
    }
}

可以看到,里面有一个Next循环,直到 isCompleted 为 true 时才停止,那么在这个 Next 中会依次执行各个过滤器。

管线针对于MVC预设的几个过滤器他们对应的执行顺序如下图:

ResourceInvoker 中,主要处理两种类型的过滤器,他们是 Authorization FiltersResource Filter

  • Authorization Filters

在 Next 中,会把处理的每个阶段的状态作为一个枚举类型,然后使用的 Switch Case 手动进行的状态的处理,那么在处理 Authorization 相关的过滤器的时候,其 State 具有以下状态:

AuthorizationBegin,
AuthorizationNext,
AuthorizationAsyncBegin,
AuthorizationAsyncEnd,
AuthorizationSync,
AuthorizationShortCircuit,
AuthorizationEnd,

关于每一种状态的处理大家可以直接看这里的
代码 ,比较简单就不详细说了。

有一点需要说明的是 在 过滤器的处理中,AuthorizationShortCircuit 这个状态是用来处理短路的情况的,也就是说在过滤器中如果对 Context.Result 赋值了,那么会短路过滤器的管道,直接将 isCompleted 标记为 true,结束管道流程。

AuthorizationSync 这个状态是执行 OnAuthorization 的关键流程,IAuthorizationFilter 的实现将在这个流程中执行。

  • Resource Filters

Resource Filter 是 ASP.NET Core MVC 中新增加的一个过滤器,它在管道中的调用顺序仅次于Authorization,在其后执行。用户可以实现 IResourceFilterIAsyncExceptionFilter 接口来自定义 Resource 过滤器, 它主要是此过滤器来实现一些需要短路过滤器管线的一些操作。比如说:缓存(如果被命中,则直接返回,短路管线)。

下面是 Resource 中涉及的所有状态。

ResourceBegin,
ResourceNext,
ResourceAsyncBegin,
ResourceAsyncEnd,
ResourceSyncBegin,
ResourceSyncEnd,
ResourceShortCircuit,
ResourceInside,
ResourceOutside,
ResourceEnd

ControllerActionInvoker

ControllerActionInvoker 重写了 ResourceInvoker 中的InvokeInnerFilterAsync()方法,然后经由 ControllerActionInvokerProvider 实例化。

既然流程是从 ResourceInvoker 开始的,那么我们看一下在 ResourceInvoker 中哪一步调用了 InvokeInnerFilterAsync()

ResourceInvoker 中主要有两个状态来调用它,一个是 State.ResourceNext,另外一个是 State.ResourceInside

State.ResourceNext 中如果没有检测到有 Resource Filter 这直接开始下一阶段的 Filter调用,即 ControllerActionInvoker 中处理的过滤器。

State.ResourceInside 中会将下一阶段的状态设置为 State.ResourceOutside,然后开始启动调用 InvokeInnerFilterAsync(),当 ControllerActionInvoker 中的过滤器执行完成之后,再回过头来执行 ResourceOutside 相关内容。

好了,现在流程已经正式来到了 ControllerActionInvoker

ControllerActionInvoker 中,主要处理 3 种类型的过滤器。

  • Exception Filters

实现了 IExceptionFilter 接口或 IAsyncExceptionFilter 接口的过滤器,处理包括发生在 Controller 创建及 模型绑定 期间出现的异常。它们只在管道内发生异常时才会被调用。

  • Action Filters

实现了 IActionFilter 接口或 IAsyncActionFilter 接口的过滤器,它们可以在 action 方法执行的前后被执行。

  • Result Filters

实现了 IResultFilterIAsyncResultFilter 接口。Result Filter 在 Action Result 执行体的周围执行。当 Action 或 Action 过滤器产生 Action 结果时,只有成功运行的才会执行结果过滤器。如果异常过滤器处理了异常,那么结果过滤器就不会运行——除非异常过滤器将异常设置为null。

源码流程

下面是 ControllerActionInvoker 中的 InvokeInnerFilterAsync


protected override async Task InvokeInnerFilterAsync()
{
    var next = State.ResourceInsideBegin;
    var scope = Scope.Resource;
    var state = (object)null;
    var isCompleted = false;

    while (!isCompleted)
    {
        await Next(ref next, ref scope, ref state, ref isCompleted);
    }
}

它的起始状态从 State.ResourceInsideBegin 开始,核心方法还是 Next 方法。

在源代码中,状态的流转是先从 Exception 开始,然后对Exception过滤器进行"压栈",但是并不会执行过滤器中的代码,接着会执行Action相关状态代码,在 State.ActionAsyncBegin 这个状态中会执行 Action Filters 中的 OnActionExecuting,然后在 State.ActionSyncEnd 这个状态中执行OnActionExecuted

注意Action Filter 的执行代码由一个 Try Catch 代码块包装,当发生异常的时候,MVC会把这些信息包装成为一个 ActionExecutedContext 对象,然后会接着执行 Action Filter 里面 OnActionExecuted

Action Filter 相关的过滤器执行完成之后会将状态置为 State.ExceptionSyncEnd 开始执行 Exception Filter 相关业务。

在 整个管道的流程中,用户在 Action 中的代码是在 State.ActionInside 这个状态中执行的,它在 Action FilterOnActionExecuting 后执行。

用户代码Action的调用主要是下面这个函数:

private async Task InvokeActionMethodAsync()
{
    var controllerContext = _controllerContext;
    var executor = _executor;
    var controller = _controller;
    var arguments = _arguments;
    
    //构建Action参数
    var orderedArguments = ControllerActionExecutor.PrepareArguments(arguments, executor);

    IActionResult result = null;
    
    var returnType = executor.MethodReturnType;
    
    // void 返回结果,执行后返回 EmptyResult
    if (returnType == typeof(void))
    {
        executor.Execute(controller, orderedArguments);
        result = new EmptyResult();
    }
    // Task 返回结果,执行后返回 EmptyResult
    else if (returnType == typeof(Task))
    {
        await (Task)executor.Execute(controller, orderedArguments);
        result = new EmptyResult();
    }
    // IActionResult 返回结果,执行后返回 IActionResult
    else if (executor.TaskGenericType == typeof(IActionResult))
    {
        result = await (Task<IActionResult>)executor.Execute(controller, orderedArguments);
        if (result == null)
        {
            throw new InvalidOperationException(
                Resources.FormatActionResult_ActionReturnValueCannotBeNull(typeof(IActionResult)));
        }
    }
    //是否为 IActionResult 的子类型
    else if (executor.IsTypeAssignableFromIActionResult)
    {
        //是否为异步Action
        if (_executor.IsMethodAsync)
        {
            result = (IActionResult)await _executor.ExecuteAsync(controller, orderedArguments);
        }
        else
        {
            result = (IActionResult)_executor.Execute(controller, orderedArguments);
        }

        if (result == null)
        {
            throw new InvalidOperationException(
                Resources.FormatActionResult_ActionReturnValueCannotBeNull(_executor.TaskGenericType ?? returnType));
        }
    }
    //非异步方法
    else if (!executor.IsMethodAsync)
    {
        var resultAsObject = executor.Execute(controller, orderedArguments);
        result = resultAsObject as IActionResult ?? new ObjectResult(resultAsObject)
        {
            DeclaredType = returnType,
        };
    }
    else if (executor.TaskGenericType != null)
    {
        var resultAsObject = await executor.ExecuteAsync(controller, orderedArguments);
        result = resultAsObject as IActionResult ?? new ObjectResult(resultAsObject)
        {
            DeclaredType = executor.TaskGenericType,
        };
    }
    else
    {
        throw new InvalidOperationException(Resources.FormatActionExecutor_UnexpectedTaskInstance(
            executor.MethodInfo.Name,
            executor.MethodInfo.DeclaringType));
    }

    _result = result;
}

下面是 ControllerActionInvoker 中用到的所有状态。

private enum State
{
    ResourceInsideBegin,
    
    ExceptionBegin,
    ExceptionNext,
    ExceptionAsyncBegin,
    ExceptionAsyncResume,
    ExceptionAsyncEnd,
    ExceptionSyncBegin,
    ExceptionSyncEnd,
    ExceptionInside,
    ExceptionHandled,
    ExceptionEnd,
    
    ActionBegin,
    ActionNext,
    ActionAsyncBegin,
    ActionAsyncEnd,
    ActionSyncBegin,
    ActionSyncEnd,
    ActionInside,
    ActionEnd,
    
    ResultBegin,
    ResultNext,
    ResultAsyncBegin,
    ResultAsyncEnd,
    ResultSyncBegin,
    ResultSyncEnd,
    ResultInside,
    ResultEnd,
    
    ResourceInsideEnd,
}

总结

本文详细描述了 MVC 在 Action 是如何激活的,以及在激活 Action 的过程中,过滤器管线中都做了哪些工作,并且讲解了其中的过程,以及各个过滤器的一些作用和功能。

如果你对 .NET Core 感兴趣可以关注我,我会定期在博客分享关于 .NET Core 的学习心得,如果你认为本篇文章对你有帮助的话,谢谢你的【推荐】。


本文地址:http://www.cnblogs.com/savorboard/p/aspnetcore-mvc-action.html
作者博客:Savorboard
欢迎转载,请在明显位置给出出处及链接

posted @ 2017-04-14 10:59 Savorboard 阅读(...) 评论(...) 编辑 收藏