Add result filters to pages
This commit is contained in:
parent
84e007a2a7
commit
688e518991
|
|
@ -6,19 +6,16 @@ using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
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.Filters;
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
|
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
{
|
{
|
||||||
|
|
@ -34,7 +31,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
private Page _page;
|
private Page _page;
|
||||||
private object _model;
|
private object _model;
|
||||||
private ViewContext _viewContext;
|
private ViewContext _viewContext;
|
||||||
private ExceptionContext _exceptionContext;
|
|
||||||
|
|
||||||
public PageActionInvoker(
|
public PageActionInvoker(
|
||||||
IPageHandlerMethodSelector handlerMethodSelector,
|
IPageHandlerMethodSelector handlerMethodSelector,
|
||||||
|
|
@ -75,8 +71,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
protected override async Task InvokeInnerFilterAsync()
|
protected override async Task InvokeInnerFilterAsync()
|
||||||
{
|
{
|
||||||
var next = State.ResourceInnerBegin;
|
var next = State.PageBegin;
|
||||||
var scope = Scope.Resource;
|
var scope = Scope.Invoker;
|
||||||
var state = (object)null;
|
var state = (object)null;
|
||||||
var isCompleted = false;
|
var isCompleted = false;
|
||||||
|
|
||||||
|
|
@ -106,186 +102,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
|
||||||
switch (next)
|
switch (next)
|
||||||
{
|
{
|
||||||
case State.ResourceInnerBegin:
|
|
||||||
{
|
|
||||||
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 'inside'.
|
|
||||||
Debug.Assert(scope == Scope.Resource);
|
|
||||||
goto case State.PageBegin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case State.ExceptionAsyncBegin:
|
|
||||||
{
|
|
||||||
var task = InvokeNextExceptionFilterAsync();
|
|
||||||
if (task.Status != TaskStatus.RanToCompletion)
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
_diagnosticSource.BeforeOnExceptionAsync(exceptionContext, filter);
|
|
||||||
|
|
||||||
var task = filter.OnExceptionAsync(exceptionContext);
|
|
||||||
if (task.Status != TaskStatus.RanToCompletion)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
|
|
||||||
_diagnosticSource.AfterOnExceptionAsync(exceptionContext, filter);
|
|
||||||
|
|
||||||
if (exceptionContext.Exception == null || exceptionContext.ExceptionHandled)
|
|
||||||
{
|
|
||||||
_logger.ExceptionFilterShortCircuited(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
goto case State.ExceptionEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
case State.ExceptionSyncBegin:
|
|
||||||
{
|
|
||||||
var task = InvokeNextExceptionFilterAsync();
|
|
||||||
if (task.Status != TaskStatus.RanToCompletion)
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
_diagnosticSource.BeforeOnException(exceptionContext, filter);
|
|
||||||
|
|
||||||
filter.OnException(exceptionContext);
|
|
||||||
|
|
||||||
_diagnosticSource.AfterOnException(exceptionContext, filter);
|
|
||||||
|
|
||||||
if (exceptionContext.Exception == null || exceptionContext.ExceptionHandled)
|
|
||||||
{
|
|
||||||
_logger.ExceptionFilterShortCircuited(filter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
goto case State.ExceptionEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
case State.ExceptionInside:
|
|
||||||
{
|
|
||||||
goto case State.PageBegin;
|
|
||||||
}
|
|
||||||
|
|
||||||
case State.ExceptionShortCircuit:
|
|
||||||
{
|
|
||||||
Debug.Assert(state != null);
|
|
||||||
Debug.Assert(_exceptionContext != null);
|
|
||||||
|
|
||||||
if (scope == Scope.Resource)
|
|
||||||
{
|
|
||||||
Debug.Assert(_exceptionContext.Result != null);
|
|
||||||
_result = _exceptionContext.Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
var task = InvokeResultAsync(_exceptionContext.Result);
|
|
||||||
if (task.Status != TaskStatus.RanToCompletion)
|
|
||||||
{
|
|
||||||
next = State.ResourceInnerEnd;
|
|
||||||
return task;
|
|
||||||
}
|
|
||||||
|
|
||||||
goto case State.ResourceInnerEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
case State.ExceptionEnd:
|
|
||||||
{
|
|
||||||
var exceptionContext = _exceptionContext;
|
|
||||||
|
|
||||||
if (scope == Scope.Exception)
|
|
||||||
{
|
|
||||||
isCompleted = true;
|
|
||||||
return TaskCache.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exceptionContext != null)
|
|
||||||
{
|
|
||||||
if (exceptionContext.Result != null && !exceptionContext.ExceptionHandled)
|
|
||||||
{
|
|
||||||
goto case State.ExceptionShortCircuit;
|
|
||||||
}
|
|
||||||
|
|
||||||
Rethrow(exceptionContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
goto case State.ResourceInnerEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
case State.PageBegin:
|
case State.PageBegin:
|
||||||
{
|
{
|
||||||
var pageContext = _pageContext;
|
var pageContext = _pageContext;
|
||||||
|
|
@ -297,20 +113,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
case State.PageEnd:
|
case State.PageEnd:
|
||||||
{
|
|
||||||
if (scope == Scope.Exception)
|
|
||||||
{
|
|
||||||
// If we're inside an exception filter, let's allow those filters to 'unwind' before
|
|
||||||
// the result.
|
|
||||||
isCompleted = true;
|
|
||||||
return TaskCache.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.Assert(scope == Scope.Resource);
|
|
||||||
goto case State.ResourceInnerEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
case State.ResourceInnerEnd:
|
|
||||||
{
|
{
|
||||||
isCompleted = true;
|
isCompleted = true;
|
||||||
return TaskCache.CompletedTask;
|
return TaskCache.CompletedTask;
|
||||||
|
|
@ -348,6 +150,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
_model = CacheEntry.ModelFactory(_pageContext);
|
_model = CacheEntry.ModelFactory(_pageContext);
|
||||||
_pageContext.ViewData.Model = _model;
|
_pageContext.ViewData.Model = _model;
|
||||||
|
|
||||||
|
// Flow the PageModel in places where the result filters would flow the controller.
|
||||||
|
_instance = _model;
|
||||||
|
|
||||||
if (CacheEntry.PropertyBinder != null)
|
if (CacheEntry.PropertyBinder != null)
|
||||||
{
|
{
|
||||||
await CacheEntry.PropertyBinder(_pageContext, _model);
|
await CacheEntry.PropertyBinder(_pageContext, _model);
|
||||||
|
|
@ -388,8 +193,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
pageResult.Page = _page;
|
pageResult.Page = _page;
|
||||||
pageResult.ViewData = pageResult.ViewData ?? _pageContext.ViewData;
|
pageResult.ViewData = pageResult.ViewData ?? _pageContext.ViewData;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _result.ExecuteResultAsync(_pageContext);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ExecutePageWithoutPageModelAsync()
|
private async Task ExecutePageWithoutPageModelAsync()
|
||||||
|
|
@ -405,6 +208,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
|
||||||
_page = (Page)CacheEntry.PageFactory(_pageContext, _viewContext);
|
_page = (Page)CacheEntry.PageFactory(_pageContext, _viewContext);
|
||||||
|
|
||||||
|
// Flow the Page in places where the result filters would flow the controller.
|
||||||
|
_instance = _page;
|
||||||
|
|
||||||
if (_actionDescriptor.ModelTypeInfo == _actionDescriptor.PageTypeInfo)
|
if (_actionDescriptor.ModelTypeInfo == _actionDescriptor.PageTypeInfo)
|
||||||
{
|
{
|
||||||
_model = _page;
|
_model = _page;
|
||||||
|
|
@ -440,8 +246,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
pageResult.Page = _page;
|
pageResult.Page = _page;
|
||||||
pageResult.ViewData = pageResult.ViewData ?? _pageContext.ViewData;
|
pageResult.ViewData = pageResult.ViewData ?? _pageContext.ViewData;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _result.ExecuteResultAsync(_pageContext);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<object[]> GetArguments(HandlerMethodDescriptor handler)
|
private async Task<object[]> GetArguments(HandlerMethodDescriptor handler)
|
||||||
|
|
@ -506,74 +310,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task InvokeNextExceptionFilterAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var next = State.ExceptionNext;
|
|
||||||
var state = (object)null;
|
|
||||||
var scope = Scope.Exception;
|
|
||||||
var isCompleted = false;
|
|
||||||
while (!isCompleted)
|
|
||||||
{
|
|
||||||
await Next(ref next, ref scope, ref state, ref isCompleted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
_exceptionContext = new ExceptionContext(_actionContext, _filters)
|
|
||||||
{
|
|
||||||
ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void Rethrow(ExceptionContext 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
|
private enum Scope
|
||||||
{
|
{
|
||||||
Resource,
|
Invoker,
|
||||||
Exception,
|
|
||||||
Page,
|
Page,
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum State
|
private enum State
|
||||||
{
|
{
|
||||||
ResourceInnerBegin,
|
|
||||||
ExceptionBegin,
|
|
||||||
ExceptionNext,
|
|
||||||
ExceptionAsyncBegin,
|
|
||||||
ExceptionAsyncResume,
|
|
||||||
ExceptionAsyncEnd,
|
|
||||||
ExceptionSyncBegin,
|
|
||||||
ExceptionSyncEnd,
|
|
||||||
ExceptionInside,
|
|
||||||
ExceptionShortCircuit,
|
|
||||||
ExceptionEnd,
|
|
||||||
PageBegin,
|
PageBegin,
|
||||||
PageEnd,
|
PageEnd,
|
||||||
ResourceInnerEnd,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
Assert.Equal(4, sink.Writes.Count);
|
Assert.Equal(4, sink.Writes.Count);
|
||||||
Assert.Equal($"Executing action {displayName}", sink.Writes[0].State?.ToString());
|
Assert.Equal($"Executing action {displayName}", sink.Writes[0].State?.ToString());
|
||||||
Assert.Equal($"Executing action method {displayName} with arguments ((null)) - ModelState is Valid", sink.Writes[1].State?.ToString());
|
Assert.Equal($"Executing action method {displayName} with arguments ((null)) - ModelState is Valid", sink.Writes[1].State?.ToString());
|
||||||
Assert.Equal($"Executed action method {displayName}, returned result Microsoft.AspNetCore.Mvc.ContentResult.", sink.Writes[2].State?.ToString());
|
Assert.Equal($"Executed action method {displayName}, returned result {Result.GetType().FullName}.", sink.Writes[2].State?.ToString());
|
||||||
// This message has the execution time embedded, which we don't want to verify.
|
// This message has the execution time embedded, which we don't want to verify.
|
||||||
Assert.StartsWith($"Executed action {displayName} ", sink.Writes[3].State?.ToString());
|
Assert.StartsWith($"Executed action {displayName} ", sink.Writes[3].State?.ToString());
|
||||||
}
|
}
|
||||||
|
|
@ -226,7 +226,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
.Callback<ActionExecutedContext>(c => result = c.Result)
|
.Callback<ActionExecutedContext>(c => result = c.Result)
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
|
|
||||||
var invoker = CreateInvoker(filter.Object);
|
var invoker = CreateInvoker(filter.Object, result: Result);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await invoker.InvokeAsync();
|
await invoker.InvokeAsync();
|
||||||
|
|
@ -254,7 +254,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
})
|
})
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
|
|
||||||
var invoker = CreateInvoker(filter.Object);
|
var invoker = CreateInvoker(filter.Object, result: Result);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await invoker.InvokeAsync();
|
await invoker.InvokeAsync();
|
||||||
|
|
@ -1397,7 +1397,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
protected override ResourceInvoker CreateInvoker(
|
protected override ResourceInvoker CreateInvoker(
|
||||||
IFilterMetadata[] filters,
|
IFilterMetadata[] filters,
|
||||||
Exception exception = null,
|
Exception exception = null,
|
||||||
List<IValueProviderFactory> valueProviderFactories = null)
|
IActionResult result = null,
|
||||||
|
IList<IValueProviderFactory> valueProviderFactories = null)
|
||||||
{
|
{
|
||||||
var actionDescriptor = new ControllerActionDescriptor()
|
var actionDescriptor = new ControllerActionDescriptor()
|
||||||
{
|
{
|
||||||
|
|
@ -1407,7 +1408,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
BoundProperties = new List<ParameterDescriptor>(),
|
BoundProperties = new List<ParameterDescriptor>(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (exception == Exception)
|
if (result == Result)
|
||||||
|
{
|
||||||
|
actionDescriptor.MethodInfo = typeof(TestController).GetMethod(nameof(TestController.ActionMethod));
|
||||||
|
}
|
||||||
|
else if (result != null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Unexpected action result {result}.");
|
||||||
|
}
|
||||||
|
else if (exception == Exception)
|
||||||
{
|
{
|
||||||
actionDescriptor.MethodInfo = typeof(TestController).GetMethod(nameof(TestController.ThrowingActionMethod));
|
actionDescriptor.MethodInfo = typeof(TestController).GetMethod(nameof(TestController.ThrowingActionMethod));
|
||||||
}
|
}
|
||||||
|
|
@ -1501,10 +1510,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
new TestHttpResponseStreamWriterFactory(),
|
new TestHttpResponseStreamWriterFactory(),
|
||||||
NullLoggerFactory.Instance));
|
NullLoggerFactory.Instance));
|
||||||
|
|
||||||
services.AddSingleton(new ContentResultExecutor(
|
|
||||||
NullLogger<ContentResultExecutor>.Instance,
|
|
||||||
new MemoryPoolHttpResponseStreamWriterFactory(ArrayPool<byte>.Shared, ArrayPool<char>.Shared)));
|
|
||||||
|
|
||||||
httpContext.Response.Body = new MemoryStream();
|
httpContext.Response.Body = new MemoryStream();
|
||||||
httpContext.RequestServices = services.BuildServiceProvider();
|
httpContext.RequestServices = services.BuildServiceProvider();
|
||||||
|
|
||||||
|
|
@ -1740,47 +1745,5 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
actionDescriptor.ControllerTypeInfo,
|
actionDescriptor.ControllerTypeInfo,
|
||||||
ParameterDefaultValues.GetParameterDefaultValues(actionDescriptor.MethodInfo));
|
ParameterDefaultValues.GetParameterDefaultValues(actionDescriptor.MethodInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MockAuthorizationFilter : IAuthorizationFilter
|
|
||||||
{
|
|
||||||
int _expectedMaxAllowedErrors;
|
|
||||||
|
|
||||||
public MockAuthorizationFilter(int maxAllowedErrors)
|
|
||||||
{
|
|
||||||
_expectedMaxAllowedErrors = maxAllowedErrors;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnAuthorization(AuthorizationFilterContext context)
|
|
||||||
{
|
|
||||||
Assert.Equal(_expectedMaxAllowedErrors, context.ModelState.MaxAllowedErrors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TestParameterBinder : ParameterBinder
|
|
||||||
{
|
|
||||||
private readonly IDictionary<string, object> _actionParameters;
|
|
||||||
public TestParameterBinder(IDictionary<string, object> actionParameters)
|
|
||||||
: base(
|
|
||||||
new EmptyModelMetadataProvider(),
|
|
||||||
TestModelBinderFactory.CreateDefault(),
|
|
||||||
Mock.Of<IObjectModelValidator>())
|
|
||||||
{
|
|
||||||
_actionParameters = actionParameters;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<ModelBindingResult> BindModelAsync(
|
|
||||||
ActionContext actionContext,
|
|
||||||
IValueProvider valueProvider,
|
|
||||||
ParameterDescriptor parameter,
|
|
||||||
object value)
|
|
||||||
{
|
|
||||||
if (_actionParameters.TryGetValue(parameter.Name, out var result))
|
|
||||||
{
|
|
||||||
return Task.FromResult(ModelBindingResult.Success(result));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.FromResult(ModelBindingResult.Failed());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,11 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -22,544 +25,66 @@ using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
{
|
{
|
||||||
public class PageActionInvokerTest
|
public class PageActionInvokerTest : CommonResourceInvokerTest
|
||||||
{
|
{
|
||||||
private readonly DivideByZeroException _pageException = new DivideByZeroException();
|
protected override ResourceInvoker CreateInvoker(
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task InvokeAsync_DoesNotInvokeExceptionFilter_WhenPageDoesNotThrow()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var filter = new Mock<IExceptionFilter>(MockBehavior.Strict);
|
|
||||||
filter
|
|
||||||
.Setup(f => f.OnException(It.IsAny<ExceptionContext>()))
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
var invoker = CreateInvoker(new[] { filter.Object }, pageThrows: false);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await invoker.InvokeAsync();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
filter.Verify(f => f.OnException(It.IsAny<ExceptionContext>()), Times.Never());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task InvokeAsync_DoesNotAsyncInvokeExceptionFilter_WhenPageDoesNotThrow()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var filter = new Mock<IAsyncExceptionFilter>(MockBehavior.Strict);
|
|
||||||
filter
|
|
||||||
.Setup(f => f.OnExceptionAsync(It.IsAny<ExceptionContext>()))
|
|
||||||
.Returns<ExceptionContext>((context) => Task.FromResult(true))
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
var invoker = CreateInvoker(new[] { filter.Object }, pageThrows: false);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await invoker.InvokeAsync();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
filter.Verify(
|
|
||||||
f => f.OnExceptionAsync(It.IsAny<ExceptionContext>()),
|
|
||||||
Times.Never());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task InvokeAsync_InvokesExceptionFilter_WhenPageThrows()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
Exception exception = null;
|
|
||||||
IActionResult pageAction = null;
|
|
||||||
var expected = new Mock<IActionResult>(MockBehavior.Strict);
|
|
||||||
expected
|
|
||||||
.Setup(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()))
|
|
||||||
.Returns(Task.FromResult(true))
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
var filter1 = new Mock<IExceptionFilter>(MockBehavior.Strict);
|
|
||||||
filter1
|
|
||||||
.Setup(f => f.OnException(It.IsAny<ExceptionContext>()))
|
|
||||||
.Verifiable();
|
|
||||||
var filter2 = new Mock<IExceptionFilter>(MockBehavior.Strict);
|
|
||||||
filter2
|
|
||||||
.Setup(f => f.OnException(It.IsAny<ExceptionContext>()))
|
|
||||||
.Callback<ExceptionContext>(context =>
|
|
||||||
{
|
|
||||||
exception = context.Exception;
|
|
||||||
pageAction = context.Result;
|
|
||||||
|
|
||||||
// Handle the exception
|
|
||||||
context.Result = expected.Object;
|
|
||||||
})
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object }, pageThrows: true);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await invoker.InvokeAsync();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expected.Verify(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()), Times.Once());
|
|
||||||
filter2.Verify(f => f.OnException(It.IsAny<ExceptionContext>()), Times.Once());
|
|
||||||
|
|
||||||
Assert.Same(_pageException, exception);
|
|
||||||
Assert.Null(pageAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task InvokeAsync_InvokesAsyncExceptionFilter_WhenPageThrows()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
Exception exception = null;
|
|
||||||
IActionResult pageAction = null;
|
|
||||||
var expected = new Mock<IActionResult>(MockBehavior.Strict);
|
|
||||||
expected
|
|
||||||
.Setup(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()))
|
|
||||||
.Returns(Task.FromResult(true))
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
var filter1 = new Mock<IAsyncExceptionFilter>(MockBehavior.Strict);
|
|
||||||
filter1
|
|
||||||
.Setup(f => f.OnExceptionAsync(It.IsAny<ExceptionContext>()))
|
|
||||||
.Returns<ExceptionContext>((context) => Task.FromResult(true))
|
|
||||||
.Verifiable();
|
|
||||||
var filter2 = new Mock<IAsyncExceptionFilter>(MockBehavior.Strict);
|
|
||||||
filter2
|
|
||||||
.Setup(f => f.OnExceptionAsync(It.IsAny<ExceptionContext>()))
|
|
||||||
.Callback<ExceptionContext>(context =>
|
|
||||||
{
|
|
||||||
exception = context.Exception;
|
|
||||||
pageAction = context.Result;
|
|
||||||
|
|
||||||
// Handle the exception
|
|
||||||
context.Result = expected.Object;
|
|
||||||
})
|
|
||||||
.Returns<ExceptionContext>((context) => Task.FromResult(true))
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object }, pageThrows: true);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await invoker.InvokeAsync();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expected.Verify(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()), Times.Once());
|
|
||||||
filter2.Verify(
|
|
||||||
f => f.OnExceptionAsync(It.IsAny<ExceptionContext>()),
|
|
||||||
Times.Once());
|
|
||||||
|
|
||||||
Assert.Same(_pageException, exception);
|
|
||||||
Assert.Null(pageAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task InvokeAsync_InvokesExceptionFilter_ShortCircuit_ExceptionNull()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var filter1 = new Mock<IExceptionFilter>(MockBehavior.Strict);
|
|
||||||
|
|
||||||
var filter2 = new Mock<IExceptionFilter>(MockBehavior.Strict);
|
|
||||||
filter2
|
|
||||||
.Setup(f => f.OnException(It.IsAny<ExceptionContext>()))
|
|
||||||
.Callback<ExceptionContext>(context =>
|
|
||||||
{
|
|
||||||
filter2.ToString();
|
|
||||||
context.Exception = null;
|
|
||||||
})
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object }, pageThrows: true);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await invoker.InvokeAsync();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
filter2.Verify(
|
|
||||||
f => f.OnException(It.IsAny<ExceptionContext>()),
|
|
||||||
Times.Once());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task InvokeAsync_InvokesExceptionFilter_ShortCircuit_ExceptionHandled()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var filter1 = new Mock<IExceptionFilter>(MockBehavior.Strict);
|
|
||||||
|
|
||||||
var filter2 = new Mock<IExceptionFilter>(MockBehavior.Strict);
|
|
||||||
filter2
|
|
||||||
.Setup(f => f.OnException(It.IsAny<ExceptionContext>()))
|
|
||||||
.Callback<ExceptionContext>(context =>
|
|
||||||
{
|
|
||||||
context.ExceptionHandled = true;
|
|
||||||
})
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object }, pageThrows: true);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await invoker.InvokeAsync();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
filter2.Verify(
|
|
||||||
f => f.OnException(It.IsAny<ExceptionContext>()),
|
|
||||||
Times.Once());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task InvokeAsync_InvokesAsyncExceptionFilter_ShortCircuit_ExceptionNull()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var filter1 = new Mock<IExceptionFilter>(MockBehavior.Strict);
|
|
||||||
var filter2 = new Mock<IAsyncExceptionFilter>(MockBehavior.Strict);
|
|
||||||
|
|
||||||
filter2
|
|
||||||
.Setup(f => f.OnExceptionAsync(It.IsAny<ExceptionContext>()))
|
|
||||||
.Callback<ExceptionContext>(context =>
|
|
||||||
{
|
|
||||||
filter2.ToString();
|
|
||||||
context.Exception = null;
|
|
||||||
})
|
|
||||||
.Returns<ExceptionContext>((context) => Task.FromResult(true))
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
var filterMetadata = new IFilterMetadata[] { filter1.Object, filter2.Object };
|
|
||||||
var invoker = CreateInvoker(filterMetadata, pageThrows: true);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await invoker.InvokeAsync();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
filter2.Verify(
|
|
||||||
f => f.OnExceptionAsync(It.IsAny<ExceptionContext>()),
|
|
||||||
Times.Once());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task InvokeAsync_InvokesAsyncExceptionFilter_ShortCircuit_ExceptionHandled()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var filter1 = new Mock<IExceptionFilter>(MockBehavior.Strict);
|
|
||||||
|
|
||||||
var filter2 = new Mock<IAsyncExceptionFilter>(MockBehavior.Strict);
|
|
||||||
filter2
|
|
||||||
.Setup(f => f.OnExceptionAsync(It.IsAny<ExceptionContext>()))
|
|
||||||
.Callback<ExceptionContext>(context =>
|
|
||||||
{
|
|
||||||
context.ExceptionHandled = true;
|
|
||||||
})
|
|
||||||
.Returns<ExceptionContext>((context) => Task.FromResult(true))
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object }, pageThrows: true);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await invoker.InvokeAsync();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
filter2.Verify(
|
|
||||||
f => f.OnExceptionAsync(It.IsAny<ExceptionContext>()),
|
|
||||||
Times.Once());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task InvokeAsync_InvokesExceptionFilter_UnhandledExceptionIsThrown()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var filter = new Mock<IExceptionFilter>(MockBehavior.Strict);
|
|
||||||
filter
|
|
||||||
.Setup(f => f.OnException(It.IsAny<ExceptionContext>()))
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
var invoker = CreateInvoker(new[] { filter.Object }, pageThrows: true);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await Assert.ThrowsAsync(_pageException.GetType(), invoker.InvokeAsync);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
filter.Verify(f => f.OnException(It.IsAny<ExceptionContext>()), Times.Once());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task InvokeAsync_InvokesAuthorizationFilter()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var filter = new Mock<IAuthorizationFilter>(MockBehavior.Strict);
|
|
||||||
filter.Setup(f => f.OnAuthorization(It.IsAny<AuthorizationFilterContext>())).Verifiable();
|
|
||||||
|
|
||||||
var invoker = CreateInvoker(new[] { filter.Object });
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await invoker.InvokeAsync();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
filter.Verify(f => f.OnAuthorization(It.IsAny<AuthorizationFilterContext>()), Times.Once());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task InvokeAsync_InvokesAsyncAuthorizationFilter()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var filter = new Mock<IAsyncAuthorizationFilter>(MockBehavior.Strict);
|
|
||||||
filter
|
|
||||||
.Setup(f => f.OnAuthorizationAsync(It.IsAny<AuthorizationFilterContext>()))
|
|
||||||
.Returns<AuthorizationFilterContext>(context => Task.FromResult(true))
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
var invoker = CreateInvoker(new[] { filter.Object });
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await invoker.InvokeAsync();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
filter.Verify(
|
|
||||||
f => f.OnAuthorizationAsync(It.IsAny<AuthorizationFilterContext>()),
|
|
||||||
Times.Once());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task InvokeAsync_InvokesAuthorizationFilter_ShortCircuit()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var createCalled = false;
|
|
||||||
var challenge = new Mock<IActionResult>(MockBehavior.Strict);
|
|
||||||
challenge
|
|
||||||
.Setup(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()))
|
|
||||||
.Returns(Task.FromResult(true))
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
var filter1 = new Mock<IAuthorizationFilter>(MockBehavior.Strict);
|
|
||||||
filter1
|
|
||||||
.Setup(f => f.OnAuthorization(It.IsAny<AuthorizationFilterContext>()))
|
|
||||||
.Callback<AuthorizationFilterContext>(c => Task.FromResult(true))
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
var filter2 = new Mock<IAuthorizationFilter>(MockBehavior.Strict);
|
|
||||||
filter2
|
|
||||||
.Setup(f => f.OnAuthorization(It.IsAny<AuthorizationFilterContext>()))
|
|
||||||
.Callback<AuthorizationFilterContext>(c => c.Result = challenge.Object)
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
var filter3 = new Mock<IAuthorizationFilter>(MockBehavior.Strict);
|
|
||||||
|
|
||||||
var actionDescriptor = new CompiledPageActionDescriptor()
|
|
||||||
{
|
|
||||||
HandlerTypeInfo = typeof(TestPage).GetTypeInfo(),
|
|
||||||
ModelTypeInfo = typeof(TestPage).GetTypeInfo(),
|
|
||||||
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
|
|
||||||
};
|
|
||||||
|
|
||||||
var cacheEntry = new PageActionInvokerCacheEntry(
|
|
||||||
actionDescriptor,
|
|
||||||
null,
|
|
||||||
(context, viewContext) => createCalled = true,
|
|
||||||
null,
|
|
||||||
(context) => null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
new FilterItem[0]);
|
|
||||||
var invoker = CreateInvoker(
|
|
||||||
new[] { filter1.Object, filter2.Object, filter3.Object },
|
|
||||||
actionDescriptor,
|
|
||||||
cacheEntry: cacheEntry);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await invoker.InvokeAsync();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
challenge.Verify(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()), Times.Once());
|
|
||||||
filter1.Verify(f => f.OnAuthorization(It.IsAny<AuthorizationFilterContext>()), Times.Once());
|
|
||||||
Assert.False(createCalled);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task InvokeAsync_InvokesAsyncAuthorizationFilter_ShortCircuit()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var createCalled = false;
|
|
||||||
var challenge = new Mock<IActionResult>(MockBehavior.Strict);
|
|
||||||
challenge
|
|
||||||
.Setup(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()))
|
|
||||||
.Returns(Task.FromResult(true))
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
var filter1 = new Mock<IAsyncAuthorizationFilter>(MockBehavior.Strict);
|
|
||||||
filter1
|
|
||||||
.Setup(f => f.OnAuthorizationAsync(It.IsAny<AuthorizationFilterContext>()))
|
|
||||||
.Returns<AuthorizationFilterContext>((context) =>
|
|
||||||
{
|
|
||||||
return Task.FromResult(true);
|
|
||||||
})
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
var filter2 = new Mock<IAsyncAuthorizationFilter>(MockBehavior.Strict);
|
|
||||||
filter2
|
|
||||||
.Setup(f => f.OnAuthorizationAsync(It.IsAny<AuthorizationFilterContext>()))
|
|
||||||
.Returns<AuthorizationFilterContext>((context) =>
|
|
||||||
{
|
|
||||||
context.Result = challenge.Object;
|
|
||||||
return Task.FromResult(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
var filter3 = new Mock<IAuthorizationFilter>(MockBehavior.Strict);
|
|
||||||
|
|
||||||
var actionDescriptor = new CompiledPageActionDescriptor()
|
|
||||||
{
|
|
||||||
HandlerTypeInfo = typeof(TestPage).GetTypeInfo(),
|
|
||||||
ModelTypeInfo = typeof(TestPage).GetTypeInfo(),
|
|
||||||
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
|
|
||||||
};
|
|
||||||
|
|
||||||
var cacheEntry = new PageActionInvokerCacheEntry(
|
|
||||||
actionDescriptor,
|
|
||||||
null,
|
|
||||||
(context, viewContext) => createCalled = true,
|
|
||||||
null,
|
|
||||||
(context) => null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
new FilterItem[0]);
|
|
||||||
var invoker = CreateInvoker(
|
|
||||||
new IFilterMetadata[] { filter1.Object, filter2.Object, filter3.Object },
|
|
||||||
actionDescriptor,
|
|
||||||
cacheEntry: cacheEntry);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await invoker.InvokeAsync();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
challenge.Verify(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()), Times.Once());
|
|
||||||
filter1.Verify(
|
|
||||||
f => f.OnAuthorizationAsync(It.IsAny<AuthorizationFilterContext>()),
|
|
||||||
Times.Once());
|
|
||||||
|
|
||||||
Assert.False(createCalled);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task InvokeAsync_ExceptionInAuthorizationFilter_CannotBeHandledByOtherFilters()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var expected = new InvalidCastException();
|
|
||||||
|
|
||||||
var exceptionFilter = new Mock<IExceptionFilter>(MockBehavior.Strict);
|
|
||||||
exceptionFilter
|
|
||||||
.Setup(f => f.OnException(It.IsAny<ExceptionContext>()))
|
|
||||||
.Callback<ExceptionContext>(context =>
|
|
||||||
{
|
|
||||||
// Mark as handled
|
|
||||||
context.Result = new EmptyResult();
|
|
||||||
})
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
var authorizationFilter1 = new Mock<IAuthorizationFilter>(MockBehavior.Strict);
|
|
||||||
authorizationFilter1
|
|
||||||
.Setup(f => f.OnAuthorization(It.IsAny<AuthorizationFilterContext>()))
|
|
||||||
.Callback<AuthorizationFilterContext>(c => { throw expected; })
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
// None of these filters should run
|
|
||||||
var authorizationFilter2 = new Mock<IAuthorizationFilter>(MockBehavior.Strict);
|
|
||||||
var resourceFilter = new Mock<IResourceFilter>(MockBehavior.Strict);
|
|
||||||
var actionFilter = new Mock<IActionFilter>(MockBehavior.Strict);
|
|
||||||
var resultFilter = new Mock<IResultFilter>(MockBehavior.Strict);
|
|
||||||
|
|
||||||
var invoker = CreateInvoker(new IFilterMetadata[]
|
|
||||||
{
|
|
||||||
exceptionFilter.Object,
|
|
||||||
authorizationFilter1.Object,
|
|
||||||
authorizationFilter2.Object,
|
|
||||||
resourceFilter.Object,
|
|
||||||
actionFilter.Object,
|
|
||||||
resultFilter.Object,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var thrown = await Assert.ThrowsAsync<InvalidCastException>(invoker.InvokeAsync);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Same(expected, thrown);
|
|
||||||
exceptionFilter.Verify(f => f.OnException(It.IsAny<ExceptionContext>()), Times.Never());
|
|
||||||
authorizationFilter1.Verify(f => f.OnAuthorization(It.IsAny<AuthorizationFilterContext>()), Times.Once());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task InvokeAsync_InvokesAuthorizationFilter_ChallengeNotSeenByResultFilters()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var challenge = new Mock<IActionResult>(MockBehavior.Strict);
|
|
||||||
challenge
|
|
||||||
.Setup(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()))
|
|
||||||
.Returns<ActionContext>((context) => Task.FromResult(true))
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
var authorizationFilter = new Mock<IAuthorizationFilter>(MockBehavior.Strict);
|
|
||||||
authorizationFilter
|
|
||||||
.Setup(f => f.OnAuthorization(It.IsAny<AuthorizationFilterContext>()))
|
|
||||||
.Callback<AuthorizationFilterContext>(c => c.Result = challenge.Object)
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
var resultFilter = new Mock<IResultFilter>(MockBehavior.Strict);
|
|
||||||
|
|
||||||
var invoker = CreateInvoker(new IFilterMetadata[] { authorizationFilter.Object, resultFilter.Object });
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await invoker.InvokeAsync();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
authorizationFilter.Verify(f => f.OnAuthorization(It.IsAny<AuthorizationFilterContext>()), Times.Once());
|
|
||||||
challenge.Verify(c => c.ExecuteResultAsync(It.IsAny<ActionContext>()), Times.Once());
|
|
||||||
}
|
|
||||||
|
|
||||||
private PageActionInvoker CreateInvoker(
|
|
||||||
IFilterMetadata[] filters,
|
IFilterMetadata[] filters,
|
||||||
bool pageThrows = false,
|
Exception exception = null,
|
||||||
int maxAllowedErrorsInModelState = 200,
|
IActionResult result = null,
|
||||||
List<IValueProviderFactory> valueProviderFactories = null)
|
IList<IValueProviderFactory> valueProviderFactories = null)
|
||||||
{
|
{
|
||||||
Func<PageContext, Task> executeAction;
|
|
||||||
if (pageThrows)
|
|
||||||
{
|
|
||||||
executeAction = _ => { throw _pageException; };
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
executeAction = context => context.HttpContext.Response.WriteAsync("Hello");
|
|
||||||
}
|
|
||||||
var executor = new TestPageResultExecutor(executeAction);
|
|
||||||
var actionDescriptor = new CompiledPageActionDescriptor
|
var actionDescriptor = new CompiledPageActionDescriptor
|
||||||
{
|
{
|
||||||
ViewEnginePath = "/Index.cshtml",
|
ViewEnginePath = "/Index.cshtml",
|
||||||
RelativePath = "/Index.cshtml",
|
RelativePath = "/Index.cshtml",
|
||||||
|
HandlerMethods = new List<HandlerMethodDescriptor>(),
|
||||||
HandlerTypeInfo = typeof(TestPage).GetTypeInfo(),
|
HandlerTypeInfo = typeof(TestPage).GetTypeInfo(),
|
||||||
ModelTypeInfo = typeof(TestPage).GetTypeInfo(),
|
ModelTypeInfo = typeof(TestPage).GetTypeInfo(),
|
||||||
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
|
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var handlers = new List<Func<object, object[], Task<IActionResult>>>();
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
handlers.Add((obj, args) => Task.FromResult(result));
|
||||||
|
actionDescriptor.HandlerMethods.Add(new HandlerMethodDescriptor()
|
||||||
|
{
|
||||||
|
HttpMethod = "GET",
|
||||||
|
Parameters = new List<HandlerParameterDescriptor>(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (exception != null)
|
||||||
|
{
|
||||||
|
handlers.Add((obj, args) => Task.FromException<IActionResult>(exception));
|
||||||
|
actionDescriptor.HandlerMethods.Add(new HandlerMethodDescriptor()
|
||||||
|
{
|
||||||
|
HttpMethod = "GET",
|
||||||
|
Parameters = new List<HandlerParameterDescriptor>(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var executor = new TestPageResultExecutor();
|
||||||
return CreateInvoker(
|
return CreateInvoker(
|
||||||
filters,
|
filters,
|
||||||
actionDescriptor,
|
actionDescriptor,
|
||||||
executor);
|
executor,
|
||||||
|
handlers: handlers.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
private PageActionInvoker CreateInvoker(
|
private PageActionInvoker CreateInvoker(
|
||||||
IFilterMetadata[] filters,
|
IFilterMetadata[] filters,
|
||||||
CompiledPageActionDescriptor actionDescriptor,
|
CompiledPageActionDescriptor actionDescriptor,
|
||||||
PageResultExecutor executor = null,
|
PageResultExecutor executor = null,
|
||||||
IPageHandlerMethodSelector selector = null,
|
|
||||||
PageActionInvokerCacheEntry cacheEntry = null,
|
PageActionInvokerCacheEntry cacheEntry = null,
|
||||||
ITempDataDictionaryFactory tempDataFactory = null,
|
ITempDataDictionaryFactory tempDataFactory = null,
|
||||||
int maxAllowedErrorsInModelState = 200,
|
IList<IValueProviderFactory> valueProviderFactories = null,
|
||||||
List<IValueProviderFactory> valueProviderFactories = null,
|
Func<object, object[], Task<IActionResult>>[] handlers = null,
|
||||||
RouteData routeData = null,
|
RouteData routeData = null,
|
||||||
ILogger logger = null)
|
ILogger logger = null)
|
||||||
{
|
{
|
||||||
|
|
@ -578,6 +103,17 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
HtmlEncoder.Default);
|
HtmlEncoder.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mvcOptionsAccessor = new TestOptionsManager<MvcOptions>();
|
||||||
|
serviceCollection.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
|
||||||
|
serviceCollection.AddSingleton<IOptions<MvcOptions>>(mvcOptionsAccessor);
|
||||||
|
serviceCollection.AddSingleton(new ObjectResultExecutor(
|
||||||
|
mvcOptionsAccessor,
|
||||||
|
new TestHttpResponseStreamWriterFactory(),
|
||||||
|
NullLoggerFactory.Instance));
|
||||||
|
|
||||||
|
httpContext.Response.Body = new MemoryStream();
|
||||||
|
httpContext.RequestServices = serviceCollection.BuildServiceProvider();
|
||||||
|
|
||||||
serviceCollection.AddSingleton(executor ?? executor);
|
serviceCollection.AddSingleton(executor ?? executor);
|
||||||
httpContext.RequestServices = serviceCollection.BuildServiceProvider();
|
httpContext.RequestServices = serviceCollection.BuildServiceProvider();
|
||||||
|
|
||||||
|
|
@ -597,11 +133,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
|
||||||
var viewDataFactory = ViewDataDictionaryFactory.CreateFactory(actionDescriptor.ModelTypeInfo);
|
var viewDataFactory = ViewDataDictionaryFactory.CreateFactory(actionDescriptor.ModelTypeInfo);
|
||||||
pageContext.ViewData = viewDataFactory(new EmptyModelMetadataProvider(), pageContext.ModelState);
|
pageContext.ViewData = viewDataFactory(new EmptyModelMetadataProvider(), pageContext.ModelState);
|
||||||
|
|
||||||
if (selector == null)
|
|
||||||
{
|
|
||||||
selector = Mock.Of<IPageHandlerMethodSelector>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (valueProviderFactories == null)
|
if (valueProviderFactories == null)
|
||||||
{
|
{
|
||||||
|
|
@ -633,17 +164,23 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
_ => Activator.CreateInstance(actionDescriptor.ModelTypeInfo.AsType()),
|
_ => Activator.CreateInstance(actionDescriptor.ModelTypeInfo.AsType()),
|
||||||
(c, model) => { (model as IDisposable)?.Dispose(); },
|
(c, model) => { (model as IDisposable)?.Dispose(); },
|
||||||
null,
|
null,
|
||||||
null,
|
handlers,
|
||||||
null,
|
null,
|
||||||
new FilterItem[0]);
|
new FilterItem[0]);
|
||||||
|
|
||||||
|
// Always just select the first one.
|
||||||
|
var selector = new Mock<IPageHandlerMethodSelector>();
|
||||||
|
selector
|
||||||
|
.Setup(s => s.Select(It.IsAny<PageContext>()))
|
||||||
|
.Returns<PageContext>(c => c.ActionDescriptor.HandlerMethods.FirstOrDefault());
|
||||||
|
|
||||||
var invoker = new PageActionInvoker(
|
var invoker = new PageActionInvoker(
|
||||||
selector,
|
selector.Object,
|
||||||
diagnosticSource,
|
diagnosticSource,
|
||||||
logger,
|
logger,
|
||||||
pageContext,
|
pageContext,
|
||||||
filters,
|
filters,
|
||||||
valueProviderFactories.AsReadOnly(),
|
valueProviderFactories.ToArray(),
|
||||||
cacheEntry,
|
cacheEntry,
|
||||||
GetParameterBinder(),
|
GetParameterBinder(),
|
||||||
tempDataFactory,
|
tempDataFactory,
|
||||||
|
|
@ -687,6 +224,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
{
|
{
|
||||||
private readonly Func<PageContext, Task> _executeAction;
|
private readonly Func<PageContext, Task> _executeAction;
|
||||||
|
|
||||||
|
public TestPageResultExecutor()
|
||||||
|
: this(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public TestPageResultExecutor(Func<PageContext, Task> executeAction)
|
public TestPageResultExecutor(Func<PageContext, Task> executeAction)
|
||||||
: base(
|
: base(
|
||||||
Mock.Of<IHttpResponseStreamWriterFactory>(),
|
Mock.Of<IHttpResponseStreamWriterFactory>(),
|
||||||
|
|
@ -700,7 +242,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task ExecuteAsync(PageContext pageContext, PageResult result)
|
public override Task ExecuteAsync(PageContext pageContext, PageResult result)
|
||||||
=> _executeAction(pageContext);
|
{
|
||||||
|
return _executeAction?.Invoke(pageContext) ?? Task.CompletedTask;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestPage : Page
|
private class TestPage : Page
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
{
|
{
|
||||||
public abstract class CommonResourceInvokerTest
|
public abstract class CommonResourceInvokerTest
|
||||||
{
|
{
|
||||||
protected static readonly ContentResult Result = new ContentResult() { Content = "Hello, world!" };
|
protected static readonly TestResult Result = new TestResult();
|
||||||
|
|
||||||
// Intentionally choosing an uncommon exception type.
|
// Intentionally choosing an uncommon exception type.
|
||||||
protected static readonly Exception Exception = new DivideByZeroException();
|
protected static readonly Exception Exception = new DivideByZeroException();
|
||||||
|
|
@ -23,15 +23,17 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
protected ResourceInvoker CreateInvoker(
|
protected ResourceInvoker CreateInvoker(
|
||||||
IFilterMetadata filter,
|
IFilterMetadata filter,
|
||||||
Exception exception = null,
|
Exception exception = null,
|
||||||
List<IValueProviderFactory> valueProviderFactories = null)
|
IActionResult result = null,
|
||||||
|
IList<IValueProviderFactory> valueProviderFactories = null)
|
||||||
{
|
{
|
||||||
return CreateInvoker(new IFilterMetadata[] { filter }, exception, valueProviderFactories);
|
return CreateInvoker(new IFilterMetadata[] { filter }, exception, result, valueProviderFactories);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract ResourceInvoker CreateInvoker(
|
protected abstract ResourceInvoker CreateInvoker(
|
||||||
IFilterMetadata[] filters,
|
IFilterMetadata[] filters,
|
||||||
Exception exception = null,
|
Exception exception = null,
|
||||||
List<IValueProviderFactory> valueProviderFactories = null);
|
IActionResult result = null,
|
||||||
|
IList<IValueProviderFactory> valueProviderFactories = null);
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task InvokeAction_DoesNotInvokeExceptionFilter_WhenActionDoesNotThrow()
|
public async Task InvokeAction_DoesNotInvokeExceptionFilter_WhenActionDoesNotThrow()
|
||||||
|
|
@ -845,7 +847,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
|
|
||||||
var filter3 = new Mock<IResultFilter>(MockBehavior.Strict);
|
var filter3 = new Mock<IResultFilter>(MockBehavior.Strict);
|
||||||
|
|
||||||
var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object, filter3.Object });
|
var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object, filter3.Object }, result: Result);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await invoker.InvokeAsync();
|
await invoker.InvokeAsync();
|
||||||
|
|
@ -859,7 +861,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
Times.Once());
|
Times.Once());
|
||||||
|
|
||||||
Assert.True(context.Canceled);
|
Assert.True(context.Canceled);
|
||||||
Assert.IsType<ContentResult>(context.Result);
|
Assert.Same(Result, context.Result);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -887,7 +889,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
|
|
||||||
var filter3 = new Mock<IResultFilter>(MockBehavior.Strict);
|
var filter3 = new Mock<IResultFilter>(MockBehavior.Strict);
|
||||||
|
|
||||||
var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object, filter3.Object });
|
var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object, filter3.Object }, result: Result);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await invoker.InvokeAsync();
|
await invoker.InvokeAsync();
|
||||||
|
|
@ -901,7 +903,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
Times.Once());
|
Times.Once());
|
||||||
|
|
||||||
Assert.True(context.Canceled);
|
Assert.True(context.Canceled);
|
||||||
Assert.IsType<ContentResult>(context.Result);
|
Assert.Same(Result, context.Result);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -1203,7 +1205,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
})
|
})
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
|
|
||||||
var invoker = CreateInvoker(resourceFilter.Object);
|
var invoker = CreateInvoker(resourceFilter.Object, result: Result);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await invoker.InvokeAsync();
|
await invoker.InvokeAsync();
|
||||||
|
|
@ -1710,5 +1712,9 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
f => f.OnResourceExecutionAsync(It.IsAny<ResourceExecutingContext>(), It.IsAny<ResourceExecutionDelegate>()),
|
f => f.OnResourceExecutionAsync(It.IsAny<ResourceExecutingContext>(), It.IsAny<ResourceExecutionDelegate>()),
|
||||||
Times.Never());
|
Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class TestResult : ActionResult
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue