Added ValueProviderFactories to ResourceFilterExecutingContext

This enables removing value provider factories from model binding(which is needed in some scenarios like large file uploads)
This commit is contained in:
Kiran Challa 2016-05-17 10:07:44 -07:00
parent df4eb283b2
commit 20a2e748ec
10 changed files with 292 additions and 88 deletions

View File

@ -31,6 +31,10 @@
"imports": [
"portable-net45+wp80+win8+wpa81+dnxcore50"
]
},
"Microsoft.DotNet.Watcher.Tools": {
"version": "1.0.0-*",
"imports": "portable-net451+win8"
}
},
"frameworks": {

View File

@ -1,7 +1,9 @@
// 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 Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Microsoft.AspNetCore.Mvc.Filters
{
@ -16,9 +18,19 @@ namespace Microsoft.AspNetCore.Mvc.Filters
/// </summary>
/// <param name="actionContext">The <see cref="ActionContext"/>.</param>
/// <param name="filters">The list of <see cref="IFilterMetadata"/> instances.</param>
public ResourceExecutingContext(ActionContext actionContext, IList<IFilterMetadata> filters)
/// <param name="valueProviderFactories">The list of <see cref="IValueProviderFactory"/> instances.</param>
public ResourceExecutingContext(
ActionContext actionContext,
IList<IFilterMetadata> filters,
IList<IValueProviderFactory> valueProviderFactories)
: base(actionContext, filters)
{
if (valueProviderFactories == null)
{
throw new ArgumentNullException(nameof(valueProviderFactories));
}
ValueProviderFactories = valueProviderFactories;
}
/// <summary>
@ -29,5 +41,10 @@ namespace Microsoft.AspNetCore.Mvc.Filters
/// short-circuit execution of additional resource filters and the action itself.
/// </remarks>
public virtual IActionResult Result { get; set; }
/// <summary>
/// Gets the list of <see cref="IValueProviderFactory"/> instances used by model binding.
/// </summary>
public IList<IValueProviderFactory> ValueProviderFactories { get; }
}
}

View File

@ -92,7 +92,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{
throw new ArgumentNullException(nameof(valueProviderFactories));
}
_controllerFactory = controllerFactory;
_controllerArgumentBinder = controllerArgumentBinder;
_logger = logger;
@ -216,7 +216,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{
_cursor.Reset();
_resourceExecutingContext = new ResourceExecutingContext(_controllerContext, _filters);
_resourceExecutingContext = new ResourceExecutingContext(
_controllerContext,
_filters,
_controllerContext.ValueProviderFactories);
return InvokeResourceFilterAsync();
}
@ -266,7 +270,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
_diagnosticSource.AfterOnResourceExecution(_resourceExecutedContext, item.FilterAsync);
if (_resourceExecutingContext.Result != null)
{
_logger.ResourceFilterShortCircuited(item.FilterAsync);

View File

@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Routing;
using Microsoft.Net.Http.Headers;
using Moq;
@ -309,7 +310,10 @@ namespace Microsoft.AspNetCore.Mvc
};
var actionContext = new ActionContext(httpContext, new RouteData(), actionWithConstraint);
var resourceExecutingContext = new ResourceExecutingContext(actionContext, new[] { consumesFilter });
var resourceExecutingContext = new ResourceExecutingContext(
actionContext,
new[] { consumesFilter },
new List<IValueProviderFactory>());
// Act
consumesFilter.OnResourceExecuting(resourceExecutingContext);
@ -336,7 +340,10 @@ namespace Microsoft.AspNetCore.Mvc
};
var actionContext = new ActionContext(httpContext, new RouteData(), actionWithConstraint);
var resourceExecutingContext = new ResourceExecutingContext(actionContext, new[] { consumesFilter });
var resourceExecutingContext = new ResourceExecutingContext(
actionContext,
new[] { consumesFilter },
new List<IValueProviderFactory>());
// Act
consumesFilter.OnResourceExecuting(resourceExecutingContext);
@ -361,7 +368,10 @@ namespace Microsoft.AspNetCore.Mvc
new List<FilterDescriptor>() { new FilterDescriptor(consumesFilter, FilterScope.Action) }
};
var actionContext = new ActionContext(httpContext, new RouteData(), actionWithConstraint);
var resourceExecutingContext = new ResourceExecutingContext(actionContext, new[] { consumesFilter });
var resourceExecutingContext = new ResourceExecutingContext(
actionContext,
new[] { consumesFilter },
new List<IValueProviderFactory>());
// Act
consumesFilter.OnResourceExecuting(resourceExecutingContext);

View File

@ -2,9 +2,11 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Buffers;
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.TestCommon;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Options;
@ -87,7 +89,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var resourceExecutingContext = new ResourceExecutingContext(
ac,
new IFilterMetadata[] { });
new IFilterMetadata[] { },
new List<IValueProviderFactory>());
var filter = new FormatFilter(mockObjects.OptionsManager);
@ -322,7 +325,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var resourceExecutingContext = new ResourceExecutingContext(
actionContext,
new IFilterMetadata[] { });
new IFilterMetadata[] { },
new List<IValueProviderFactory>());
var filter = new FormatFilter(mockObjects.OptionsManager);
@ -356,7 +360,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var resourceExecutingContext = new ResourceExecutingContext(
actionContext,
new IFilterMetadata[] { });
new IFilterMetadata[] { },
new List<IValueProviderFactory>());
var filter = new FormatFilter(mockObjects.OptionsManager);
@ -392,7 +397,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
{
var context = new ResourceExecutingContext(
MockActionContext,
filters);
filters,
new List<IValueProviderFactory>());
return context;
}

View File

@ -1862,6 +1862,66 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Assert.False(invoker.ControllerFactory.CreateCalled);
}
[Fact]
public async Task AddingValueProviderFactory_AtResourceFilter_IsAvailableInControllerContext()
{
// Arrange
var valueProviderFactory2 = Mock.Of<IValueProviderFactory>();
var resourceFilter = new Mock<IResourceFilter>();
resourceFilter
.Setup(f => f.OnResourceExecuting(It.IsAny<ResourceExecutingContext>()))
.Callback<ResourceExecutingContext>((resourceExecutingContext) =>
{
resourceExecutingContext.ValueProviderFactories.Add(valueProviderFactory2);
});
var valueProviderFactory1 = Mock.Of<IValueProviderFactory>();
var valueProviderFactories = new List<IValueProviderFactory>();
valueProviderFactories.Add(valueProviderFactory1);
var invoker = CreateInvoker(
new IFilterMetadata[] { resourceFilter.Object }, valueProviderFactories: valueProviderFactories);
// Act
await invoker.InvokeAsync();
// Assert
var controllerContext = invoker.ControllerFactory.ControllerContext;
Assert.NotNull(controllerContext);
Assert.Equal(2, controllerContext.ValueProviderFactories.Count);
Assert.Same(valueProviderFactory1, controllerContext.ValueProviderFactories[0]);
Assert.Same(valueProviderFactory2, controllerContext.ValueProviderFactories[1]);
}
[Fact]
public async Task DeletingValueProviderFactory_AtResourceFilter_IsNotAvailableInControllerContext()
{
// Arrange
var resourceFilter = new Mock<IResourceFilter>();
resourceFilter
.Setup(f => f.OnResourceExecuting(It.IsAny<ResourceExecutingContext>()))
.Callback<ResourceExecutingContext>((resourceExecutingContext) =>
{
resourceExecutingContext.ValueProviderFactories.RemoveAt(0);
});
var valueProviderFactory1 = Mock.Of<IValueProviderFactory>();
var valueProviderFactory2 = Mock.Of<IValueProviderFactory>();
var valueProviderFactories = new List<IValueProviderFactory>();
valueProviderFactories.Add(valueProviderFactory1);
valueProviderFactories.Add(valueProviderFactory2);
var invoker = CreateInvoker(
new IFilterMetadata[] { resourceFilter.Object }, valueProviderFactories: valueProviderFactories);
// Act
await invoker.InvokeAsync();
// Assert
var controllerContext = invoker.ControllerFactory.ControllerContext;
Assert.NotNull(controllerContext);
Assert.Equal(1, controllerContext.ValueProviderFactories.Count);
Assert.Same(valueProviderFactory2, controllerContext.ValueProviderFactories[0]);
}
[Fact]
public async Task MaxAllowedErrorsIsSet_BeforeCallingAuthorizationFilter()
{
@ -2038,8 +2098,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
.Verifiable();
var invoker = CreateInvoker(
new[] { filter.Object },
nameof(TestController.EchoWithException),
new[] { filter.Object },
nameof(TestController.EchoWithException),
new Dictionary<string, object>() { { "input", inputString } });
// Act & Assert
@ -2061,8 +2121,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
.Verifiable();
var invoker = CreateInvoker(
new[] { filter.Object },
nameof(TestController.EchoWithDefaultValue),
new[] { filter.Object },
nameof(TestController.EchoWithDefaultValue),
new Dictionary<string, object>());
// Act
@ -2088,7 +2148,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
.Verifiable();
var invoker = CreateInvoker(
new[] { filter.Object },
new[] { filter.Object },
nameof(TestController.EchoWithDefaultValue),
new Dictionary<string, object>() { { "input", inputString } });
@ -2360,18 +2420,80 @@ namespace Microsoft.AspNetCore.Mvc.Internal
+ "'.");
}
[Fact]
public async Task Invoke_UsesDefaultValuesIfNotBound()
{
// Arrange
var actionDescriptor = new ControllerActionDescriptor
{
ControllerTypeInfo = typeof(TestController).GetTypeInfo(),
BoundProperties = new List<ParameterDescriptor>(),
MethodInfo = typeof(TestController).GetTypeInfo()
.DeclaredMethods
.First(m => m.Name.Equals("ActionMethodWithDefaultValues", StringComparison.Ordinal)),
Parameters = new List<ParameterDescriptor>
{
new ParameterDescriptor
{
Name = "value",
ParameterType = typeof(int),
BindingInfo = new BindingInfo(),
}
},
FilterDescriptors = new List<FilterDescriptor>()
};
var context = new Mock<HttpContext>();
context.SetupGet(c => c.Items)
.Returns(new Dictionary<object, object>());
context.Setup(c => c.RequestServices.GetService(typeof(ILoggerFactory)))
.Returns(new NullLoggerFactory());
var actionContext = new ActionContext(context.Object, new RouteData(), actionDescriptor);
var controllerFactory = new Mock<IControllerFactory>();
controllerFactory.Setup(c => c.CreateController(It.IsAny<ControllerContext>()))
.Returns(new TestController());
var metadataProvider = new EmptyModelMetadataProvider();
var argumentBinder = new ControllerArgumentBinder(
metadataProvider,
TestModelBinderFactory.CreateDefault(metadataProvider),
new DefaultObjectValidator(metadataProvider, new IModelValidatorProvider[0]));
var invoker = new ControllerActionInvoker(
CreateFilterCache(),
controllerFactory.Object,
argumentBinder,
new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(),
new DiagnosticListener("Microsoft.AspNetCore"),
actionContext,
new IValueProviderFactory[0],
200);
// Act
await invoker.InvokeAsync();
// Assert
Assert.Equal(5, context.Object.Items["Result"]);
}
private TestControllerActionInvoker CreateInvoker(
IFilterMetadata filter,
bool actionThrows = false,
int maxAllowedErrorsInModelState = 200)
int maxAllowedErrorsInModelState = 200,
List<IValueProviderFactory> valueProviderFactories = null)
{
return CreateInvoker(new[] { filter }, actionThrows, maxAllowedErrorsInModelState);
return CreateInvoker(new[] { filter }, actionThrows, maxAllowedErrorsInModelState, valueProviderFactories);
}
private TestControllerActionInvoker CreateInvoker(
IFilterMetadata[] filters,
bool actionThrows = false,
int maxAllowedErrorsInModelState = 200)
int maxAllowedErrorsInModelState = 200,
List<IValueProviderFactory> valueProviderFactories = null)
{
var actionDescriptor = new ControllerActionDescriptor()
{
@ -2381,15 +2503,18 @@ namespace Microsoft.AspNetCore.Mvc.Internal
if (actionThrows)
{
actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod(nameof(ControllerActionInvokerTest.ThrowingActionMethod));
actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod(
nameof(ControllerActionInvokerTest.ThrowingActionMethod));
}
else
{
actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod(nameof(ControllerActionInvokerTest.ActionMethod));
actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod(
nameof(ControllerActionInvokerTest.ActionMethod));
}
actionDescriptor.ControllerTypeInfo = typeof(ControllerActionInvokerTest).GetTypeInfo();
return CreateInvoker(filters, actionDescriptor, null, null, maxAllowedErrorsInModelState);
return CreateInvoker(
filters, actionDescriptor, null, null, maxAllowedErrorsInModelState, valueProviderFactories);
}
private TestControllerActionInvoker CreateInvoker(
@ -2417,7 +2542,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
ControllerActionDescriptor actionDescriptor,
IControllerArgumentBinder controllerArgumentBinder,
object controller,
int maxAllowedErrorsInModelState = 200)
int maxAllowedErrorsInModelState = 200,
List<IValueProviderFactory> valueProviderFactories = null)
{
var httpContext = new Mock<HttpContext>(MockBehavior.Loose);
@ -2512,6 +2638,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
.SetupGet(fp => fp.Order)
.Returns(-1000);
if (valueProviderFactories == null)
{
valueProviderFactories = new List<IValueProviderFactory>();
}
var invoker = new TestControllerActionInvoker(
new[] { filterProvider.Object },
new MockControllerFactory(controller ?? this),
@ -2519,72 +2650,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(),
new DiagnosticListener("Microsoft.AspNetCore"),
actionContext,
new IValueProviderFactory[0],
valueProviderFactories.AsReadOnly(),
maxAllowedErrorsInModelState);
return invoker;
}
[Fact]
public async Task Invoke_UsesDefaultValuesIfNotBound()
{
// Arrange
var actionDescriptor = new ControllerActionDescriptor
{
ControllerTypeInfo = typeof(TestController).GetTypeInfo(),
BoundProperties = new List<ParameterDescriptor>(),
MethodInfo = typeof(TestController).GetTypeInfo()
.DeclaredMethods
.First(m => m.Name.Equals("ActionMethodWithDefaultValues", StringComparison.Ordinal)),
Parameters = new List<ParameterDescriptor>
{
new ParameterDescriptor
{
Name = "value",
ParameterType = typeof(int),
BindingInfo = new BindingInfo(),
}
},
FilterDescriptors = new List<FilterDescriptor>()
};
var context = new Mock<HttpContext>();
context.SetupGet(c => c.Items)
.Returns(new Dictionary<object, object>());
context.Setup(c => c.RequestServices.GetService(typeof(ILoggerFactory)))
.Returns(new NullLoggerFactory());
var actionContext = new ActionContext(context.Object, new RouteData(), actionDescriptor);
var controllerFactory = new Mock<IControllerFactory>();
controllerFactory.Setup(c => c.CreateController(It.IsAny<ControllerContext>()))
.Returns(new TestController());
var metadataProvider = new EmptyModelMetadataProvider();
var argumentBinder = new ControllerArgumentBinder(
metadataProvider,
TestModelBinderFactory.CreateDefault(metadataProvider),
new DefaultObjectValidator(metadataProvider, new IModelValidatorProvider[0]));
var invoker = new ControllerActionInvoker(
CreateFilterCache(),
controllerFactory.Object,
argumentBinder,
new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(),
new DiagnosticListener("Microsoft.AspNetCore"),
actionContext,
new IValueProviderFactory[0],
200);
// Act
await invoker.InvokeAsync();
// Assert
Assert.Equal(5, context.Object.Items["Result"]);
}
public IActionResult ActionMethod()
{
return _result;
@ -2785,8 +2855,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
public bool ReleaseCalled { get; private set; }
public ControllerContext ControllerContext { get; private set; }
public object CreateController(ControllerContext context)
{
ControllerContext = context;
CreateCalled = true;
return _controller;
}
@ -2886,6 +2959,5 @@ namespace Microsoft.AspNetCore.Mvc.Internal
return TaskCache.CompletedTask;
}
}
}
}

View File

@ -2,6 +2,7 @@
// 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.Linq;
using System.Net;
using System.Net.Http;
@ -106,7 +107,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
public async Task CanAuthorize(string testAction)
{
// Arrange & Act
var response = await Client.GetAsync("http://localhost/AuthorizeUser/"+testAction);
var response = await Client.GetAsync("http://localhost/AuthorizeUser/" + testAction);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
@ -509,5 +510,31 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal("application/json", response.Content.Headers.ContentType.MediaType);
Assert.Equal("\"someValue\"", await response.Content.ReadAsStringAsync());
}
[Fact]
public async Task ResourceFilter_RemovingValueProviderFactoriesForAnAction_DoesNotAffectOtherActions()
{
// Request to an action which does NOT expect form value model binding
// Arrange & Act
var response = await Client.PostAsync(
"http://localhost/ResourceFilter/FormValueModelBinding_Disabled",
new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("SampleInt", "10") }));
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("text/plain", response.Content.Headers.ContentType.MediaType);
Assert.Equal("Data:0", await response.Content.ReadAsStringAsync());
// Request to an action which expects form value model binding
// Arrange & Act
response = await Client.PostAsync(
"http://localhost/ResourceFilter/FormValueModelBinding_Enabled",
new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("SampleInt", "10") }));
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("text/plain", response.Content.Headers.ContentType.MediaType);
Assert.Equal("Data:10", await response.Content.ReadAsStringAsync());
}
}
}

View File

@ -1,10 +1,12 @@
// 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.Collections.Generic;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Routing;
using Xunit;
@ -43,7 +45,8 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
{
return new ResourceExecutingContext(
CreateActionContext(),
filters);
filters,
new List<IValueProviderFactory>());
}
private static ActionContext CreateActionContext()

View File

@ -28,6 +28,29 @@ namespace FiltersWebSite.Controllers
return "NeverGetsExecuted";
}
[HttpPost]
public IActionResult FormValueModelBinding_Enabled(DummyClass dc)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
return Ok("Data:" + dc?.SampleInt);
}
[HttpPost]
[DisableFormValueModelBinding]
public IActionResult FormValueModelBinding_Disabled(DummyClass dc)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
return Ok("Data:" + dc?.SampleInt);
}
private class ShortCircuitWithFormatterAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuted(ResourceExecutedContext context)

View File

@ -0,0 +1,38 @@
// 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.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace FiltersWebSite
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var formValueProviderFactory = context.ValueProviderFactories
.OfType<FormValueProviderFactory>()
.FirstOrDefault();
if (formValueProviderFactory != null)
{
context.ValueProviderFactories.Remove(formValueProviderFactory);
}
var jqueryFormValueProviderFactory = context.ValueProviderFactories
.OfType<JQueryFormValueProviderFactory>()
.FirstOrDefault();
if (jqueryFormValueProviderFactory != null)
{
context.ValueProviderFactories.Remove(jqueryFormValueProviderFactory);
}
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
}