File: Infrastructure\ResourceInvoker.cs
Web Access
Project: src\src\Mvc\Mvc.Core\src\Microsoft.AspNetCore.Mvc.Core.csproj (Microsoft.AspNetCore.Mvc.Core)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Runtime.ExceptionServices;
using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Logging;
 
namespace Microsoft.AspNetCore.Mvc.Infrastructure;
 
internal abstract partial class ResourceInvoker
{
    protected readonly DiagnosticListener _diagnosticListener;
    protected readonly ILogger _logger;
    protected readonly IActionContextAccessor _actionContextAccessor;
    protected readonly IActionResultTypeMapper _mapper;
    protected readonly ActionContext _actionContext;
    protected readonly IFilterMetadata[] _filters;
    protected readonly IList<IValueProviderFactory> _valueProviderFactories;
 
    private AuthorizationFilterContextSealed? _authorizationContext;
    private ResourceExecutingContextSealed? _resourceExecutingContext;
    private ResourceExecutedContextSealed? _resourceExecutedContext;
    private ExceptionContextSealed? _exceptionContext;
    private ResultExecutingContextSealed? _resultExecutingContext;
    private ResultExecutedContextSealed? _resultExecutedContext;
 
    // Do not make this readonly, it's mutable. We don't want to make a copy.
    // https://blogs.msdn.microsoft.com/ericlippert/2008/05/14/mutating-readonly-structs/
    protected FilterCursor _cursor;
    protected IActionResult? _result;
    protected object? _instance;
 
    public ResourceInvoker(
        DiagnosticListener diagnosticListener,
        ILogger logger,
        IActionContextAccessor actionContextAccessor,
        IActionResultTypeMapper mapper,
        ActionContext actionContext,
        IFilterMetadata[] filters,
        IList<IValueProviderFactory> valueProviderFactories)
    {
        _diagnosticListener = diagnosticListener ?? throw new ArgumentNullException(nameof(diagnosticListener));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        _actionContextAccessor = actionContextAccessor ?? throw new ArgumentNullException(nameof(actionContextAccessor));
        _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
        _actionContext = actionContext ?? throw new ArgumentNullException(nameof(actionContext));
 
        _filters = filters ?? throw new ArgumentNullException(nameof(filters));
        _valueProviderFactories = valueProviderFactories ?? throw new ArgumentNullException(nameof(valueProviderFactories));
        _cursor = new FilterCursor(filters);
    }
 
    public virtual Task InvokeAsync()
    {
        if (_diagnosticListener.IsEnabled() || _logger.IsEnabled(LogLevel.Information))
        {
            return Logged(this);
        }
 
        _actionContextAccessor.ActionContext = _actionContext;
        var scope = _logger.ActionScope(_actionContext.ActionDescriptor);
 
        Task task;
        try
        {
            task = InvokeFilterPipelineAsync();
        }
        catch (Exception exception)
        {
            return Awaited(this, Task.FromException(exception), scope);
        }
 
        if (!task.IsCompletedSuccessfully)
        {
            return Awaited(this, task, scope);
        }
 
        return ReleaseResourcesCore(scope).AsTask();
 
        static async Task Awaited(ResourceInvoker invoker, Task task, IDisposable? scope)
        {
            try
            {
                await task;
            }
            finally
            {
                await invoker.ReleaseResourcesCore(scope);
            }
        }
 
        static async Task Logged(ResourceInvoker invoker)
        {
            var actionContext = invoker._actionContext;
            invoker._actionContextAccessor.ActionContext = actionContext;
            try
            {
                var logger = invoker._logger;
 
                invoker._diagnosticListener.BeforeAction(
                    actionContext.ActionDescriptor,
                    actionContext.HttpContext,
                    actionContext.RouteData);
 
                var actionScope = logger.ActionScope(actionContext.ActionDescriptor);
 
                Log.ExecutingAction(logger, actionContext.ActionDescriptor);
 
                var filters = invoker._filters;
                logger.AuthorizationFiltersExecutionPlan(filters);
                logger.ResourceFiltersExecutionPlan(filters);
                logger.ActionFiltersExecutionPlan(filters);
                logger.ExceptionFiltersExecutionPlan(filters);
                logger.ResultFiltersExecutionPlan(filters);
 
                var stopwatch = ValueStopwatch.StartNew();
 
                try
                {
                    await invoker.InvokeFilterPipelineAsync();
                }
                finally
                {
                    await invoker.ReleaseResourcesCore(actionScope);
                    Log.ExecutedAction(logger, actionContext.ActionDescriptor, stopwatch.GetElapsedTime());
                }
            }
            finally
            {
                invoker._diagnosticListener.AfterAction(
                    actionContext.ActionDescriptor,
                    actionContext.HttpContext,
                    actionContext.RouteData);
            }
        }
    }
 
    internal ValueTask ReleaseResourcesCore(IDisposable? scope)
    {
        Exception? releaseException = null;
        ValueTask releaseResult;
        try
        {
            releaseResult = ReleaseResources();
            if (!releaseResult.IsCompletedSuccessfully)
            {
                return HandleAsyncReleaseErrors(releaseResult, scope);
            }
        }
        catch (Exception exception)
        {
            releaseException = exception;
        }
 
        return HandleReleaseErrors(scope, releaseException);
 
        static async ValueTask HandleAsyncReleaseErrors(ValueTask releaseResult, IDisposable? scope)
        {
            Exception? releaseException = null;
            try
            {
                await releaseResult;
            }
            catch (Exception exception)
            {
                releaseException = exception;
            }
 
            await HandleReleaseErrors(scope, releaseException);
        }
 
        static ValueTask HandleReleaseErrors(IDisposable? scope, Exception? releaseException)
        {
            Exception? scopeException = null;
            try
            {
                scope?.Dispose();
            }
            catch (Exception exception)
            {
                scopeException = exception;
            }
 
            if (releaseException == null && scopeException == null)
            {
                return default;
            }
            else if (releaseException != null && scopeException != null)
            {
                return ValueTask.FromException(new AggregateException(releaseException, scopeException));
            }
            else if (releaseException != null)
            {
                return ValueTask.FromException(releaseException);
            }
            else
            {
                return ValueTask.FromException(scopeException!);
            }
        }
    }
 
    /// <summary>
    /// In derived types, releases resources such as controller, model, or page instances created as
    /// part of invoking the inner pipeline.
    /// </summary>
    protected abstract ValueTask ReleaseResources();
 
    private Task InvokeFilterPipelineAsync()
    {
        var next = State.InvokeBegin;
 
        // The `scope` tells the `Next` method who the caller is, and what kind of state to initialize to
        // communicate a result. The outermost scope is `Scope.Invoker` and doesn't require any type
        // of context or result other than throwing.
        var scope = Scope.Invoker;
 
        // The `state` is used for internal state handling during transitions between states. In practice this
        // means storing a filter instance in `state` and then retrieving it in the next state.
        var state = (object?)null;
 
        // `isCompleted` will be set to true when we've reached a terminal state.
        var isCompleted = false;
        try
        {
            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(ResourceInvoker 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);
            }
        }
    }
 
    protected abstract Task InvokeInnerFilterAsync();
 
    protected virtual Task InvokeResultAsync(IActionResult result)
    {
        if (_diagnosticListener.IsEnabled() || _logger.IsEnabled(LogLevel.Trace))
        {
            return Logged(this, result);
        }
 
        return result.ExecuteResultAsync(_actionContext);
 
        static async Task Logged(ResourceInvoker invoker, IActionResult result)
        {
            var actionContext = invoker._actionContext;
 
            invoker._diagnosticListener.BeforeActionResult(actionContext, result);
            Log.BeforeExecutingActionResult(invoker._logger, result);
 
            try
            {
                await result.ExecuteResultAsync(actionContext);
            }
            finally
            {
                invoker._diagnosticListener.AfterActionResult(actionContext, result);
                Log.AfterExecutingActionResult(invoker._logger, result);
            }
        }
    }
 
    private Task Next(ref State next, ref Scope scope, ref object? state, ref bool isCompleted)
    {
        switch (next)
        {
            case State.InvokeBegin:
                {
                    goto case State.AuthorizationBegin;
                }
 
            case State.AuthorizationBegin:
                {
                    _cursor.Reset();
                    goto case State.AuthorizationNext;
                }
 
            case State.AuthorizationNext:
                {
                    var current = _cursor.GetNextFilter<IAuthorizationFilter, IAsyncAuthorizationFilter>();
                    if (current.FilterAsync != null)
                    {
                        if (_authorizationContext == null)
                        {
                            _authorizationContext = new AuthorizationFilterContextSealed(_actionContext, _filters);
                        }
 
                        state = current.FilterAsync;
                        goto case State.AuthorizationAsyncBegin;
                    }
                    else if (current.Filter != null)
                    {
                        if (_authorizationContext == null)
                        {
                            _authorizationContext = new AuthorizationFilterContextSealed(_actionContext, _filters);
                        }
 
                        state = current.Filter;
                        goto case State.AuthorizationSync;
                    }
                    else
                    {
                        goto case State.AuthorizationEnd;
                    }
                }
 
            case State.AuthorizationAsyncBegin:
                {
                    Debug.Assert(state != null);
                    Debug.Assert(_authorizationContext != null);
 
                    var filter = (IAsyncAuthorizationFilter)state;
                    var authorizationContext = _authorizationContext;
 
                    _diagnosticListener.BeforeOnAuthorizationAsync(authorizationContext, filter);
                    _logger.BeforeExecutingMethodOnFilter(
                        FilterTypeConstants.AuthorizationFilter,
                        nameof(IAsyncAuthorizationFilter.OnAuthorizationAsync),
                        filter);
 
                    var task = filter.OnAuthorizationAsync(authorizationContext);
                    if (!task.IsCompletedSuccessfully)
                    {
                        next = State.AuthorizationAsyncEnd;
                        return task;
                    }
 
                    goto case State.AuthorizationAsyncEnd;
                }
 
            case State.AuthorizationAsyncEnd:
                {
                    Debug.Assert(state != null);
                    Debug.Assert(_authorizationContext != null);
 
                    var filter = (IAsyncAuthorizationFilter)state;
                    var authorizationContext = _authorizationContext;
 
                    _diagnosticListener.AfterOnAuthorizationAsync(authorizationContext, filter);
                    _logger.AfterExecutingMethodOnFilter(
                        FilterTypeConstants.AuthorizationFilter,
                        nameof(IAsyncAuthorizationFilter.OnAuthorizationAsync),
                        filter);
 
                    if (authorizationContext.Result != null)
                    {
                        goto case State.AuthorizationShortCircuit;
                    }
 
                    goto case State.AuthorizationNext;
                }
 
            case State.AuthorizationSync:
                {
                    Debug.Assert(state != null);
                    Debug.Assert(_authorizationContext != null);
 
                    var filter = (IAuthorizationFilter)state;
                    var authorizationContext = _authorizationContext;
 
                    _diagnosticListener.BeforeOnAuthorization(authorizationContext, filter);
                    _logger.BeforeExecutingMethodOnFilter(
                        FilterTypeConstants.AuthorizationFilter,
                        nameof(IAuthorizationFilter.OnAuthorization),
                        filter);
 
                    filter.OnAuthorization(authorizationContext);
 
                    _diagnosticListener.AfterOnAuthorization(authorizationContext, filter);
                    _logger.AfterExecutingMethodOnFilter(
                        FilterTypeConstants.AuthorizationFilter,
                        nameof(IAuthorizationFilter.OnAuthorization),
                        filter);
 
                    if (authorizationContext.Result != null)
                    {
                        goto case State.AuthorizationShortCircuit;
                    }
 
                    goto case State.AuthorizationNext;
                }
 
            case State.AuthorizationShortCircuit:
                {
                    Debug.Assert(state != null);
                    Debug.Assert(_authorizationContext != null);
                    Debug.Assert(_authorizationContext.Result != null);
                    Log.AuthorizationFailure(_logger, (IFilterMetadata)state);
 
                    // This is a short-circuit - execute relevant result filters + result and complete this invocation.
                    isCompleted = true;
                    _result = _authorizationContext.Result;
                    return InvokeAlwaysRunResultFilters();
                }
 
            case State.AuthorizationEnd:
                {
                    goto case State.ResourceBegin;
                }
 
            case State.ResourceBegin:
                {
                    _cursor.Reset();
                    goto case State.ResourceNext;
                }
 
            case State.ResourceNext:
                {
                    var current = _cursor.GetNextFilter<IResourceFilter, IAsyncResourceFilter>();
                    if (current.FilterAsync != null)
                    {
                        if (_resourceExecutingContext == null)
                        {
                            _resourceExecutingContext = new ResourceExecutingContextSealed(
                                _actionContext,
                                _filters,
                                _valueProviderFactories);
                        }
 
                        state = current.FilterAsync;
                        goto case State.ResourceAsyncBegin;
                    }
                    else if (current.Filter != null)
                    {
                        if (_resourceExecutingContext == null)
                        {
                            _resourceExecutingContext = new ResourceExecutingContextSealed(
                                _actionContext,
                                _filters,
                                _valueProviderFactories);
                        }
 
                        state = current.Filter;
                        goto case State.ResourceSyncBegin;
                    }
                    else
                    {
                        // All resource filters are currently on the stack - now execute the 'inside'.
                        goto case State.ResourceInside;
                    }
                }
 
            case State.ResourceAsyncBegin:
                {
                    Debug.Assert(state != null);
                    Debug.Assert(_resourceExecutingContext != null);
 
                    var filter = (IAsyncResourceFilter)state;
                    var resourceExecutingContext = _resourceExecutingContext;
 
                    _diagnosticListener.BeforeOnResourceExecution(resourceExecutingContext, filter);
                    _logger.BeforeExecutingMethodOnFilter(
                        FilterTypeConstants.ResourceFilter,
                        nameof(IAsyncResourceFilter.OnResourceExecutionAsync),
                        filter);
 
                    var task = filter.OnResourceExecutionAsync(resourceExecutingContext, InvokeNextResourceFilterAwaitedAsync);
                    if (!task.IsCompletedSuccessfully)
                    {
                        next = State.ResourceAsyncEnd;
                        return task;
                    }
 
                    goto case State.ResourceAsyncEnd;
                }
 
            case State.ResourceAsyncEnd:
                {
                    Debug.Assert(state != null);
                    Debug.Assert(_resourceExecutingContext != null);
 
                    var filter = (IAsyncResourceFilter)state;
                    try
                    {
                        if (_resourceExecutedContext == null)
                        {
                            // If we get here then the filter didn't call 'next' indicating a short circuit.
                            _resourceExecutedContext = new ResourceExecutedContextSealed(_resourceExecutingContext, _filters)
                            {
                                Canceled = true,
                                Result = _resourceExecutingContext.Result,
                            };
 
                            // A filter could complete a Task without setting a result
                            if (_resourceExecutingContext.Result != null)
                            {
                                goto case State.ResourceShortCircuit;
                            }
                        }
                    }
                    finally
                    {
                        _diagnosticListener.AfterOnResourceExecution(_resourceExecutedContext, filter);
                        _logger.AfterExecutingMethodOnFilter(
                            FilterTypeConstants.ResourceFilter,
                            nameof(IAsyncResourceFilter.OnResourceExecutionAsync),
                            filter);
                    }
                    goto case State.ResourceEnd;
                }
 
            case State.ResourceSyncBegin:
                {
                    Debug.Assert(state != null);
                    Debug.Assert(_resourceExecutingContext != null);
 
                    var filter = (IResourceFilter)state;
                    var resourceExecutingContext = _resourceExecutingContext;
 
                    _diagnosticListener.BeforeOnResourceExecuting(resourceExecutingContext, filter);
                    _logger.BeforeExecutingMethodOnFilter(
                        FilterTypeConstants.ResourceFilter,
                        nameof(IResourceFilter.OnResourceExecuting),
                        filter);
 
                    filter.OnResourceExecuting(resourceExecutingContext);
 
                    _diagnosticListener.AfterOnResourceExecuting(resourceExecutingContext, filter);
                    _logger.AfterExecutingMethodOnFilter(
                        FilterTypeConstants.ResourceFilter,
                        nameof(IResourceFilter.OnResourceExecuting),
                        filter);
 
                    if (resourceExecutingContext.Result != null)
                    {
                        _resourceExecutedContext = new ResourceExecutedContextSealed(resourceExecutingContext, _filters)
                        {
                            Canceled = true,
                            Result = _resourceExecutingContext.Result,
                        };
 
                        goto case State.ResourceShortCircuit;
                    }
 
                    var task = InvokeNextResourceFilter();
                    if (!task.IsCompletedSuccessfully)
                    {
                        next = State.ResourceSyncEnd;
                        return task;
                    }
 
                    goto case State.ResourceSyncEnd;
                }
 
            case State.ResourceSyncEnd:
                {
                    Debug.Assert(state != null);
                    Debug.Assert(_resourceExecutingContext != null);
                    Debug.Assert(_resourceExecutedContext != null);
 
                    var filter = (IResourceFilter)state;
                    var resourceExecutedContext = _resourceExecutedContext;
 
                    _diagnosticListener.BeforeOnResourceExecuted(resourceExecutedContext, filter);
                    _logger.BeforeExecutingMethodOnFilter(
                        FilterTypeConstants.ResourceFilter,
                        nameof(IResourceFilter.OnResourceExecuted),
                        filter);
 
                    filter.OnResourceExecuted(resourceExecutedContext);
 
                    _diagnosticListener.AfterOnResourceExecuted(resourceExecutedContext, filter);
                    _logger.AfterExecutingMethodOnFilter(
                        FilterTypeConstants.ResourceFilter,
                        nameof(IResourceFilter.OnResourceExecuted),
                        filter);
 
                    goto case State.ResourceEnd;
                }
 
            case State.ResourceShortCircuit:
                {
                    Debug.Assert(state != null);
                    Debug.Assert(_resourceExecutingContext != null);
                    Debug.Assert(_resourceExecutedContext != null);
                    Log.ResourceFilterShortCircuited(_logger, (IFilterMetadata)state);
 
                    _result = _resourceExecutingContext.Result;
                    var task = InvokeAlwaysRunResultFilters();
                    if (!task.IsCompletedSuccessfully)
                    {
                        next = State.ResourceEnd;
                        return task;
                    }
 
                    goto case State.ResourceEnd;
                }
 
            case State.ResourceInside:
                {
                    goto case State.ExceptionBegin;
                }
 
            case State.ExceptionBegin:
                {
                    _cursor.Reset();
                    goto case State.ExceptionNext;
                }
 
            case State.ExceptionNext:
                {
                    var current = _cursor.GetNextFilter<IExceptionFilter, IAsyncExceptionFilter>();
                    if (current.FilterAsync != null)
                    {
                        state = current.FilterAsync;
                        goto case State.ExceptionAsyncBegin;
                    }
                    else if (current.Filter != null)
                    {
                        state = current.Filter;
                        goto case State.ExceptionSyncBegin;
                    }
                    else if (scope == Scope.Exception)
                    {
                        // All exception filters are on the stack already - so execute the 'inside'.
                        goto case State.ExceptionInside;
                    }
                    else
                    {
                        // There are no exception filters - so jump right to the action.
                        Debug.Assert(scope == Scope.Invoker || scope == Scope.Resource);
                        goto case State.ActionBegin;
                    }
                }
 
            case State.ExceptionAsyncBegin:
                {
                    var task = InvokeNextExceptionFilterAsync();
                    if (!task.IsCompletedSuccessfully)
                    {
                        next = State.ExceptionAsyncResume;
                        return task;
                    }
 
                    goto case State.ExceptionAsyncResume;
                }
 
            case State.ExceptionAsyncResume:
                {
                    Debug.Assert(state != null);
 
                    var filter = (IAsyncExceptionFilter)state;
                    var exceptionContext = _exceptionContext;
 
                    // When we get here we're 'unwinding' the stack of exception filters. If we have an unhandled exception,
                    // we'll call the filter. Otherwise there's nothing to do.
                    if (exceptionContext?.Exception != null && !exceptionContext.ExceptionHandled)
                    {
                        _diagnosticListener.BeforeOnExceptionAsync(exceptionContext, filter);
                        _logger.BeforeExecutingMethodOnFilter(
                            FilterTypeConstants.ExceptionFilter,
                            nameof(IAsyncExceptionFilter.OnExceptionAsync),
                            filter);
 
                        var task = filter.OnExceptionAsync(exceptionContext);
                        if (!task.IsCompletedSuccessfully)
                        {
                            next = State.ExceptionAsyncEnd;
                            return task;
                        }
 
                        goto case State.ExceptionAsyncEnd;
                    }
 
                    goto case State.ExceptionEnd;
                }
 
            case State.ExceptionAsyncEnd:
                {
                    Debug.Assert(state != null);
                    Debug.Assert(_exceptionContext != null);
 
                    var filter = (IAsyncExceptionFilter)state;
                    var exceptionContext = _exceptionContext;
 
                    _diagnosticListener.AfterOnExceptionAsync(exceptionContext, filter);
                    _logger.AfterExecutingMethodOnFilter(
                        FilterTypeConstants.ExceptionFilter,
                        nameof(IAsyncExceptionFilter.OnExceptionAsync),
                        filter);
 
                    if (exceptionContext.Exception == null || exceptionContext.ExceptionHandled)
                    {
                        // We don't need to do anything to trigger a short circuit. If there's another
                        // exception filter on the stack it will check the same set of conditions
                        // and then just skip itself.
                        _logger.ExceptionFilterShortCircuited(filter);
                    }
 
                    goto case State.ExceptionEnd;
                }
 
            case State.ExceptionSyncBegin:
                {
                    var task = InvokeNextExceptionFilterAsync();
                    if (!task.IsCompletedSuccessfully)
                    {
                        next = State.ExceptionSyncEnd;
                        return task;
                    }
 
                    goto case State.ExceptionSyncEnd;
                }
 
            case State.ExceptionSyncEnd:
                {
                    Debug.Assert(state != null);
 
                    var filter = (IExceptionFilter)state;
                    var exceptionContext = _exceptionContext;
 
                    // When we get here we're 'unwinding' the stack of exception filters. If we have an unhandled exception,
                    // we'll call the filter. Otherwise there's nothing to do.
                    if (exceptionContext?.Exception != null && !exceptionContext.ExceptionHandled)
                    {
                        _diagnosticListener.BeforeOnException(exceptionContext, filter);
                        _logger.BeforeExecutingMethodOnFilter(
                            FilterTypeConstants.ExceptionFilter,
                            nameof(IExceptionFilter.OnException),
                            filter);
 
                        filter.OnException(exceptionContext);
 
                        _diagnosticListener.AfterOnException(exceptionContext, filter);
                        _logger.AfterExecutingMethodOnFilter(
                            FilterTypeConstants.ExceptionFilter,
                            nameof(IExceptionFilter.OnException),
                            filter);
 
                        if (exceptionContext.Exception == null || exceptionContext.ExceptionHandled)
                        {
                            // We don't need to do anything to trigger a short circuit. If there's another
                            // exception filter on the stack it will check the same set of conditions
                            // and then just skip itself.
                            _logger.ExceptionFilterShortCircuited(filter);
                        }
                    }
 
                    goto case State.ExceptionEnd;
                }
 
            case State.ExceptionInside:
                {
                    goto case State.ActionBegin;
                }
 
            case State.ExceptionHandled:
                {
                    // We arrive in this state when an exception happened, but was handled by exception filters
                    // either by setting ExceptionHandled, or nulling out the Exception or setting a result
                    // on the ExceptionContext.
                    //
                    // We need to execute the result (if any) and then exit gracefully which unwinding Resource
                    // filters.
 
                    Debug.Assert(state != null);
                    Debug.Assert(_exceptionContext != null);
 
                    if (_exceptionContext.Result == null)
                    {
                        _exceptionContext.Result = new EmptyResult();
                    }
 
                    _result = _exceptionContext.Result;
 
                    var task = InvokeAlwaysRunResultFilters();
                    if (!task.IsCompletedSuccessfully)
                    {
                        next = State.ResourceInsideEnd;
                        return task;
                    }
 
                    goto case State.ResourceInsideEnd;
                }
 
            case State.ExceptionEnd:
                {
                    var exceptionContext = _exceptionContext;
 
                    if (scope == Scope.Exception)
                    {
                        isCompleted = true;
                        return Task.CompletedTask;
                    }
 
                    if (exceptionContext != null)
                    {
                        if (exceptionContext.Result != null ||
                            exceptionContext.Exception == null ||
                            exceptionContext.ExceptionHandled)
                        {
                            goto case State.ExceptionHandled;
                        }
 
                        Rethrow(exceptionContext);
                        Debug.Fail("unreachable");
                    }
 
                    var task = InvokeResultFilters();
                    if (!task.IsCompletedSuccessfully)
                    {
                        next = State.ResourceInsideEnd;
                        return task;
                    }
                    goto case State.ResourceInsideEnd;
                }
 
            case State.ActionBegin:
                {
                    var task = InvokeInnerFilterAsync();
                    if (!task.IsCompletedSuccessfully)
                    {
                        next = State.ActionEnd;
                        return task;
                    }
 
                    goto case State.ActionEnd;
                }
 
            case State.ActionEnd:
                {
                    if (scope == Scope.Exception)
                    {
                        // If we're inside an exception filter, let's allow those filters to 'unwind' before
                        // the result.
                        isCompleted = true;
                        return Task.CompletedTask;
                    }
 
                    Debug.Assert(scope == Scope.Invoker || scope == Scope.Resource);
                    var task = InvokeResultFilters();
                    if (!task.IsCompletedSuccessfully)
                    {
                        next = State.ResourceInsideEnd;
                        return task;
                    }
                    goto case State.ResourceInsideEnd;
                }
 
            case State.ResourceInsideEnd:
                {
                    if (scope == Scope.Resource)
                    {
                        _resourceExecutedContext = new ResourceExecutedContextSealed(_actionContext, _filters)
                        {
                            Result = _result,
                        };
 
                        goto case State.ResourceEnd;
                    }
 
                    goto case State.InvokeEnd;
                }
 
            case State.ResourceEnd:
                {
                    if (scope == Scope.Resource)
                    {
                        isCompleted = true;
                        return Task.CompletedTask;
                    }
 
                    Debug.Assert(scope == Scope.Invoker);
                    Rethrow(_resourceExecutedContext!);
 
                    goto case State.InvokeEnd;
                }
 
            case State.InvokeEnd:
                {
                    isCompleted = true;
                    return Task.CompletedTask;
                }
 
            default:
                throw new InvalidOperationException();
        }
    }
 
    private Task<ResourceExecutedContext> InvokeNextResourceFilterAwaitedAsync()
    {
        Debug.Assert(_resourceExecutingContext != null);
 
        if (_resourceExecutingContext.Result != null)
        {
            // If we get here, it means that an async filter set a result AND called next(). This is forbidden.
            return Throw();
        }
 
        var task = InvokeNextResourceFilter();
        if (!task.IsCompletedSuccessfully)
        {
            return Awaited(this, task);
        }
 
        Debug.Assert(_resourceExecutedContext != null);
        return Task.FromResult<ResourceExecutedContext>(_resourceExecutedContext);
 
        static async Task<ResourceExecutedContext> Awaited(ResourceInvoker invoker, Task task)
        {
            await task;
 
            Debug.Assert(invoker._resourceExecutedContext != null);
            return invoker._resourceExecutedContext;
        }
#pragma warning disable CS1998
        static async Task<ResourceExecutedContext> Throw()
        {
            var message = Resources.FormatAsyncResourceFilter_InvalidShortCircuit(
                typeof(IAsyncResourceFilter).Name,
                nameof(ResourceExecutingContext.Result),
                typeof(ResourceExecutingContext).Name,
                typeof(ResourceExecutionDelegate).Name);
            throw new InvalidOperationException(message);
        }
#pragma warning restore CS1998
    }
 
    private Task InvokeNextResourceFilter()
    {
        try
        {
            var scope = Scope.Resource;
            var next = State.ResourceNext;
            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);
                }
            }
        }
        catch (Exception exception)
        {
            _resourceExecutedContext = new ResourceExecutedContextSealed(_resourceExecutingContext!, _filters)
            {
                ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception),
            };
        }
 
        Debug.Assert(_resourceExecutedContext != null);
        return Task.CompletedTask;
 
        static async Task Awaited(ResourceInvoker 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._resourceExecutedContext = new ResourceExecutedContextSealed(invoker._resourceExecutingContext!, invoker._filters)
                {
                    ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception),
                };
            }
 
            Debug.Assert(invoker._resourceExecutedContext != null);
        }
    }
 
    private Task InvokeNextExceptionFilterAsync()
    {
        try
        {
            var next = State.ExceptionNext;
            var state = (object?)null;
            var scope = Scope.Exception;
            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(ResourceInvoker 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._exceptionContext = new ExceptionContextSealed(invoker._actionContext, invoker._filters)
                {
                    ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception),
                };
            }
        }
    }
 
    private Task InvokeAlwaysRunResultFilters()
    {
        try
        {
            var next = State.ResultBegin;
            var scope = Scope.Invoker;
            var state = (object?)null;
            var isCompleted = false;
 
            while (!isCompleted)
            {
                var lastTask = ResultNext<IAlwaysRunResultFilter, IAsyncAlwaysRunResultFilter>(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(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object? state, bool isCompleted)
        {
            await lastTask;
 
            while (!isCompleted)
            {
                await invoker.ResultNext<IAlwaysRunResultFilter, IAsyncAlwaysRunResultFilter>(ref next, ref scope, ref state, ref isCompleted);
            }
        }
    }
 
    private Task InvokeResultFilters()
    {
        try
        {
            var next = State.ResultBegin;
            var scope = Scope.Invoker;
            var state = (object?)null;
            var isCompleted = false;
 
            while (!isCompleted)
            {
                var lastTask = ResultNext<IResultFilter, IAsyncResultFilter>(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(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object? state, bool isCompleted)
        {
            await lastTask;
 
            while (!isCompleted)
            {
                await invoker.ResultNext<IResultFilter, IAsyncResultFilter>(ref next, ref scope, ref state, ref isCompleted);
            }
        }
    }
 
    private Task ResultNext<TFilter, TFilterAsync>(ref State next, ref Scope scope, ref object? state, ref bool isCompleted)
        where TFilter : class, IResultFilter
        where TFilterAsync : class, IAsyncResultFilter
    {
        var resultFilterKind = typeof(TFilter) == typeof(IAlwaysRunResultFilter) ?
            FilterTypeConstants.AlwaysRunResultFilter :
            FilterTypeConstants.ResultFilter;
 
        switch (next)
        {
            case State.ResultBegin:
                {
                    _cursor.Reset();
                    goto case State.ResultNext;
                }
 
            case State.ResultNext:
                {
                    var current = _cursor.GetNextFilter<TFilter, TFilterAsync>();
                    if (current.FilterAsync != null)
                    {
                        if (_resultExecutingContext == null)
                        {
                            _resultExecutingContext = new ResultExecutingContextSealed(_actionContext, _filters, _result!, _instance!);
                        }
 
                        state = current.FilterAsync;
                        goto case State.ResultAsyncBegin;
                    }
                    else if (current.Filter != null)
                    {
                        if (_resultExecutingContext == null)
                        {
                            _resultExecutingContext = new ResultExecutingContextSealed(_actionContext, _filters, _result!, _instance!);
                        }
 
                        state = current.Filter;
                        goto case State.ResultSyncBegin;
                    }
                    else
                    {
                        goto case State.ResultInside;
                    }
                }
 
            case State.ResultAsyncBegin:
                {
                    Debug.Assert(state != null);
                    Debug.Assert(_resultExecutingContext != null);
 
                    var filter = (TFilterAsync)state;
                    var resultExecutingContext = _resultExecutingContext;
 
                    _diagnosticListener.BeforeOnResultExecution(resultExecutingContext, filter);
                    _logger.BeforeExecutingMethodOnFilter(
                        resultFilterKind,
                        nameof(IAsyncResultFilter.OnResultExecutionAsync),
                        filter);
 
                    var task = filter.OnResultExecutionAsync(resultExecutingContext, InvokeNextResultFilterAwaitedAsync<TFilter, TFilterAsync>);
                    if (!task.IsCompletedSuccessfully)
                    {
                        next = State.ResultAsyncEnd;
                        return task;
                    }
 
                    goto case State.ResultAsyncEnd;
                }
 
            case State.ResultAsyncEnd:
                {
                    Debug.Assert(state != null);
                    Debug.Assert(_resultExecutingContext != null);
 
                    var filter = (TFilterAsync)state;
                    var resultExecutingContext = _resultExecutingContext;
                    var resultExecutedContext = _resultExecutedContext;
 
                    if (resultExecutedContext == null || resultExecutingContext.Cancel)
                    {
                        // Short-circuited by not calling next || Short-circuited by setting Cancel == true
                        _logger.ResultFilterShortCircuited(filter);
 
                        _resultExecutedContext = new ResultExecutedContextSealed(
                            _actionContext,
                            _filters,
                            resultExecutingContext.Result,
                            _instance!)
                        {
                            Canceled = true,
                        };
                    }
 
                    _diagnosticListener.AfterOnResultExecution(_resultExecutedContext!, filter);
                    _logger.AfterExecutingMethodOnFilter(
                        resultFilterKind,
                        nameof(IAsyncResultFilter.OnResultExecutionAsync),
                        filter);
 
                    goto case State.ResultEnd;
                }
 
            case State.ResultSyncBegin:
                {
                    Debug.Assert(state != null);
                    Debug.Assert(_resultExecutingContext != null);
 
                    var filter = (TFilter)state;
                    var resultExecutingContext = _resultExecutingContext;
 
                    _diagnosticListener.BeforeOnResultExecuting(resultExecutingContext, filter);
                    _logger.BeforeExecutingMethodOnFilter(
                        resultFilterKind,
                        nameof(IResultFilter.OnResultExecuting),
                        filter);
 
                    filter.OnResultExecuting(resultExecutingContext);
 
                    _diagnosticListener.AfterOnResultExecuting(resultExecutingContext, filter);
                    _logger.AfterExecutingMethodOnFilter(
                        resultFilterKind,
                        nameof(IResultFilter.OnResultExecuting),
                        filter);
 
                    if (_resultExecutingContext.Cancel)
                    {
                        // Short-circuited by setting Cancel == true
                        _logger.ResultFilterShortCircuited(filter);
 
                        _resultExecutedContext = new ResultExecutedContextSealed(
                            resultExecutingContext,
                            _filters,
                            resultExecutingContext.Result,
                            _instance!)
                        {
                            Canceled = true,
                        };
 
                        goto case State.ResultEnd;
                    }
 
                    var task = InvokeNextResultFilterAsync<TFilter, TFilterAsync>();
                    if (!task.IsCompletedSuccessfully)
                    {
                        next = State.ResultSyncEnd;
                        return task;
                    }
 
                    goto case State.ResultSyncEnd;
                }
 
            case State.ResultSyncEnd:
                {
                    Debug.Assert(state != null);
                    Debug.Assert(_resultExecutingContext != null);
                    Debug.Assert(_resultExecutedContext != null);
 
                    var filter = (TFilter)state;
                    var resultExecutedContext = _resultExecutedContext;
 
                    _diagnosticListener.BeforeOnResultExecuted(resultExecutedContext, filter);
                    _logger.BeforeExecutingMethodOnFilter(
                        resultFilterKind,
                        nameof(IResultFilter.OnResultExecuted),
                        filter);
 
                    filter.OnResultExecuted(resultExecutedContext);
 
                    _diagnosticListener.AfterOnResultExecuted(resultExecutedContext, filter);
                    _logger.AfterExecutingMethodOnFilter(
                        resultFilterKind,
                        nameof(IResultFilter.OnResultExecuted),
                        filter);
 
                    goto case State.ResultEnd;
                }
 
            case State.ResultInside:
                {
                    // If we executed result filters then we need to grab the result from there.
                    if (_resultExecutingContext != null)
                    {
                        _result = _resultExecutingContext.Result;
                    }
 
                    if (_result == null)
                    {
                        // The empty result is always flowed back as the 'executed' result if we don't have one.
                        _result = new EmptyResult();
                    }
 
                    var task = InvokeResultAsync(_result);
                    if (!task.IsCompletedSuccessfully)
                    {
                        next = State.ResultEnd;
                        return task;
                    }
 
                    goto case State.ResultEnd;
                }
 
            case State.ResultEnd:
                {
                    var result = _result;
                    isCompleted = true;
 
                    if (scope == Scope.Result)
                    {
                        if (_resultExecutedContext == null)
                        {
                            _resultExecutedContext = new ResultExecutedContextSealed(_actionContext, _filters, result!, _instance!);
                        }
 
                        return Task.CompletedTask;
                    }
 
                    Rethrow(_resultExecutedContext!);
                    return Task.CompletedTask;
                }
 
            default:
                throw new InvalidOperationException(); // Unreachable.
        }
    }
 
    private Task InvokeNextResultFilterAsync<TFilter, TFilterAsync>()
        where TFilter : class, IResultFilter
        where TFilterAsync : class, IAsyncResultFilter
    {
        try
        {
            var next = State.ResultNext;
            var state = (object?)null;
            var scope = Scope.Result;
            var isCompleted = false;
            while (!isCompleted)
            {
                var lastTask = ResultNext<TFilter, TFilterAsync>(ref next, ref scope, ref state, ref isCompleted);
                if (!lastTask.IsCompletedSuccessfully)
                {
                    return Awaited(this, lastTask, next, scope, state, isCompleted);
                }
            }
        }
        catch (Exception exception)
        {
            _resultExecutedContext = new ResultExecutedContextSealed(_actionContext, _filters, _result!, _instance!)
            {
                ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception),
            };
        }
 
        Debug.Assert(_resultExecutedContext != null);
 
        return Task.CompletedTask;
 
        static async Task Awaited(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object? state, bool isCompleted)
        {
            try
            {
                await lastTask;
 
                while (!isCompleted)
                {
                    await invoker.ResultNext<TFilter, TFilterAsync>(ref next, ref scope, ref state, ref isCompleted);
                }
            }
            catch (Exception exception)
            {
                invoker._resultExecutedContext = new ResultExecutedContextSealed(invoker._actionContext, invoker._filters, invoker._result!, invoker._instance!)
                {
                    ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception),
                };
            }
 
            Debug.Assert(invoker._resultExecutedContext != null);
        }
    }
 
    private Task<ResultExecutedContext> InvokeNextResultFilterAwaitedAsync<TFilter, TFilterAsync>()
        where TFilter : class, IResultFilter
        where TFilterAsync : class, IAsyncResultFilter
    {
        Debug.Assert(_resultExecutingContext != null);
        if (_resultExecutingContext.Cancel)
        {
            // If we get here, it means that an async filter set cancel == true AND called next().
            // This is forbidden.
            return Throw();
        }
 
        var task = InvokeNextResultFilterAsync<TFilter, TFilterAsync>();
        if (!task.IsCompletedSuccessfully)
        {
            return Awaited(this, task);
        }
 
        Debug.Assert(_resultExecutedContext != null);
        return Task.FromResult<ResultExecutedContext>(_resultExecutedContext);
 
        static async Task<ResultExecutedContext> Awaited(ResourceInvoker invoker, Task task)
        {
            await task;
 
            Debug.Assert(invoker._resultExecutedContext != null);
            return invoker._resultExecutedContext;
        }
#pragma warning disable CS1998
        static async Task<ResultExecutedContext> Throw()
        {
            var message = Resources.FormatAsyncResultFilter_InvalidShortCircuit(
                typeof(IAsyncResultFilter).Name,
                nameof(ResultExecutingContext.Cancel),
                typeof(ResultExecutingContext).Name,
                typeof(ResultExecutionDelegate).Name);
 
            throw new InvalidOperationException(message);
        }
#pragma warning restore CS1998
    }
 
    private static void Rethrow(ResourceExecutedContextSealed context)
    {
        if (context == null)
        {
            return;
        }
 
        if (context.ExceptionHandled)
        {
            return;
        }
 
        context.ExceptionDispatchInfo?.Throw();
 
        if (context.Exception != null)
        {
            throw context.Exception;
        }
    }
 
    private static void Rethrow(ExceptionContextSealed context)
    {
        if (context == null)
        {
            return;
        }
 
        if (context.ExceptionHandled)
        {
            return;
        }
 
        context.ExceptionDispatchInfo?.Throw();
 
        if (context.Exception != null)
        {
            throw context.Exception;
        }
    }
 
    private static void Rethrow(ResultExecutedContextSealed context)
    {
        if (context == null)
        {
            return;
        }
 
        if (context.ExceptionHandled)
        {
            return;
        }
 
        context.ExceptionDispatchInfo?.Throw();
 
        if (context.Exception != null)
        {
            throw context.Exception;
        }
    }
 
    private enum Scope
    {
        Invoker,
        Resource,
        Exception,
        Result,
    }
 
    private enum State
    {
        InvokeBegin,
        AuthorizationBegin,
        AuthorizationNext,
        AuthorizationAsyncBegin,
        AuthorizationAsyncEnd,
        AuthorizationSync,
        AuthorizationShortCircuit,
        AuthorizationEnd,
        ResourceBegin,
        ResourceNext,
        ResourceAsyncBegin,
        ResourceAsyncEnd,
        ResourceSyncBegin,
        ResourceSyncEnd,
        ResourceShortCircuit,
        ResourceInside,
        ResourceInsideEnd,
        ResourceEnd,
        ExceptionBegin,
        ExceptionNext,
        ExceptionAsyncBegin,
        ExceptionAsyncResume,
        ExceptionAsyncEnd,
        ExceptionSyncBegin,
        ExceptionSyncEnd,
        ExceptionInside,
        ExceptionHandled,
        ExceptionEnd,
        ActionBegin,
        ActionEnd,
        ResultBegin,
        ResultNext,
        ResultAsyncBegin,
        ResultAsyncEnd,
        ResultSyncBegin,
        ResultSyncEnd,
        ResultInside,
        ResultEnd,
        InvokeEnd,
    }
 
    private static class FilterTypeConstants
    {
        public const string AuthorizationFilter = "Authorization Filter";
        public const string ResourceFilter = "Resource Filter";
        public const string ActionFilter = "Action Filter";
        public const string ExceptionFilter = "Exception Filter";
        public const string ResultFilter = "Result Filter";
        public const string AlwaysRunResultFilter = "Always Run Result Filter";
    }
 
    private sealed class ResultExecutedContextSealed : ResultExecutedContext
    {
        public ResultExecutedContextSealed(
            ActionContext actionContext,
            IList<IFilterMetadata> filters,
            IActionResult result,
            object controller)
        : base(actionContext, filters, result, controller) { }
    }
 
    private sealed class ResultExecutingContextSealed : ResultExecutingContext
    {
        public ResultExecutingContextSealed(
            ActionContext actionContext,
            IList<IFilterMetadata> filters,
            IActionResult result,
            object controller)
            : base(actionContext, filters, result, controller)
        { }
    }
 
    private sealed class ExceptionContextSealed : ExceptionContext
    {
        public ExceptionContextSealed(ActionContext actionContext, IList<IFilterMetadata> filters) : base(actionContext, filters) { }
    }
    private sealed class ResourceExecutedContextSealed : ResourceExecutedContext
    {
        public ResourceExecutedContextSealed(ActionContext actionContext, IList<IFilterMetadata> filters) : base(actionContext, filters) { }
    }
    private sealed class ResourceExecutingContextSealed : ResourceExecutingContext
    {
        public ResourceExecutingContextSealed(ActionContext actionContext, IList<IFilterMetadata> filters, IList<IValueProviderFactory> valueProviderFactories) : base(actionContext, filters, valueProviderFactories) { }
    }
    private sealed class AuthorizationFilterContextSealed : AuthorizationFilterContext
    {
        public AuthorizationFilterContextSealed(ActionContext actionContext, IList<IFilterMetadata> filters) : base(actionContext, filters) { }
    }
}