MVC Source Analysis - Action/Result Filter Execution Timing

Keywords: ASP.NET

Front It also spent two articles to see the use of authorization and error filters. However, the execution of Action/Result and the execution timing of the two filters in Action/Result are not clearly seen.

I. Execution of Action Method and Execution of Filter

//System.Web.Mvc.ControllerActionInvoker
protected virtual ActionExecutedContext InvokeActionMethodWithFilters(ControllerContext controllerContext,
   IList<IActionFilter> filters, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters) { ActionExecutingContext preContext = new ActionExecutingContext(controllerContext, actionDescriptor, parameters); //This defines a Func delegate with the return value of the ActionExecutedContext type
   //In the returned ActionExecutedContext type instance, the execution method is specified for Result
   //It's all about specifying the method to be executed., After it has been executed, No method has been implemented.. The part marked red below, Execution Action Method

   Func
<ActionExecutedContext> seed = () => new ActionExecutedContext(controllerContext, actionDescriptor, false, null)
    { Result = this.InvokeActionMethod(controllerContext, actionDescriptor, parameters) }; return filters
       //The filters variable is a List < IActionFilter > collection with controller as the leading element. The purpose here is to invert the collection into
       // IActionFilter->Controller
      .Reverse<IActionFilter>()
       //Aggregate function, where next is actually seed object
       //filter is an element in IEnumberable <IActionFilter>.
       //The effect here is to return the accumulated seed object

      .Aggregate<IActionFilter, Func<ActionExecutedContext>>(seed,
         (next, filter) => () => InvokeActionMethodFilter(filter, preContext, next))
      (); //This step execution method }

The code here is ____________ Front 3.4, but I partitioned him so that it was easy to understand.

Here's an Aggregate <>() which may not be so well understood. The first demo should be able to promote understanding.

var numbers = new int[] { 1, 3, 9, 2 };  
// Multiply the current total by the following value to get the new total 
var product = numbers.Aggregate((total, next) => total * next);
//The final value is: 1 X 3 X 9 X 2 = 54. 

The execution process here is as follows:

1. When Aggregate() is executed for the first time, next in the anonymous method (next, filter) is the seed object, and filter is the ActionFilter object. He takes InvokeAction Method Filter (filter, preContext, next) as the return value and as the next parameter in the next execution (next, filter).

2. On the second execution (next, filter), the value of next parameter is InvokeAction Method Filter (filter, preContext, next), and filter is Controller, which eventually will

InvokeAction Method Filter (filter, preContext, next) returns the final result to the filter variable.

The whole execution expression is: filters = InvokeAction Method Filter (controller, preContext, InvokeAction Method Filter (ActionFilter, preContext, seed);

After these steps, filters form a linked list, which is first executed by Controller.

 

1. InvokeActionMethodFilter

internal static ActionExecutedContext InvokeActionMethodFilter(IActionFilter filter, 
  ActionExecutingContext preContext, Func<ActionExecutedContext> continuation) {
   //This step executes the ActionExecuting filter filter.OnActionExecuting(preContext);
if (preContext.Result != null) {
     //When you can't get through the filter, you go here.
return new ActionExecutedContext(preContext, preContext.ActionDescriptor, true, null)
      { Result = preContext.Result }; } bool flag = false; ActionExecutedContext filterContext = null; try {
     //1. The first time you perform this step, you will jump to the Invoke Action Filter (filter, preContext, next) to execute it.
      //2. The second time you run here, you will execute the above seed Method
      //When the seed method is executed, go back and run the InvokeActionMethod() method filterContext
= continuation(); } catch (ThreadAbortException) { filterContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false, null); filter.OnActionExecuted(filterContext); throw; } catch (Exception exception) { flag = true; filterContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false, exception); filter.OnActionExecuted(filterContext); if (!filterContext.ExceptionHandled) { throw; } } if (!flag) { filter.OnActionExecuted(filterContext); } return filterContext; }

Here, you can see the execution time of ActionExecuting and ActionExecuted filters.

According to the comments above, then take a look at the InvokeAction Method method

protected virtual ActionResult InvokeActionMethod(ControllerContext controllerContext,
     ActionDescriptor actionDescriptor, IDictionary<string, object> parameters) { object actionReturnValue = actionDescriptor.Execute(controllerContext, parameters); return this.CreateActionResult(controllerContext, actionDescriptor, actionReturnValue); }

The Execute method here returns ActionResult, which is called to convert the returned actionReturnValue to ActionResult for type conversion.

This is actually the Action method that is executed and the result of execution is returned.

 

2. Result execution and filter execution

//ControllerActionInvoker
protected virtual ResultExecutedContext InvokeActionResultWithFilters(ControllerContext controllerContext,
   IList<IResultFilter> filters, ActionResult actionResult) { ResultExecutingContext preContext = new ResultExecutingContext(controllerContext, actionResult); Func<ResultExecutedContext> seed = delegate { this.InvokeActionResult(controllerContext, actionResult); return new ResultExecutedContext(controllerContext, actionResult, false, null); }; return filters
      .Reverse<IResultFilter>()
      .Aggregate<IResultFilter, Func<ResultExecutedContext>>(seed,
        (next, filter) => () => InvokeActionResultFilter(filter, preContext, next))
      (); }

Look at the structure of the method here and the Action above. And notice the return value here: ResultExecutedContext, which returns the result of executing View.

First look at the InvokeAction ResultFilter method

1. InvokeActionResultFilter

internal static ResultExecutedContext InvokeActionResultFilter(IResultFilter filter,
   ResultExecutingContext preContext, Func<ResultExecutedContext> continuation) { filter.OnResultExecuting(preContext); if (preContext.Cancel) { return new ResultExecutedContext(preContext, preContext.Result, true, null); } bool flag = false; ResultExecutedContext filterContext = null; try { filterContext = continuation(); } catch (ThreadAbortException) { filterContext = new ResultExecutedContext(preContext, preContext.Result, false, null); filter.OnResultExecuted(filterContext); throw; } catch (Exception exception) { flag = true; filterContext = new ResultExecutedContext(preContext, preContext.Result, false, exception); filter.OnResultExecuted(filterContext); if (!filterContext.ExceptionHandled) { throw; } } if (!flag) { filter.OnResultExecuted(filterContext); } return filterContext; }

The execution order of the internal method is the same. From this method, it can be seen that the execution order of the Result filter is exactly as mentioned above. Executing is performed first, then Result is executed, and finally Executed method is executed.

 

2. InvokeActionResult 

protected virtual void InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)
{
    actionResult.ExecuteResult(controllerContext);
}

This step is to execute View. If there is an opportunity for the specific execution process, it will be parsed later.

This article is mainly to verify whether the execution sequence of the four Action/Result filters is as described above. Of course, in the process of exploring, we also see the execution time of the Action/Result method.

 

Reference resources:

  In-depth analysis of Asp.net Mvc

Directory synchronized

Posted by schajee on Thu, 21 Mar 2019 14:36:51 -0700