Lazy create MVC ControllerActionInvoker statemachines (#9533)
* Lazy create ControllerActionInvoker.InvokeInnerFilterAsync statemachine * Lazy create ControllerActionInvoker.InvokeNextActionFilterAwaitedAsync statemachine * Lazy create ControllerActionInvoker.InvokeNextActionFilterAsync statemachine * Lazy create ControllerActionInvoker.InvokeActionMethodAsync statemachine * Lazy create Controller.OnActionExecutionAsync statemachine * Ref assembly * Formatting feedback
This commit is contained in:
parent
a1d2d64120
commit
d46388b58e
|
|
@ -282,7 +282,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task InvokeNextActionFilterAsync()
|
private Task InvokeNextActionFilterAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -292,7 +292,11 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
||||||
var isCompleted = false;
|
var isCompleted = false;
|
||||||
while (!isCompleted)
|
while (!isCompleted)
|
||||||
{
|
{
|
||||||
await Next(ref next, ref scope, ref state, ref isCompleted);
|
var lastTask = Next(ref next, ref scope, ref state, ref isCompleted);
|
||||||
|
if (!lastTask.IsCompletedSuccessfully)
|
||||||
|
{
|
||||||
|
return Awaited(this, lastTask, next, scope, state, isCompleted);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
|
|
@ -304,14 +308,59 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug.Assert(_actionExecutedContext != null);
|
Debug.Assert(_actionExecutedContext != null);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
static async Task Awaited(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await lastTask;
|
||||||
|
|
||||||
|
while (!isCompleted)
|
||||||
|
{
|
||||||
|
await invoker.Next(ref next, ref scope, ref state, ref isCompleted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
invoker._actionExecutedContext = new ActionExecutedContext(invoker._controllerContext, invoker._filters, invoker._instance)
|
||||||
|
{
|
||||||
|
ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Assert(invoker._actionExecutedContext != null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<ActionExecutedContext> InvokeNextActionFilterAwaitedAsync()
|
private Task<ActionExecutedContext> InvokeNextActionFilterAwaitedAsync()
|
||||||
{
|
{
|
||||||
Debug.Assert(_actionExecutingContext != null);
|
Debug.Assert(_actionExecutingContext != null);
|
||||||
if (_actionExecutingContext.Result != null)
|
if (_actionExecutingContext.Result != null)
|
||||||
{
|
{
|
||||||
// If we get here, it means that an async filter set a result AND called next(). This is forbidden.
|
// If we get here, it means that an async filter set a result AND called next(). This is forbidden.
|
||||||
|
return Throw();
|
||||||
|
}
|
||||||
|
|
||||||
|
var task = InvokeNextActionFilterAsync();
|
||||||
|
if (!task.IsCompletedSuccessfully)
|
||||||
|
{
|
||||||
|
return Awaited(this, task);
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Assert(_actionExecutedContext != null);
|
||||||
|
return Task.FromResult(_actionExecutedContext);
|
||||||
|
|
||||||
|
static async Task<ActionExecutedContext> Awaited(ControllerActionInvoker invoker, Task task)
|
||||||
|
{
|
||||||
|
await task;
|
||||||
|
|
||||||
|
Debug.Assert(invoker._actionExecutedContext != null);
|
||||||
|
return invoker._actionExecutedContext;
|
||||||
|
}
|
||||||
|
#pragma warning disable CS1998
|
||||||
|
static async Task<ActionExecutedContext> Throw()
|
||||||
|
{
|
||||||
var message = Resources.FormatAsyncActionFilter_InvalidShortCircuit(
|
var message = Resources.FormatAsyncActionFilter_InvalidShortCircuit(
|
||||||
typeof(IAsyncActionFilter).Name,
|
typeof(IAsyncActionFilter).Name,
|
||||||
nameof(ActionExecutingContext.Result),
|
nameof(ActionExecutingContext.Result),
|
||||||
|
|
@ -320,69 +369,119 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
||||||
|
|
||||||
throw new InvalidOperationException(message);
|
throw new InvalidOperationException(message);
|
||||||
}
|
}
|
||||||
|
#pragma warning restore CS1998
|
||||||
await InvokeNextActionFilterAsync();
|
|
||||||
|
|
||||||
Debug.Assert(_actionExecutedContext != null);
|
|
||||||
return _actionExecutedContext;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task InvokeActionMethodAsync()
|
private Task InvokeActionMethodAsync()
|
||||||
{
|
{
|
||||||
var controllerContext = _controllerContext;
|
if (_diagnosticListener.IsEnabled() || _logger.IsEnabled(LogLevel.Trace))
|
||||||
var objectMethodExecutor = _cacheEntry.ObjectMethodExecutor;
|
|
||||||
var controller = _instance;
|
|
||||||
var arguments = _arguments;
|
|
||||||
var actionMethodExecutor = _cacheEntry.ActionMethodExecutor;
|
|
||||||
var orderedArguments = PrepareArguments(arguments, objectMethodExecutor);
|
|
||||||
|
|
||||||
var diagnosticListener = _diagnosticListener;
|
|
||||||
var logger = _logger;
|
|
||||||
|
|
||||||
IActionResult result = null;
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
diagnosticListener.BeforeActionMethod(
|
return Logged(this);
|
||||||
controllerContext,
|
|
||||||
arguments,
|
|
||||||
controller);
|
|
||||||
logger.ActionMethodExecuting(controllerContext, orderedArguments);
|
|
||||||
var stopwatch = ValueStopwatch.StartNew();
|
|
||||||
var actionResultValueTask = actionMethodExecutor.Execute(_mapper, objectMethodExecutor, controller, orderedArguments);
|
|
||||||
if (actionResultValueTask.IsCompletedSuccessfully)
|
|
||||||
{
|
|
||||||
result = actionResultValueTask.Result;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = await actionResultValueTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
_result = result;
|
|
||||||
logger.ActionMethodExecuted(controllerContext, result, stopwatch.GetElapsedTime());
|
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
|
var objectMethodExecutor = _cacheEntry.ObjectMethodExecutor;
|
||||||
|
var actionMethodExecutor = _cacheEntry.ActionMethodExecutor;
|
||||||
|
var orderedArguments = PrepareArguments(_arguments, objectMethodExecutor);
|
||||||
|
|
||||||
|
var actionResultValueTask = actionMethodExecutor.Execute(_mapper, objectMethodExecutor, _instance, orderedArguments);
|
||||||
|
if (actionResultValueTask.IsCompletedSuccessfully)
|
||||||
{
|
{
|
||||||
diagnosticListener.AfterActionMethod(
|
_result = actionResultValueTask.Result;
|
||||||
controllerContext,
|
}
|
||||||
arguments,
|
else
|
||||||
controllerContext,
|
{
|
||||||
result);
|
return Awaited(this, actionResultValueTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
static async Task Awaited(ControllerActionInvoker invoker, ValueTask<IActionResult> actionResultValueTask)
|
||||||
|
{
|
||||||
|
invoker._result = await actionResultValueTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async Task Logged(ControllerActionInvoker invoker)
|
||||||
|
{
|
||||||
|
var controllerContext = invoker._controllerContext;
|
||||||
|
var objectMethodExecutor = invoker._cacheEntry.ObjectMethodExecutor;
|
||||||
|
var controller = invoker._instance;
|
||||||
|
var arguments = invoker._arguments;
|
||||||
|
var actionMethodExecutor = invoker._cacheEntry.ActionMethodExecutor;
|
||||||
|
var orderedArguments = PrepareArguments(arguments, objectMethodExecutor);
|
||||||
|
|
||||||
|
var diagnosticListener = invoker._diagnosticListener;
|
||||||
|
var logger = invoker._logger;
|
||||||
|
|
||||||
|
IActionResult result = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
diagnosticListener.BeforeActionMethod(
|
||||||
|
controllerContext,
|
||||||
|
arguments,
|
||||||
|
controller);
|
||||||
|
logger.ActionMethodExecuting(controllerContext, orderedArguments);
|
||||||
|
var stopwatch = ValueStopwatch.StartNew();
|
||||||
|
var actionResultValueTask = actionMethodExecutor.Execute(invoker._mapper, objectMethodExecutor, controller, orderedArguments);
|
||||||
|
if (actionResultValueTask.IsCompletedSuccessfully)
|
||||||
|
{
|
||||||
|
result = actionResultValueTask.Result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = await actionResultValueTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
invoker._result = result;
|
||||||
|
logger.ActionMethodExecuted(controllerContext, result, stopwatch.GetElapsedTime());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
diagnosticListener.AfterActionMethod(
|
||||||
|
controllerContext,
|
||||||
|
arguments,
|
||||||
|
controllerContext,
|
||||||
|
result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <remarks><see cref="ResourceInvoker.InvokeFilterPipelineAsync"/> for details on what the
|
/// <remarks><see cref="ResourceInvoker.InvokeFilterPipelineAsync"/> for details on what the
|
||||||
/// variables in this method represent.</remarks>
|
/// variables in this method represent.</remarks>
|
||||||
protected override async Task InvokeInnerFilterAsync()
|
protected override Task InvokeInnerFilterAsync()
|
||||||
{
|
{
|
||||||
var next = State.ActionBegin;
|
try
|
||||||
var scope = Scope.Invoker;
|
|
||||||
var state = (object)null;
|
|
||||||
var isCompleted = false;
|
|
||||||
|
|
||||||
while (!isCompleted)
|
|
||||||
{
|
{
|
||||||
await Next(ref next, ref scope, ref state, ref isCompleted);
|
var next = State.ActionBegin;
|
||||||
|
var scope = Scope.Invoker;
|
||||||
|
var state = (object)null;
|
||||||
|
var isCompleted = false;
|
||||||
|
|
||||||
|
while (!isCompleted)
|
||||||
|
{
|
||||||
|
var lastTask = Next(ref next, ref scope, ref state, ref isCompleted);
|
||||||
|
if (!lastTask.IsCompletedSuccessfully)
|
||||||
|
{
|
||||||
|
return Awaited(this, lastTask, next, scope, state, isCompleted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Wrap non task-wrapped exceptions in a Task,
|
||||||
|
// as this isn't done automatically since the method is not async.
|
||||||
|
return Task.FromException(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async Task Awaited(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
|
||||||
|
{
|
||||||
|
await lastTask;
|
||||||
|
|
||||||
|
while (!isCompleted)
|
||||||
|
{
|
||||||
|
await invoker.Next(ref next, ref scope, ref state, ref isCompleted);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
[Microsoft.AspNetCore.Mvc.NonActionAttribute]
|
[Microsoft.AspNetCore.Mvc.NonActionAttribute]
|
||||||
public virtual void OnActionExecuting(Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext context) { }
|
public virtual void OnActionExecuting(Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext context) { }
|
||||||
[Microsoft.AspNetCore.Mvc.NonActionAttribute]
|
[Microsoft.AspNetCore.Mvc.NonActionAttribute]
|
||||||
[System.Diagnostics.DebuggerStepThroughAttribute]
|
|
||||||
public virtual System.Threading.Tasks.Task OnActionExecutionAsync(Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext context, Microsoft.AspNetCore.Mvc.Filters.ActionExecutionDelegate next) { throw null; }
|
public virtual System.Threading.Tasks.Task OnActionExecutionAsync(Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext context, Microsoft.AspNetCore.Mvc.Filters.ActionExecutionDelegate next) { throw null; }
|
||||||
[Microsoft.AspNetCore.Mvc.NonActionAttribute]
|
[Microsoft.AspNetCore.Mvc.NonActionAttribute]
|
||||||
public virtual Microsoft.AspNetCore.Mvc.PartialViewResult PartialView() { throw null; }
|
public virtual Microsoft.AspNetCore.Mvc.PartialViewResult PartialView() { throw null; }
|
||||||
|
|
|
||||||
|
|
@ -340,7 +340,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
/// of <see cref="OnActionExecutionAsync" /> to continue execution of the action.</param>
|
/// of <see cref="OnActionExecutionAsync" /> to continue execution of the action.</param>
|
||||||
/// <returns>A <see cref="Task"/> instance.</returns>
|
/// <returns>A <see cref="Task"/> instance.</returns>
|
||||||
[NonAction]
|
[NonAction]
|
||||||
public virtual async Task OnActionExecutionAsync(
|
public virtual Task OnActionExecutionAsync(
|
||||||
ActionExecutingContext context,
|
ActionExecutingContext context,
|
||||||
ActionExecutionDelegate next)
|
ActionExecutionDelegate next)
|
||||||
{
|
{
|
||||||
|
|
@ -357,7 +357,20 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
OnActionExecuting(context);
|
OnActionExecuting(context);
|
||||||
if (context.Result == null)
|
if (context.Result == null)
|
||||||
{
|
{
|
||||||
OnActionExecuted(await next());
|
var task = next();
|
||||||
|
if (!task.IsCompletedSuccessfully)
|
||||||
|
{
|
||||||
|
return Awaited(this, task);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnActionExecuted(task.Result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
static async Task Awaited(Controller controller, Task<ActionExecutedContext> task)
|
||||||
|
{
|
||||||
|
controller.OnActionExecuted(await task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue