Introduce a base type for executing auth and resource filters.
This commit is contained in:
parent
2b8233932a
commit
78492b39d8
|
|
@ -4,42 +4,27 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
#if NETSTANDARD1_6
|
|
||||||
using System.Reflection;
|
|
||||||
#endif
|
|
||||||
using System.Runtime.ExceptionServices;
|
using System.Runtime.ExceptionServices;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||||
using Microsoft.AspNetCore.Mvc.Core;
|
using Microsoft.AspNetCore.Mvc.Core;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Core.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.Filters;
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
{
|
{
|
||||||
public class ControllerActionInvoker : IActionInvoker
|
public class ControllerActionInvoker : ResourceInvoker, IActionInvoker
|
||||||
{
|
{
|
||||||
private readonly IControllerFactory _controllerFactory;
|
private readonly IControllerFactory _controllerFactory;
|
||||||
private readonly IControllerArgumentBinder _controllerArgumentBinder;
|
private readonly IControllerArgumentBinder _controllerArgumentBinder;
|
||||||
private readonly DiagnosticSource _diagnosticSource;
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
|
|
||||||
private readonly ControllerContext _controllerContext;
|
private readonly ControllerContext _controllerContext;
|
||||||
private readonly IFilterMetadata[] _filters;
|
|
||||||
private readonly ObjectMethodExecutor _executor;
|
private readonly ObjectMethodExecutor _executor;
|
||||||
|
|
||||||
// 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/
|
|
||||||
private FilterCursor _cursor;
|
|
||||||
private object _controller;
|
private object _controller;
|
||||||
private Dictionary<string, object> _arguments;
|
private Dictionary<string, object> _arguments;
|
||||||
private IActionResult _result;
|
|
||||||
|
|
||||||
private AuthorizationFilterContext _authorizationContext;
|
|
||||||
|
|
||||||
private ResourceExecutingContext _resourceExecutingContext;
|
|
||||||
private ResourceExecutedContext _resourceExecutedContext;
|
|
||||||
|
|
||||||
private ExceptionContext _exceptionContext;
|
private ExceptionContext _exceptionContext;
|
||||||
|
|
||||||
|
|
@ -50,19 +35,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
private ResultExecutedContext _resultExecutedContext;
|
private ResultExecutedContext _resultExecutedContext;
|
||||||
|
|
||||||
public ControllerActionInvoker(
|
public ControllerActionInvoker(
|
||||||
ControllerActionInvokerCache cache,
|
|
||||||
IControllerFactory controllerFactory,
|
IControllerFactory controllerFactory,
|
||||||
IControllerArgumentBinder controllerArgumentBinder,
|
IControllerArgumentBinder controllerArgumentBinder,
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
DiagnosticSource diagnosticSource,
|
DiagnosticSource diagnosticSource,
|
||||||
ActionContext actionContext,
|
ControllerContext controllerContext,
|
||||||
IReadOnlyList<IValueProviderFactory> valueProviderFactories,
|
IFilterMetadata[] filters,
|
||||||
int maxModelValidationErrors)
|
ObjectMethodExecutor objectMethodExecutor)
|
||||||
|
: base(diagnosticSource, logger, controllerContext, filters, controllerContext.ValueProviderFactories)
|
||||||
{
|
{
|
||||||
if (cache == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(cache));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (controllerFactory == null)
|
if (controllerFactory == null)
|
||||||
{
|
{
|
||||||
|
|
@ -74,41 +55,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
throw new ArgumentNullException(nameof(controllerArgumentBinder));
|
throw new ArgumentNullException(nameof(controllerArgumentBinder));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (logger == null)
|
if (objectMethodExecutor == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(logger));
|
throw new ArgumentNullException(nameof(objectMethodExecutor));
|
||||||
}
|
|
||||||
|
|
||||||
if (diagnosticSource == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(diagnosticSource));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actionContext == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(actionContext));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (valueProviderFactories == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(valueProviderFactories));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_controllerFactory = controllerFactory;
|
_controllerFactory = controllerFactory;
|
||||||
_controllerArgumentBinder = controllerArgumentBinder;
|
_controllerArgumentBinder = controllerArgumentBinder;
|
||||||
_logger = logger;
|
_controllerContext = controllerContext;
|
||||||
_diagnosticSource = diagnosticSource;
|
_executor = objectMethodExecutor;
|
||||||
|
|
||||||
_controllerContext = new ControllerContext(actionContext);
|
|
||||||
_controllerContext.ModelState.MaxAllowedErrors = maxModelValidationErrors;
|
|
||||||
|
|
||||||
// PERF: These are rarely going to be changed, so let's go copy-on-write.
|
|
||||||
_controllerContext.ValueProviderFactories = new CopyOnWriteList<IValueProviderFactory>(valueProviderFactories);
|
|
||||||
|
|
||||||
var cacheEntry = cache.GetState(_controllerContext);
|
|
||||||
_filters = cacheEntry.Filters;
|
|
||||||
_executor = cacheEntry.ActionMethodExecutor;
|
|
||||||
_cursor = new FilterCursor(_filters);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual async Task InvokeAsync()
|
public virtual async Task InvokeAsync()
|
||||||
|
|
@ -126,43 +81,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
var startTimestamp = _logger.IsEnabled(LogLevel.Information) ? Stopwatch.GetTimestamp() : 0;
|
var startTimestamp = _logger.IsEnabled(LogLevel.Information) ? Stopwatch.GetTimestamp() : 0;
|
||||||
|
|
||||||
// The invoker is implemented using a 'Taskerator' or perhaps an 'Asyncerator' (both terms are correct
|
|
||||||
// and in common usage). This method is the main 'driver' loop and will call into the `Next` method
|
|
||||||
// (`await`ing the result) until a terminal state is reached.
|
|
||||||
//
|
|
||||||
// The `Next` method walks through the state transitions of the invoker and returns a `Task` when there's
|
|
||||||
// actual async work that we need to await. As an optimization that Next method won't return a `Task`
|
|
||||||
// that completes synchronously.
|
|
||||||
//
|
|
||||||
// Additionally the `Next` funtion will be called recursively when we're 'inside' a filter invocation.
|
|
||||||
// Executing 'inside' a filter requires an async method call within a `try`/`catch` for error handling, so
|
|
||||||
// we have to recurse. Each 'frame' calls into `Next` with a value of `Scope` that communicates what kind
|
|
||||||
// of 'frame' is executing. This has an effect on the state machine transitions as well as what kinds of
|
|
||||||
// contexts need to be constructed to communicate the result of execution of the 'frame'.
|
|
||||||
|
|
||||||
// When returning, the `Next` method will set `next` to the state to goto on the subsequent invocation.
|
|
||||||
// This is similar to `Task.ContinueWith`, but since we have a fixed number of states we can avoid
|
|
||||||
// the overhead of actually using `Task.ContinueWith`.
|
|
||||||
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
|
try
|
||||||
{
|
{
|
||||||
while (!isCompleted)
|
await InvokeFilterPipelineAsync();
|
||||||
{
|
|
||||||
await Next(ref next, ref scope, ref state, ref isCompleted);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
@ -192,309 +114,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
switch (next)
|
switch (next)
|
||||||
{
|
{
|
||||||
case State.InvokeBegin:
|
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 AuthorizationFilterContext(_controllerContext, _filters);
|
|
||||||
}
|
|
||||||
|
|
||||||
state = current.FilterAsync;
|
|
||||||
goto case State.AuthorizationAsyncBegin;
|
|
||||||
}
|
|
||||||
else if (current.Filter != null)
|
|
||||||
{
|
|
||||||
if (_authorizationContext == null)
|
|
||||||
{
|
|
||||||
_authorizationContext = new AuthorizationFilterContext(_controllerContext, _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;
|
|
||||||
|
|
||||||
_diagnosticSource.BeforeOnAuthorizationAsync(authorizationContext, filter);
|
|
||||||
|
|
||||||
var task = filter.OnAuthorizationAsync(authorizationContext);
|
|
||||||
if (task.Status != TaskStatus.RanToCompletion)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
|
|
||||||
_diagnosticSource.AfterOnAuthorizationAsync(authorizationContext, 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;
|
|
||||||
|
|
||||||
_diagnosticSource.BeforeOnAuthorization(authorizationContext, filter);
|
|
||||||
|
|
||||||
filter.OnAuthorization(authorizationContext);
|
|
||||||
|
|
||||||
_diagnosticSource.AfterOnAuthorization(authorizationContext, filter);
|
|
||||||
|
|
||||||
if (authorizationContext.Result != null)
|
|
||||||
{
|
|
||||||
goto case State.AuthorizationShortCircuit;
|
|
||||||
}
|
|
||||||
|
|
||||||
goto case State.AuthorizationNext;
|
|
||||||
}
|
|
||||||
|
|
||||||
case State.AuthorizationShortCircuit:
|
|
||||||
{
|
|
||||||
Debug.Assert(state != null);
|
|
||||||
Debug.Assert(_authorizationContext != null);
|
|
||||||
|
|
||||||
_logger.AuthorizationFailure((IFilterMetadata)state);
|
|
||||||
|
|
||||||
// If an authorization filter short circuits, the result is the last thing we execute
|
|
||||||
// so just return that task instead of calling back into the state machine.
|
|
||||||
isCompleted = true;
|
|
||||||
return InvokeResultAsync(_authorizationContext.Result);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 ResourceExecutingContext(
|
|
||||||
_controllerContext,
|
|
||||||
_filters,
|
|
||||||
_controllerContext.ValueProviderFactories);
|
|
||||||
}
|
|
||||||
|
|
||||||
state = current.FilterAsync;
|
|
||||||
goto case State.ResourceAsyncBegin;
|
|
||||||
}
|
|
||||||
else if (current.Filter != null)
|
|
||||||
{
|
|
||||||
if (_resourceExecutingContext == null)
|
|
||||||
{
|
|
||||||
_resourceExecutingContext = new ResourceExecutingContext(
|
|
||||||
_controllerContext,
|
|
||||||
_filters,
|
|
||||||
_controllerContext.ValueProviderFactories);
|
|
||||||
}
|
|
||||||
|
|
||||||
state = current.Filter;
|
|
||||||
goto case State.ResourceSyncBegin;
|
|
||||||
}
|
|
||||||
else if (scope == Scope.Resource)
|
|
||||||
{
|
|
||||||
// All resource filters are currently on the stack - now execute the 'inside'.
|
|
||||||
Debug.Assert(_resourceExecutingContext != null);
|
|
||||||
goto case State.ResourceInside;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// There are no resource filters - so jump right to 'inside'.
|
|
||||||
Debug.Assert(scope == Scope.Invoker);
|
|
||||||
goto case State.ExceptionBegin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case State.ResourceAsyncBegin:
|
|
||||||
{
|
|
||||||
Debug.Assert(state != null);
|
|
||||||
Debug.Assert(_resourceExecutingContext != null);
|
|
||||||
|
|
||||||
var filter = (IAsyncResourceFilter)state;
|
|
||||||
var resourceExecutingContext = _resourceExecutingContext;
|
|
||||||
|
|
||||||
_diagnosticSource.BeforeOnResourceExecution(resourceExecutingContext, filter);
|
|
||||||
|
|
||||||
var task = filter.OnResourceExecutionAsync(resourceExecutingContext, InvokeNextResourceFilterAwaitedAsync);
|
|
||||||
if (task.Status != TaskStatus.RanToCompletion)
|
|
||||||
{
|
|
||||||
next = State.ResourceAsyncEnd;
|
|
||||||
return task;
|
|
||||||
}
|
|
||||||
|
|
||||||
goto case State.ResourceAsyncEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
case State.ResourceAsyncEnd:
|
|
||||||
{
|
|
||||||
Debug.Assert(state != null);
|
|
||||||
Debug.Assert(_resourceExecutingContext != null);
|
|
||||||
|
|
||||||
var filter = (IAsyncResourceFilter)state;
|
|
||||||
if (_resourceExecutedContext == null)
|
|
||||||
{
|
|
||||||
// If we get here then the filter didn't call 'next' indicating a short circuit.
|
|
||||||
_resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters)
|
|
||||||
{
|
|
||||||
Canceled = true,
|
|
||||||
Result = _resourceExecutingContext.Result,
|
|
||||||
};
|
|
||||||
|
|
||||||
_diagnosticSource.AfterOnResourceExecution(_resourceExecutedContext, filter);
|
|
||||||
|
|
||||||
// A filter could complete a Task without setting a result
|
|
||||||
if (_resourceExecutingContext.Result != null)
|
|
||||||
{
|
|
||||||
goto case State.ResourceShortCircuit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
goto case State.ResourceEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
case State.ResourceSyncBegin:
|
|
||||||
{
|
|
||||||
Debug.Assert(state != null);
|
|
||||||
Debug.Assert(_resourceExecutingContext != null);
|
|
||||||
|
|
||||||
var filter = (IResourceFilter)state;
|
|
||||||
var resourceExecutingContext = _resourceExecutingContext;
|
|
||||||
|
|
||||||
_diagnosticSource.BeforeOnResourceExecuting(resourceExecutingContext, filter);
|
|
||||||
|
|
||||||
filter.OnResourceExecuting(resourceExecutingContext);
|
|
||||||
|
|
||||||
_diagnosticSource.AfterOnResourceExecuting(resourceExecutingContext, filter);
|
|
||||||
|
|
||||||
if (resourceExecutingContext.Result != null)
|
|
||||||
{
|
|
||||||
_resourceExecutedContext = new ResourceExecutedContext(resourceExecutingContext, _filters)
|
|
||||||
{
|
|
||||||
Canceled = true,
|
|
||||||
Result = _resourceExecutingContext.Result,
|
|
||||||
};
|
|
||||||
|
|
||||||
goto case State.ResourceShortCircuit;
|
|
||||||
}
|
|
||||||
|
|
||||||
var task = InvokeNextResourceFilter();
|
|
||||||
if (task.Status != TaskStatus.RanToCompletion)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
|
|
||||||
_diagnosticSource.BeforeOnResourceExecuted(resourceExecutedContext, filter);
|
|
||||||
|
|
||||||
filter.OnResourceExecuted(resourceExecutedContext);
|
|
||||||
|
|
||||||
_diagnosticSource.AfterOnResourceExecuted(resourceExecutedContext, filter);
|
|
||||||
|
|
||||||
goto case State.ResourceEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
case State.ResourceShortCircuit:
|
|
||||||
{
|
|
||||||
Debug.Assert(state != null);
|
|
||||||
Debug.Assert(_resourceExecutingContext != null);
|
|
||||||
Debug.Assert(_resourceExecutedContext != null);
|
|
||||||
|
|
||||||
_logger.ResourceFilterShortCircuited((IFilterMetadata)state);
|
|
||||||
|
|
||||||
var task = InvokeResultAsync(_resourceExecutingContext.Result);
|
|
||||||
if (task.Status != TaskStatus.RanToCompletion)
|
|
||||||
{
|
|
||||||
next = State.ResourceEnd;
|
|
||||||
return task;
|
|
||||||
}
|
|
||||||
|
|
||||||
goto case State.ResourceEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
case State.ResourceInside:
|
|
||||||
{
|
{
|
||||||
goto case State.ExceptionBegin;
|
goto case State.ExceptionBegin;
|
||||||
}
|
}
|
||||||
|
|
||||||
case State.ResourceEnd:
|
|
||||||
{
|
|
||||||
if (scope == Scope.Resource)
|
|
||||||
{
|
|
||||||
isCompleted = true;
|
|
||||||
return TaskCache.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.Assert(scope == Scope.Invoker);
|
|
||||||
Rethrow(_resourceExecutedContext);
|
|
||||||
|
|
||||||
goto case State.InvokeEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
case State.ExceptionBegin:
|
case State.ExceptionBegin:
|
||||||
{
|
{
|
||||||
_cursor.Reset();
|
_cursor.Reset();
|
||||||
|
|
@ -522,7 +145,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// There are no exception filters - so jump right to 'inside'.
|
// There are no exception filters - so jump right to 'inside'.
|
||||||
Debug.Assert(scope == Scope.Invoker || scope == Scope.Resource);
|
Debug.Assert(scope == Scope.Invoker);
|
||||||
goto case State.ActionBegin;
|
goto case State.ActionBegin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -631,33 +254,20 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
Debug.Assert(state != null);
|
Debug.Assert(state != null);
|
||||||
Debug.Assert(_exceptionContext != null);
|
Debug.Assert(_exceptionContext != null);
|
||||||
|
|
||||||
Task task;
|
if (scope == Scope.Invoker)
|
||||||
if (scope == Scope.Resource)
|
|
||||||
{
|
{
|
||||||
Debug.Assert(_exceptionContext.Result != null);
|
Debug.Assert(_exceptionContext.Result != null);
|
||||||
_resourceExecutedContext = new ResourceExecutedContext(_controllerContext, _filters)
|
_result = _exceptionContext.Result;
|
||||||
{
|
|
||||||
Result = _exceptionContext.Result,
|
|
||||||
};
|
|
||||||
|
|
||||||
task = InvokeResultAsync(_exceptionContext.Result);
|
|
||||||
if (task.Status != TaskStatus.RanToCompletion)
|
|
||||||
{
|
|
||||||
next = State.ResourceEnd;
|
|
||||||
return task;
|
|
||||||
}
|
|
||||||
|
|
||||||
goto case State.ResourceEnd;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
task = InvokeResultAsync(_exceptionContext.Result);
|
var task = InvokeResultAsync(_exceptionContext.Result);
|
||||||
if (task.Status != TaskStatus.RanToCompletion)
|
if (task.Status != TaskStatus.RanToCompletion)
|
||||||
{
|
{
|
||||||
next = State.InvokeEnd;
|
next = State.InvokeEnd;
|
||||||
return task;
|
return task;
|
||||||
}
|
}
|
||||||
|
|
||||||
goto case State.ResourceEnd;
|
goto case State.InvokeEnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
case State.ExceptionEnd:
|
case State.ExceptionEnd:
|
||||||
|
|
@ -881,7 +491,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
return TaskCache.CompletedTask;
|
return TaskCache.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug.Assert(scope == Scope.Invoker || scope == Scope.Resource);
|
Debug.Assert(scope == Scope.Invoker);
|
||||||
goto case State.ResultBegin;
|
goto case State.ResultBegin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1068,16 +678,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
Rethrow(_resultExecutedContext);
|
Rethrow(_resultExecutedContext);
|
||||||
|
|
||||||
if (scope == Scope.Resource)
|
|
||||||
{
|
|
||||||
_resourceExecutedContext = new ResourceExecutedContext(_controllerContext, _filters)
|
|
||||||
{
|
|
||||||
Result = result,
|
|
||||||
};
|
|
||||||
|
|
||||||
goto case State.ResourceEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
goto case State.InvokeEnd;
|
goto case State.InvokeEnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1092,51 +692,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task InvokeNextResourceFilter()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var next = State.ResourceNext;
|
|
||||||
var state = (object)null;
|
|
||||||
var scope = Scope.Resource;
|
|
||||||
var isCompleted = false;
|
|
||||||
while (!isCompleted)
|
|
||||||
{
|
|
||||||
await Next(ref next, ref scope, ref state, ref isCompleted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
_resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters)
|
|
||||||
{
|
|
||||||
ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.Assert(_resourceExecutedContext != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async 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.
|
|
||||||
var message = Resources.FormatAsyncResourceFilter_InvalidShortCircuit(
|
|
||||||
typeof(IAsyncResourceFilter).Name,
|
|
||||||
nameof(ResourceExecutingContext.Result),
|
|
||||||
typeof(ResourceExecutingContext).Name,
|
|
||||||
typeof(ResourceExecutionDelegate).Name);
|
|
||||||
throw new InvalidOperationException(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
await InvokeNextResourceFilter();
|
|
||||||
|
|
||||||
Debug.Assert(_resourceExecutedContext != null);
|
|
||||||
return _resourceExecutedContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task InvokeNextExceptionFilterAsync()
|
private async Task InvokeNextExceptionFilterAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -1344,42 +899,42 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
return _resultExecutedContext;
|
return _resultExecutedContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task InvokeResultAsync(IActionResult result)
|
protected override async Task InvokeInnerFilterAsync()
|
||||||
{
|
{
|
||||||
var controllerContext = _controllerContext;
|
// The invoker is implemented using a 'Taskerator' or perhaps an 'Asyncerator' (both terms are correct
|
||||||
|
// and in common usage). This method is the main 'driver' loop and will call into the `Next` method
|
||||||
|
// (`await`ing the result) until a terminal state is reached.
|
||||||
|
//
|
||||||
|
// The `Next` method walks through the state transitions of the invoker and returns a `Task` when there's
|
||||||
|
// actual async work that we need to await. As an optimization that Next method won't return a `Task`
|
||||||
|
// that completes synchronously.
|
||||||
|
//
|
||||||
|
// Additionally the `Next` funtion will be called recursively when we're 'inside' a filter invocation.
|
||||||
|
// Executing 'inside' a filter requires an async method call within a `try`/`catch` for error handling, so
|
||||||
|
// we have to recurse. Each 'frame' calls into `Next` with a value of `Scope` that communicates what kind
|
||||||
|
// of 'frame' is executing. This has an effect on the state machine transitions as well as what kinds of
|
||||||
|
// contexts need to be constructed to communicate the result of execution of the 'frame'.
|
||||||
|
|
||||||
_diagnosticSource.BeforeActionResult(controllerContext, result);
|
// When returning, the `Next` method will set `next` to the state to goto on the subsequent invocation.
|
||||||
|
// This is similar to `Task.ContinueWith`, but since we have a fixed number of states we can avoid
|
||||||
|
// the overhead of actually using `Task.ContinueWith`.
|
||||||
|
var next = State.InvokeBegin;
|
||||||
|
|
||||||
try
|
// 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
|
||||||
await result.ExecuteResultAsync(controllerContext);
|
// of context or result other than throwing.
|
||||||
}
|
var scope = Scope.Invoker;
|
||||||
finally
|
|
||||||
{
|
|
||||||
_diagnosticSource.AfterActionResult(controllerContext, result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void Rethrow(ResourceExecutedContext context)
|
// 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.
|
||||||
if (context == null)
|
var state = (object)null;
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.ExceptionHandled)
|
// `isCompleted` will be set to true when we've reached a terminal state.
|
||||||
{
|
var isCompleted = false;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.ExceptionDispatchInfo != null)
|
while (!isCompleted)
|
||||||
{
|
{
|
||||||
context.ExceptionDispatchInfo.Throw();
|
await Next(ref next, ref scope, ref state, ref isCompleted);
|
||||||
}
|
|
||||||
|
|
||||||
if (context.Exception != null)
|
|
||||||
{
|
|
||||||
throw context.Exception;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1455,7 +1010,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
private enum Scope
|
private enum Scope
|
||||||
{
|
{
|
||||||
Invoker,
|
Invoker,
|
||||||
Resource,
|
|
||||||
Exception,
|
Exception,
|
||||||
Action,
|
Action,
|
||||||
Result,
|
Result,
|
||||||
|
|
@ -1464,22 +1018,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
private enum State
|
private enum State
|
||||||
{
|
{
|
||||||
InvokeBegin,
|
InvokeBegin,
|
||||||
AuthorizationBegin,
|
InvokeBeginOutside,
|
||||||
AuthorizationNext,
|
InvokeBeginInside,
|
||||||
AuthorizationAsyncBegin,
|
|
||||||
AuthorizationAsyncEnd,
|
|
||||||
AuthorizationSync,
|
|
||||||
AuthorizationShortCircuit,
|
|
||||||
AuthorizationEnd,
|
|
||||||
ResourceBegin,
|
|
||||||
ResourceNext,
|
|
||||||
ResourceAsyncBegin,
|
|
||||||
ResourceAsyncEnd,
|
|
||||||
ResourceSyncBegin,
|
|
||||||
ResourceSyncEnd,
|
|
||||||
ResourceShortCircuit,
|
|
||||||
ResourceInside,
|
|
||||||
ResourceEnd,
|
|
||||||
ExceptionBegin,
|
ExceptionBegin,
|
||||||
ExceptionNext,
|
ExceptionNext,
|
||||||
ExceptionAsyncBegin,
|
ExceptionAsyncBegin,
|
||||||
|
|
@ -1508,82 +1048,5 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ResultEnd,
|
ResultEnd,
|
||||||
InvokeEnd,
|
InvokeEnd,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A one-way cursor for filters.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This will iterate the filter collection once per-stage, and skip any filters that don't have
|
|
||||||
/// the one of interfaces that applies to the current stage.
|
|
||||||
///
|
|
||||||
/// Filters are always executed in the following order, but short circuiting plays a role.
|
|
||||||
///
|
|
||||||
/// Indentation reflects nesting.
|
|
||||||
///
|
|
||||||
/// 1. Exception Filters
|
|
||||||
/// 2. Authorization Filters
|
|
||||||
/// 3. Action Filters
|
|
||||||
/// Action
|
|
||||||
///
|
|
||||||
/// 4. Result Filters
|
|
||||||
/// Result
|
|
||||||
///
|
|
||||||
/// </remarks>
|
|
||||||
private struct FilterCursor
|
|
||||||
{
|
|
||||||
private int _index;
|
|
||||||
private readonly IFilterMetadata[] _filters;
|
|
||||||
|
|
||||||
public FilterCursor(int index, IFilterMetadata[] filters)
|
|
||||||
{
|
|
||||||
_index = index;
|
|
||||||
_filters = filters;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FilterCursor(IFilterMetadata[] filters)
|
|
||||||
{
|
|
||||||
_index = 0;
|
|
||||||
_filters = filters;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Reset()
|
|
||||||
{
|
|
||||||
_index = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FilterCursorItem<TFilter, TFilterAsync> GetNextFilter<TFilter, TFilterAsync>()
|
|
||||||
where TFilter : class
|
|
||||||
where TFilterAsync : class
|
|
||||||
{
|
|
||||||
while (_index < _filters.Length)
|
|
||||||
{
|
|
||||||
var filter = _filters[_index] as TFilter;
|
|
||||||
var filterAsync = _filters[_index] as TFilterAsync;
|
|
||||||
|
|
||||||
_index += 1;
|
|
||||||
|
|
||||||
if (filter != null || filterAsync != null)
|
|
||||||
{
|
|
||||||
return new FilterCursorItem<TFilter, TFilterAsync>(_index, filter, filterAsync);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return default(FilterCursorItem<TFilter, TFilterAsync>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct FilterCursorItem<TFilter, TFilterAsync>
|
|
||||||
{
|
|
||||||
public readonly int Index;
|
|
||||||
public readonly TFilter Filter;
|
|
||||||
public readonly TFilterAsync FilterAsync;
|
|
||||||
|
|
||||||
public FilterCursorItem(int index, TFilter filter, TFilterAsync filterAsync)
|
|
||||||
{
|
|
||||||
Index = index;
|
|
||||||
Filter = filter;
|
|
||||||
FilterAsync = filterAsync;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,15 +57,21 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
if (actionDescriptor != null)
|
if (actionDescriptor != null)
|
||||||
{
|
{
|
||||||
|
var controllerContext = new ControllerContext(context.ActionContext);
|
||||||
|
// PERF: These are rarely going to be changed, so let's go copy-on-write.
|
||||||
|
controllerContext.ValueProviderFactories = new CopyOnWriteList<IValueProviderFactory>(_valueProviderFactories);
|
||||||
|
controllerContext.ModelState.MaxAllowedErrors = _maxModelValidationErrors;
|
||||||
|
|
||||||
|
var cacheState = _controllerActionInvokerCache.GetState(controllerContext);
|
||||||
|
|
||||||
context.Result = new ControllerActionInvoker(
|
context.Result = new ControllerActionInvoker(
|
||||||
_controllerActionInvokerCache,
|
|
||||||
_controllerFactory,
|
_controllerFactory,
|
||||||
_argumentBinder,
|
_argumentBinder,
|
||||||
_logger,
|
_logger,
|
||||||
_diagnosticSource,
|
_diagnosticSource,
|
||||||
context.ActionContext,
|
controllerContext,
|
||||||
_valueProviderFactories,
|
cacheState.Filters,
|
||||||
_maxModelValidationErrors);
|
cacheState.ActionMethodExecutor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A one-way cursor for filters.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This will iterate the filter collection once per-stage, and skip any filters that don't have
|
||||||
|
/// the one of interfaces that applies to the current stage.
|
||||||
|
///
|
||||||
|
/// Filters are always executed in the following order, but short circuiting plays a role.
|
||||||
|
///
|
||||||
|
/// Indentation reflects nesting.
|
||||||
|
///
|
||||||
|
/// 1. Exception Filters
|
||||||
|
/// 2. Authorization Filters
|
||||||
|
/// 3. Action Filters
|
||||||
|
/// Action
|
||||||
|
///
|
||||||
|
/// 4. Result Filters
|
||||||
|
/// Result
|
||||||
|
///
|
||||||
|
/// </remarks>
|
||||||
|
public struct FilterCursor
|
||||||
|
{
|
||||||
|
private readonly IFilterMetadata[] _filters;
|
||||||
|
private int _index;
|
||||||
|
|
||||||
|
public FilterCursor(IFilterMetadata[] filters)
|
||||||
|
{
|
||||||
|
_filters = filters;
|
||||||
|
_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilterCursorItem<TFilter, TFilterAsync> GetNextFilter<TFilter, TFilterAsync>()
|
||||||
|
where TFilter : class
|
||||||
|
where TFilterAsync : class
|
||||||
|
{
|
||||||
|
while (_index < _filters.Length)
|
||||||
|
{
|
||||||
|
var filter = _filters[_index] as TFilter;
|
||||||
|
var filterAsync = _filters[_index] as TFilterAsync;
|
||||||
|
|
||||||
|
_index += 1;
|
||||||
|
|
||||||
|
if (filter != null || filterAsync != null)
|
||||||
|
{
|
||||||
|
return new FilterCursorItem<TFilter, TFilterAsync>(filter, filterAsync);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return default(FilterCursorItem<TFilter, TFilterAsync>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
{
|
||||||
|
public struct FilterCursorItem<TFilter, TFilterAsync>
|
||||||
|
{
|
||||||
|
public FilterCursorItem(TFilter filter, TFilterAsync filterAsync)
|
||||||
|
{
|
||||||
|
Filter = filter;
|
||||||
|
FilterAsync = filterAsync;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TFilter Filter { get; }
|
||||||
|
|
||||||
|
public TFilterAsync FilterAsync { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,552 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.ExceptionServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Core.Internal
|
||||||
|
{
|
||||||
|
public abstract class ResourceInvoker
|
||||||
|
{
|
||||||
|
protected readonly DiagnosticSource _diagnosticSource;
|
||||||
|
protected readonly ILogger _logger;
|
||||||
|
protected readonly ActionContext _actionContext;
|
||||||
|
protected readonly IFilterMetadata[] _filters;
|
||||||
|
protected readonly IList<IValueProviderFactory> _valueProviderFactories;
|
||||||
|
|
||||||
|
private AuthorizationFilterContext _authorizationContext;
|
||||||
|
private ResourceExecutingContext _resourceExecutingContext;
|
||||||
|
private ResourceExecutedContext _resourceExecutedContext;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
public ResourceInvoker(
|
||||||
|
DiagnosticSource diagnosticSource,
|
||||||
|
ILogger logger,
|
||||||
|
ActionContext actionContext,
|
||||||
|
IFilterMetadata[] filters,
|
||||||
|
IList<IValueProviderFactory> valueProviderFactories)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (diagnosticSource == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(diagnosticSource));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logger == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(logger));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actionContext == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(actionContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(filters));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valueProviderFactories == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(valueProviderFactories));
|
||||||
|
}
|
||||||
|
|
||||||
|
_diagnosticSource = diagnosticSource;
|
||||||
|
_logger = logger;
|
||||||
|
_actionContext = actionContext;
|
||||||
|
|
||||||
|
_filters = filters;
|
||||||
|
_valueProviderFactories = valueProviderFactories;
|
||||||
|
_cursor = new FilterCursor(filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async 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;
|
||||||
|
|
||||||
|
while (!isCompleted)
|
||||||
|
{
|
||||||
|
await Next(ref next, ref scope, ref state, ref isCompleted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Task InvokeInnerFilterAsync();
|
||||||
|
|
||||||
|
protected async Task InvokeResultAsync(IActionResult result)
|
||||||
|
{
|
||||||
|
var actionContext = _actionContext;
|
||||||
|
|
||||||
|
_diagnosticSource.BeforeActionResult(actionContext, result);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await result.ExecuteResultAsync(actionContext);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_diagnosticSource.AfterActionResult(actionContext, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
|
||||||
|
{
|
||||||
|
var diagnosticSource = _diagnosticSource;
|
||||||
|
var logger = _logger;
|
||||||
|
|
||||||
|
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 AuthorizationFilterContext(_actionContext, _filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
state = current.FilterAsync;
|
||||||
|
goto case State.AuthorizationAsyncBegin;
|
||||||
|
}
|
||||||
|
else if (current.Filter != null)
|
||||||
|
{
|
||||||
|
if (_authorizationContext == null)
|
||||||
|
{
|
||||||
|
_authorizationContext = new AuthorizationFilterContext(_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;
|
||||||
|
|
||||||
|
_diagnosticSource.BeforeOnAuthorizationAsync(authorizationContext, filter);
|
||||||
|
|
||||||
|
var task = filter.OnAuthorizationAsync(authorizationContext);
|
||||||
|
if (task.Status != TaskStatus.RanToCompletion)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
|
||||||
|
_diagnosticSource.AfterOnAuthorizationAsync(authorizationContext, 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;
|
||||||
|
|
||||||
|
_diagnosticSource.BeforeOnAuthorization(authorizationContext, filter);
|
||||||
|
|
||||||
|
filter.OnAuthorization(authorizationContext);
|
||||||
|
|
||||||
|
_diagnosticSource.AfterOnAuthorization(authorizationContext, filter);
|
||||||
|
|
||||||
|
if (authorizationContext.Result != null)
|
||||||
|
{
|
||||||
|
goto case State.AuthorizationShortCircuit;
|
||||||
|
}
|
||||||
|
|
||||||
|
goto case State.AuthorizationNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
case State.AuthorizationShortCircuit:
|
||||||
|
{
|
||||||
|
Debug.Assert(state != null);
|
||||||
|
Debug.Assert(_authorizationContext != null);
|
||||||
|
|
||||||
|
_logger.AuthorizationFailure((IFilterMetadata)state);
|
||||||
|
|
||||||
|
// If an authorization filter short circuits, the result is the last thing we execute
|
||||||
|
// so just return that task instead of calling back into the state machine.
|
||||||
|
isCompleted = true;
|
||||||
|
return InvokeResultAsync(_authorizationContext.Result);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ResourceExecutingContext(
|
||||||
|
_actionContext,
|
||||||
|
_filters,
|
||||||
|
_valueProviderFactories);
|
||||||
|
}
|
||||||
|
|
||||||
|
state = current.FilterAsync;
|
||||||
|
goto case State.ResourceAsyncBegin;
|
||||||
|
}
|
||||||
|
else if (current.Filter != null)
|
||||||
|
{
|
||||||
|
if (_resourceExecutingContext == null)
|
||||||
|
{
|
||||||
|
_resourceExecutingContext = new ResourceExecutingContext(
|
||||||
|
_actionContext,
|
||||||
|
_filters,
|
||||||
|
_valueProviderFactories);
|
||||||
|
}
|
||||||
|
|
||||||
|
state = current.Filter;
|
||||||
|
goto case State.ResourceSyncBegin;
|
||||||
|
}
|
||||||
|
else if (scope == Scope.Resource)
|
||||||
|
{
|
||||||
|
// All resource filters are currently on the stack - now execute the 'inside'.
|
||||||
|
Debug.Assert(_resourceExecutingContext != null);
|
||||||
|
goto case State.ResourceInside;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// There are no resource filters - so jump right to 'inside'.
|
||||||
|
Debug.Assert(scope == Scope.Invoker);
|
||||||
|
next = State.InvokeEnd;
|
||||||
|
return InvokeInnerFilterAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case State.ResourceAsyncBegin:
|
||||||
|
{
|
||||||
|
Debug.Assert(state != null);
|
||||||
|
Debug.Assert(_resourceExecutingContext != null);
|
||||||
|
|
||||||
|
var filter = (IAsyncResourceFilter)state;
|
||||||
|
var resourceExecutingContext = _resourceExecutingContext;
|
||||||
|
|
||||||
|
_diagnosticSource.BeforeOnResourceExecution(resourceExecutingContext, filter);
|
||||||
|
|
||||||
|
var task = filter.OnResourceExecutionAsync(resourceExecutingContext, InvokeNextResourceFilterAwaitedAsync);
|
||||||
|
if (task.Status != TaskStatus.RanToCompletion)
|
||||||
|
{
|
||||||
|
next = State.ResourceAsyncEnd;
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
goto case State.ResourceAsyncEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
case State.ResourceAsyncEnd:
|
||||||
|
{
|
||||||
|
Debug.Assert(state != null);
|
||||||
|
Debug.Assert(_resourceExecutingContext != null);
|
||||||
|
|
||||||
|
var filter = (IAsyncResourceFilter)state;
|
||||||
|
if (_resourceExecutedContext == null)
|
||||||
|
{
|
||||||
|
// If we get here then the filter didn't call 'next' indicating a short circuit.
|
||||||
|
_resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters)
|
||||||
|
{
|
||||||
|
Canceled = true,
|
||||||
|
Result = _resourceExecutingContext.Result,
|
||||||
|
};
|
||||||
|
|
||||||
|
_diagnosticSource.AfterOnResourceExecution(_resourceExecutedContext, filter);
|
||||||
|
|
||||||
|
// A filter could complete a Task without setting a result
|
||||||
|
if (_resourceExecutingContext.Result != null)
|
||||||
|
{
|
||||||
|
goto case State.ResourceShortCircuit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
goto case State.ResourceEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
case State.ResourceSyncBegin:
|
||||||
|
{
|
||||||
|
Debug.Assert(state != null);
|
||||||
|
Debug.Assert(_resourceExecutingContext != null);
|
||||||
|
|
||||||
|
var filter = (IResourceFilter)state;
|
||||||
|
var resourceExecutingContext = _resourceExecutingContext;
|
||||||
|
|
||||||
|
_diagnosticSource.BeforeOnResourceExecuting(resourceExecutingContext, filter);
|
||||||
|
|
||||||
|
filter.OnResourceExecuting(resourceExecutingContext);
|
||||||
|
|
||||||
|
_diagnosticSource.AfterOnResourceExecuting(resourceExecutingContext, filter);
|
||||||
|
|
||||||
|
if (resourceExecutingContext.Result != null)
|
||||||
|
{
|
||||||
|
_resourceExecutedContext = new ResourceExecutedContext(resourceExecutingContext, _filters)
|
||||||
|
{
|
||||||
|
Canceled = true,
|
||||||
|
Result = _resourceExecutingContext.Result,
|
||||||
|
};
|
||||||
|
|
||||||
|
goto case State.ResourceShortCircuit;
|
||||||
|
}
|
||||||
|
|
||||||
|
var task = InvokeNextResourceFilter();
|
||||||
|
if (task.Status != TaskStatus.RanToCompletion)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
|
||||||
|
_diagnosticSource.BeforeOnResourceExecuted(resourceExecutedContext, filter);
|
||||||
|
|
||||||
|
filter.OnResourceExecuted(resourceExecutedContext);
|
||||||
|
|
||||||
|
_diagnosticSource.AfterOnResourceExecuted(resourceExecutedContext, filter);
|
||||||
|
|
||||||
|
goto case State.ResourceEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
case State.ResourceShortCircuit:
|
||||||
|
{
|
||||||
|
Debug.Assert(state != null);
|
||||||
|
Debug.Assert(_resourceExecutingContext != null);
|
||||||
|
Debug.Assert(_resourceExecutedContext != null);
|
||||||
|
|
||||||
|
_logger.ResourceFilterShortCircuited((IFilterMetadata)state);
|
||||||
|
|
||||||
|
var task = InvokeResultAsync(_resourceExecutingContext.Result);
|
||||||
|
if (task.Status != TaskStatus.RanToCompletion)
|
||||||
|
{
|
||||||
|
next = State.ResourceEnd;
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
goto case State.ResourceEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
case State.ResourceInside:
|
||||||
|
{
|
||||||
|
next = State.ResourceOutside;
|
||||||
|
return InvokeInnerFilterAsync();
|
||||||
|
}
|
||||||
|
case State.ResourceOutside:
|
||||||
|
{
|
||||||
|
var result = _result;
|
||||||
|
|
||||||
|
if (scope == Scope.Resource)
|
||||||
|
{
|
||||||
|
_resourceExecutedContext = new ResourceExecutedContext(_actionContext, _filters)
|
||||||
|
{
|
||||||
|
Result = _result,
|
||||||
|
};
|
||||||
|
|
||||||
|
goto case State.ResourceEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
goto case State.InvokeEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
case State.ResourceEnd:
|
||||||
|
{
|
||||||
|
if (scope == Scope.Resource)
|
||||||
|
{
|
||||||
|
isCompleted = true;
|
||||||
|
return TaskCache.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Assert(scope == Scope.Invoker);
|
||||||
|
Rethrow(_resourceExecutedContext);
|
||||||
|
|
||||||
|
goto case State.InvokeEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
case State.InvokeEnd:
|
||||||
|
{
|
||||||
|
isCompleted = true;
|
||||||
|
return TaskCache.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async 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.
|
||||||
|
var message = Resources.FormatAsyncResourceFilter_InvalidShortCircuit(
|
||||||
|
typeof(IAsyncResourceFilter).Name,
|
||||||
|
nameof(ResourceExecutingContext.Result),
|
||||||
|
typeof(ResourceExecutingContext).Name,
|
||||||
|
typeof(ResourceExecutionDelegate).Name);
|
||||||
|
throw new InvalidOperationException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
await InvokeNextResourceFilter();
|
||||||
|
|
||||||
|
Debug.Assert(_resourceExecutedContext != null);
|
||||||
|
return _resourceExecutedContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task InvokeNextResourceFilter()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var scope = Scope.Resource;
|
||||||
|
var next = State.ResourceNext;
|
||||||
|
var state = (object)null;
|
||||||
|
var isCompleted = false;
|
||||||
|
while (!isCompleted)
|
||||||
|
{
|
||||||
|
await Next(ref next, ref scope, ref state, ref isCompleted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters)
|
||||||
|
{
|
||||||
|
ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Assert(_resourceExecutedContext != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Rethrow(ResourceExecutedContext context)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.ExceptionHandled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.ExceptionDispatchInfo != null)
|
||||||
|
{
|
||||||
|
context.ExceptionDispatchInfo.Throw();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.Exception != null)
|
||||||
|
{
|
||||||
|
throw context.Exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum Scope
|
||||||
|
{
|
||||||
|
Invoker,
|
||||||
|
Resource,
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum State
|
||||||
|
{
|
||||||
|
InvokeBegin,
|
||||||
|
AuthorizationBegin,
|
||||||
|
AuthorizationNext,
|
||||||
|
AuthorizationAsyncBegin,
|
||||||
|
AuthorizationAsyncEnd,
|
||||||
|
AuthorizationSync,
|
||||||
|
AuthorizationShortCircuit,
|
||||||
|
AuthorizationEnd,
|
||||||
|
ResourceBegin,
|
||||||
|
ResourceNext,
|
||||||
|
ResourceAsyncBegin,
|
||||||
|
ResourceAsyncEnd,
|
||||||
|
ResourceSyncBegin,
|
||||||
|
ResourceSyncEnd,
|
||||||
|
ResourceShortCircuit,
|
||||||
|
ResourceInside,
|
||||||
|
ResourceOutside,
|
||||||
|
ResourceEnd,
|
||||||
|
InvokeEnd,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2686,15 +2686,22 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
TestModelBinderFactory.CreateDefault(metadataProvider),
|
TestModelBinderFactory.CreateDefault(metadataProvider),
|
||||||
new DefaultObjectValidator(metadataProvider, new IModelValidatorProvider[0]));
|
new DefaultObjectValidator(metadataProvider, new IModelValidatorProvider[0]));
|
||||||
|
|
||||||
|
var controllerContext = new ControllerContext(actionContext)
|
||||||
|
{
|
||||||
|
ValueProviderFactories = new IValueProviderFactory[0]
|
||||||
|
};
|
||||||
|
controllerContext.ModelState.MaxAllowedErrors = 200;
|
||||||
|
|
||||||
var invoker = new ControllerActionInvoker(
|
var invoker = new ControllerActionInvoker(
|
||||||
CreateFilterCache(),
|
|
||||||
controllerFactory.Object,
|
controllerFactory.Object,
|
||||||
argumentBinder,
|
argumentBinder,
|
||||||
new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(),
|
new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(),
|
||||||
new DiagnosticListener("Microsoft.AspNetCore"),
|
new DiagnosticListener("Microsoft.AspNetCore"),
|
||||||
actionContext,
|
controllerContext,
|
||||||
new IValueProviderFactory[0],
|
new IFilterMetadata[0],
|
||||||
200);
|
ObjectMethodExecutor.Create(
|
||||||
|
actionDescriptor.MethodInfo,
|
||||||
|
actionDescriptor.ControllerTypeInfo));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await invoker.InvokeAsync();
|
await invoker.InvokeAsync();
|
||||||
|
|
@ -2996,24 +3003,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
routeData: routeData,
|
routeData: routeData,
|
||||||
actionDescriptor: actionDescriptor);
|
actionDescriptor: actionDescriptor);
|
||||||
|
|
||||||
var filterProvider = new Mock<IFilterProvider>(MockBehavior.Strict);
|
|
||||||
filterProvider
|
|
||||||
.Setup(fp => fp.OnProvidersExecuting(It.IsAny<FilterProviderContext>()))
|
|
||||||
.Callback<FilterProviderContext>(context =>
|
|
||||||
{
|
|
||||||
foreach (var filterMetadata in filters)
|
|
||||||
{
|
|
||||||
context.Results.Add(new FilterItem(new FilterDescriptor(filterMetadata, FilterScope.Action))
|
|
||||||
{
|
|
||||||
Filter = filterMetadata,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
filterProvider
|
|
||||||
.Setup(fp => fp.OnProvidersExecuted(It.IsAny<FilterProviderContext>()))
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
IControllerArgumentBinder argumentBinder = null;
|
IControllerArgumentBinder argumentBinder = null;
|
||||||
|
|
||||||
if (controllerArgumentBinder == null)
|
if (controllerArgumentBinder == null)
|
||||||
|
|
@ -3032,10 +3021,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
argumentBinder = controllerArgumentBinder;
|
argumentBinder = controllerArgumentBinder;
|
||||||
}
|
}
|
||||||
|
|
||||||
filterProvider
|
|
||||||
.SetupGet(fp => fp.Order)
|
|
||||||
.Returns(-1000);
|
|
||||||
|
|
||||||
if (valueProviderFactories == null)
|
if (valueProviderFactories == null)
|
||||||
{
|
{
|
||||||
valueProviderFactories = new List<IValueProviderFactory>();
|
valueProviderFactories = new List<IValueProviderFactory>();
|
||||||
|
|
@ -3053,7 +3038,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
var invoker = new TestControllerActionInvoker(
|
var invoker = new TestControllerActionInvoker(
|
||||||
new[] { filterProvider.Object },
|
filters,
|
||||||
new MockControllerFactory(controller ?? this),
|
new MockControllerFactory(controller ?? this),
|
||||||
argumentBinder,
|
argumentBinder,
|
||||||
logger,
|
logger,
|
||||||
|
|
@ -3299,18 +3284,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ControllerActionInvokerCache CreateFilterCache(IFilterProvider[] filterProviders = null)
|
|
||||||
{
|
|
||||||
var descriptorProvider = new ActionDescriptorCollectionProvider(
|
|
||||||
Enumerable.Empty<IActionDescriptorProvider>(),
|
|
||||||
Enumerable.Empty<IActionDescriptorChangeProvider>());
|
|
||||||
return new ControllerActionInvokerCache(descriptorProvider, filterProviders.AsEnumerable() ?? new List<IFilterProvider>());
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TestControllerActionInvoker : ControllerActionInvoker
|
private class TestControllerActionInvoker : ControllerActionInvoker
|
||||||
{
|
{
|
||||||
public TestControllerActionInvoker(
|
public TestControllerActionInvoker(
|
||||||
IFilterProvider[] filterProviders,
|
IFilterMetadata[] filters,
|
||||||
MockControllerFactory controllerFactory,
|
MockControllerFactory controllerFactory,
|
||||||
IControllerArgumentBinder argumentBinder,
|
IControllerArgumentBinder argumentBinder,
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
|
|
@ -3319,14 +3296,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
IReadOnlyList<IValueProviderFactory> valueProviderFactories,
|
IReadOnlyList<IValueProviderFactory> valueProviderFactories,
|
||||||
int maxAllowedErrorsInModelState)
|
int maxAllowedErrorsInModelState)
|
||||||
: base(
|
: base(
|
||||||
CreateFilterCache(filterProviders),
|
|
||||||
controllerFactory,
|
controllerFactory,
|
||||||
argumentBinder,
|
argumentBinder,
|
||||||
logger,
|
logger,
|
||||||
diagnosticSource,
|
diagnosticSource,
|
||||||
actionContext,
|
CreatControllerContext(actionContext, valueProviderFactories, maxAllowedErrorsInModelState),
|
||||||
valueProviderFactories,
|
filters,
|
||||||
maxAllowedErrorsInModelState)
|
CreateExecutor((ControllerActionDescriptor)actionContext.ActionDescriptor))
|
||||||
{
|
{
|
||||||
ControllerFactory = controllerFactory;
|
ControllerFactory = controllerFactory;
|
||||||
}
|
}
|
||||||
|
|
@ -3340,6 +3316,25 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
// Make sure that the controller was disposed in every test that creates ones.
|
// Make sure that the controller was disposed in every test that creates ones.
|
||||||
ControllerFactory.Verify();
|
ControllerFactory.Verify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ObjectMethodExecutor CreateExecutor(ControllerActionDescriptor actionDescriptor)
|
||||||
|
{
|
||||||
|
return ObjectMethodExecutor.Create(actionDescriptor.MethodInfo, actionDescriptor.ControllerTypeInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ControllerContext CreatControllerContext(
|
||||||
|
ActionContext actionContext,
|
||||||
|
IReadOnlyList<IValueProviderFactory> valueProviderFactories,
|
||||||
|
int maxAllowedErrorsInModelState)
|
||||||
|
{
|
||||||
|
var controllerContext = new ControllerContext(actionContext)
|
||||||
|
{
|
||||||
|
ValueProviderFactories = valueProviderFactories.ToList()
|
||||||
|
};
|
||||||
|
controllerContext.ModelState.MaxAllowedErrors = maxAllowedErrorsInModelState;
|
||||||
|
|
||||||
|
return controllerContext;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MockAuthorizationFilter : IAuthorizationFilter
|
private class MockAuthorizationFilter : IAuthorizationFilter
|
||||||
|
|
|
||||||
|
|
@ -276,33 +276,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
var actionContext = new ActionContext(httpContext, new RouteData(), actionDescriptor);
|
var actionContext = new ActionContext(httpContext, new RouteData(), actionDescriptor);
|
||||||
|
|
||||||
var filterProvider = new Mock<IFilterProvider>(MockBehavior.Strict);
|
|
||||||
filterProvider
|
|
||||||
.Setup(fp => fp.OnProvidersExecuting(It.IsAny<FilterProviderContext>()))
|
|
||||||
.Callback<FilterProviderContext>(context =>
|
|
||||||
{
|
|
||||||
foreach (var filterMetadata in filters)
|
|
||||||
{
|
|
||||||
context.Results.Add(new FilterItem(new FilterDescriptor(filterMetadata, FilterScope.Action))
|
|
||||||
{
|
|
||||||
Filter = filterMetadata,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
filterProvider
|
|
||||||
.Setup(fp => fp.OnProvidersExecuted(It.IsAny<FilterProviderContext>()))
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
filterProvider
|
|
||||||
.SetupGet(fp => fp.Order)
|
|
||||||
.Returns(-1000);
|
|
||||||
|
|
||||||
var diagnosticSource = new DiagnosticListener("Microsoft.AspNetCore");
|
var diagnosticSource = new DiagnosticListener("Microsoft.AspNetCore");
|
||||||
diagnosticSource.SubscribeWithAdapter(new TestDiagnosticListener());
|
diagnosticSource.SubscribeWithAdapter(new TestDiagnosticListener());
|
||||||
|
|
||||||
var invoker = new TestControllerActionInvoker(
|
var invoker = new TestControllerActionInvoker(
|
||||||
new[] { filterProvider.Object },
|
filters,
|
||||||
new MockControllerFactory(controller ?? this),
|
new MockControllerFactory(controller ?? this),
|
||||||
new TestControllerArgumentBinder(actionParameters: null),
|
new TestControllerArgumentBinder(actionParameters: null),
|
||||||
new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(),
|
new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(),
|
||||||
|
|
@ -412,7 +390,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
private class TestControllerActionInvoker : ControllerActionInvoker
|
private class TestControllerActionInvoker : ControllerActionInvoker
|
||||||
{
|
{
|
||||||
public TestControllerActionInvoker(
|
public TestControllerActionInvoker(
|
||||||
IFilterProvider[] filterProviders,
|
IFilterMetadata[] filters,
|
||||||
MockControllerFactory controllerFactory,
|
MockControllerFactory controllerFactory,
|
||||||
IControllerArgumentBinder argumentBinder,
|
IControllerArgumentBinder argumentBinder,
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
|
|
@ -421,14 +399,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
IReadOnlyList<IValueProviderFactory> valueProviderFactories,
|
IReadOnlyList<IValueProviderFactory> valueProviderFactories,
|
||||||
int maxAllowedErrorsInModelState)
|
int maxAllowedErrorsInModelState)
|
||||||
: base(
|
: base(
|
||||||
CreateFilterCache(filterProviders),
|
|
||||||
controllerFactory,
|
controllerFactory,
|
||||||
argumentBinder,
|
argumentBinder,
|
||||||
logger,
|
logger,
|
||||||
diagnosticSource,
|
diagnosticSource,
|
||||||
actionContext,
|
CreatControllerContext(actionContext, valueProviderFactories, maxAllowedErrorsInModelState),
|
||||||
valueProviderFactories,
|
filters,
|
||||||
maxAllowedErrorsInModelState)
|
CreateExecutor((ControllerActionDescriptor)actionContext.ActionDescriptor))
|
||||||
{
|
{
|
||||||
ControllerFactory = controllerFactory;
|
ControllerFactory = controllerFactory;
|
||||||
}
|
}
|
||||||
|
|
@ -442,6 +419,25 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
// Make sure that the controller was disposed in every test that creates ones.
|
// Make sure that the controller was disposed in every test that creates ones.
|
||||||
ControllerFactory.Verify();
|
ControllerFactory.Verify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ObjectMethodExecutor CreateExecutor(ControllerActionDescriptor actionDescriptor)
|
||||||
|
{
|
||||||
|
return ObjectMethodExecutor.Create(actionDescriptor.MethodInfo, actionDescriptor.ControllerTypeInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ControllerContext CreatControllerContext(
|
||||||
|
ActionContext actionContext,
|
||||||
|
IReadOnlyList<IValueProviderFactory> valueProviderFactories,
|
||||||
|
int maxAllowedErrorsInModelState)
|
||||||
|
{
|
||||||
|
var controllerContext = new ControllerContext(actionContext)
|
||||||
|
{
|
||||||
|
ValueProviderFactories = valueProviderFactories.ToList()
|
||||||
|
};
|
||||||
|
controllerContext.ModelState.MaxAllowedErrors = maxAllowedErrorsInModelState;
|
||||||
|
|
||||||
|
return controllerContext;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestControllerArgumentBinder : IControllerArgumentBinder
|
private class TestControllerArgumentBinder : IControllerArgumentBinder
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue