diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionContext.cs b/src/Microsoft.AspNet.Mvc.Core/ActionContext.cs
index 79de5fffee..ff754b235c 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ActionContext.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ActionContext.cs
@@ -40,12 +40,6 @@ namespace Microsoft.AspNet.Mvc
public ActionDescriptor ActionDescriptor { get; private set; }
- ///
- /// Input formatters associated with this context.
- /// The formatters are populated only after IInputFormattersProvider runs.
- ///
- public IList InputFormatters { get; set; }
-
///
/// The controller is available only after the controller factory runs.
///
diff --git a/src/Microsoft.AspNet.Mvc.Core/Controller.cs b/src/Microsoft.AspNet.Mvc.Core/Controller.cs
index c5f8b85b62..1daedbbd46 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Controller.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Controller.cs
@@ -92,10 +92,13 @@ namespace Microsoft.AspNet.Mvc
public ActionContext ActionContext { get; set; }
[Activate]
- public IUrlHelper Url { get; set; }
+ public ActionBindingContext BindingContext { get; set; }
[Activate]
- public IActionBindingContextProvider BindingContextProvider { get; set; }
+ public IModelMetadataProvider MetadataProvider { get; set; }
+
+ [Activate]
+ public IUrlHelper Url { get; set; }
public IPrincipal User
{
@@ -701,7 +704,7 @@ namespace Microsoft.AspNet.Mvc
[NonAction]
public virtual CreatedAtActionResult CreatedAtAction(string actionName, object routeValues, object value)
{
- return CreatedAtAction(actionName, controllerName: null, routeValues: routeValues, value: value);
+ return CreatedAtAction(actionName, controllerName: null, routeValues: routeValues, value: value);
}
///
@@ -810,7 +813,7 @@ namespace Microsoft.AspNet.Mvc
}
///
- /// Updates the specified instance using values from the controller's current
+ /// Updates the specified instance using values from the controller's current
/// and a .
///
/// The type of the model object.
@@ -823,19 +826,19 @@ namespace Microsoft.AspNet.Mvc
[NotNull] string prefix)
where TModel : class
{
- if (BindingContextProvider == null)
+ if (BindingContext == null)
{
- var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider),
- GetType().FullName);
+ var message = Resources.FormatPropertyOfTypeCannotBeNull(
+ nameof(BindingContext),
+ typeof(Controller).FullName);
throw new InvalidOperationException(message);
}
- var bindingContext = await BindingContextProvider.GetActionBindingContextAsync(ActionContext);
- return await TryUpdateModelAsync(model, prefix, bindingContext.ValueProvider);
+ return await TryUpdateModelAsync(model, prefix, BindingContext.ValueProvider);
}
///
- /// Updates the specified instance using the and a
+ /// Updates the specified instance using the and a
/// .
///
/// The type of the model object.
@@ -850,33 +853,34 @@ namespace Microsoft.AspNet.Mvc
[NotNull] IValueProvider valueProvider)
where TModel : class
{
- if (BindingContextProvider == null)
+ if (BindingContext == null)
{
- var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider),
- GetType().FullName);
+ var message = Resources.FormatPropertyOfTypeCannotBeNull(
+ nameof(BindingContext),
+ typeof(Controller).FullName);
throw new InvalidOperationException(message);
}
- var bindingContext = await BindingContextProvider.GetActionBindingContextAsync(ActionContext);
- return await ModelBindingHelper.TryUpdateModelAsync(model,
- prefix,
- ActionContext.HttpContext,
- ModelState,
- bindingContext.MetadataProvider,
- bindingContext.ModelBinder,
- valueProvider,
- bindingContext.ValidatorProvider);
+ return await ModelBindingHelper.TryUpdateModelAsync(
+ model,
+ prefix,
+ ActionContext.HttpContext,
+ ModelState,
+ MetadataProvider,
+ BindingContext.ModelBinder,
+ valueProvider,
+ BindingContext.ValidatorProvider);
}
///
- /// Updates the specified instance using values from the controller's current
+ /// Updates the specified instance using values from the controller's current
/// and a .
///
/// The type of the model object.
/// The model instance to update.
/// The prefix to use when looking up values in the current .
///
- /// (s) which represent top-level properties
+ /// (s) which represent top-level properties
/// which need to be included for the current model.
/// A that on completion returns true if the update is successful
[NonAction]
@@ -886,27 +890,28 @@ namespace Microsoft.AspNet.Mvc
[NotNull] params Expression>[] includeExpressions)
where TModel : class
{
- if (BindingContextProvider == null)
+ if (BindingContext == null)
{
- var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider),
- GetType().FullName);
+ var message = Resources.FormatPropertyOfTypeCannotBeNull(
+ nameof(BindingContext),
+ typeof(Controller).FullName);
throw new InvalidOperationException(message);
}
- var bindingContext = await BindingContextProvider.GetActionBindingContextAsync(ActionContext);
- return await ModelBindingHelper.TryUpdateModelAsync(model,
- prefix,
- ActionContext.HttpContext,
- ModelState,
- bindingContext.MetadataProvider,
- bindingContext.ModelBinder,
- bindingContext.ValueProvider,
- bindingContext.ValidatorProvider,
- includeExpressions);
+ return await ModelBindingHelper.TryUpdateModelAsync(
+ model,
+ prefix,
+ ActionContext.HttpContext,
+ ModelState,
+ MetadataProvider,
+ BindingContext.ModelBinder,
+ BindingContext.ValueProvider,
+ BindingContext.ValidatorProvider,
+ includeExpressions);
}
///
- /// Updates the specified instance using values from the controller's current
+ /// Updates the specified instance using values from the controller's current
/// and a .
///
/// The type of the model object.
@@ -922,27 +927,28 @@ namespace Microsoft.AspNet.Mvc
[NotNull] Func predicate)
where TModel : class
{
- if (BindingContextProvider == null)
+ if (BindingContext == null)
{
- var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider),
- GetType().FullName);
+ var message = Resources.FormatPropertyOfTypeCannotBeNull(
+ nameof(BindingContext),
+ typeof(Controller).FullName);
throw new InvalidOperationException(message);
}
- var bindingContext = await BindingContextProvider.GetActionBindingContextAsync(ActionContext);
- return await ModelBindingHelper.TryUpdateModelAsync(model,
- prefix,
- ActionContext.HttpContext,
- ModelState,
- bindingContext.MetadataProvider,
- bindingContext.ModelBinder,
- bindingContext.ValueProvider,
- bindingContext.ValidatorProvider,
- predicate);
+ return await ModelBindingHelper.TryUpdateModelAsync(
+ model,
+ prefix,
+ ActionContext.HttpContext,
+ ModelState,
+ MetadataProvider,
+ BindingContext.ModelBinder,
+ BindingContext.ValueProvider,
+ BindingContext.ValidatorProvider,
+ predicate);
}
///
- /// Updates the specified instance using the and a
+ /// Updates the specified instance using the and a
/// .
///
/// The type of the model object.
@@ -950,7 +956,7 @@ namespace Microsoft.AspNet.Mvc
/// The prefix to use when looking up values in the
///
/// The used for looking up values.
- /// (s) which represent top-level properties
+ /// (s) which represent top-level properties
/// which need to be included for the current model.
/// A that on completion returns true if the update is successful
[NonAction]
@@ -961,27 +967,28 @@ namespace Microsoft.AspNet.Mvc
[NotNull] params Expression>[] includeExpressions)
where TModel : class
{
- if (BindingContextProvider == null)
+ if (BindingContext == null)
{
- var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider),
- GetType().FullName);
+ var message = Resources.FormatPropertyOfTypeCannotBeNull(
+ nameof(BindingContext),
+ typeof(Controller).FullName);
throw new InvalidOperationException(message);
}
- var bindingContext = await BindingContextProvider.GetActionBindingContextAsync(ActionContext);
- return await ModelBindingHelper.TryUpdateModelAsync(model,
- prefix,
- ActionContext.HttpContext,
- ModelState,
- bindingContext.MetadataProvider,
- bindingContext.ModelBinder,
- valueProvider,
- bindingContext.ValidatorProvider,
- includeExpressions);
+ return await ModelBindingHelper.TryUpdateModelAsync(
+ model,
+ prefix,
+ ActionContext.HttpContext,
+ ModelState,
+ MetadataProvider,
+ BindingContext.ModelBinder,
+ valueProvider,
+ BindingContext.ValidatorProvider,
+ includeExpressions);
}
///
- /// Updates the specified instance using the and a
+ /// Updates the specified instance using the and a
/// .
///
/// The type of the model object.
@@ -999,23 +1006,24 @@ namespace Microsoft.AspNet.Mvc
[NotNull] Func predicate)
where TModel : class
{
- if (BindingContextProvider == null)
+ if (BindingContext == null)
{
- var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider),
- GetType().FullName);
+ var message = Resources.FormatPropertyOfTypeCannotBeNull(
+ nameof(BindingContext),
+ typeof(Controller).FullName);
throw new InvalidOperationException(message);
}
- var bindingContext = await BindingContextProvider.GetActionBindingContextAsync(ActionContext);
- return await ModelBindingHelper.TryUpdateModelAsync(model,
- prefix,
- ActionContext.HttpContext,
- ModelState,
- bindingContext.MetadataProvider,
- bindingContext.ModelBinder,
- valueProvider,
- bindingContext.ValidatorProvider,
- predicate);
+ return await ModelBindingHelper.TryUpdateModelAsync(
+ model,
+ prefix,
+ ActionContext.HttpContext,
+ ModelState,
+ MetadataProvider,
+ BindingContext.ModelBinder,
+ valueProvider,
+ BindingContext.ValidatorProvider,
+ predicate);
}
public void Dispose()
diff --git a/src/Microsoft.AspNet.Mvc.Core/ControllerActionInvoker.cs b/src/Microsoft.AspNet.Mvc.Core/ControllerActionInvoker.cs
index 4d1b6ae9e9..26458fa557 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ControllerActionInvoker.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ControllerActionInvoker.cs
@@ -3,9 +3,10 @@
using System;
using System.Collections.Generic;
-using System.Linq;
+using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core;
+using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc
@@ -14,21 +15,36 @@ namespace Microsoft.AspNet.Mvc
{
private readonly ControllerActionDescriptor _descriptor;
private readonly IControllerFactory _controllerFactory;
- private readonly IInputFormattersProvider _inputFormattersProvider;
- private readonly IControllerActionArgumentBinder _actionInvocationProvider;
+ private readonly IControllerActionArgumentBinder _argumentBinder;
- public ControllerActionInvoker([NotNull] ActionContext actionContext,
- [NotNull] INestedProviderManager filterProvider,
- [NotNull] IControllerFactory controllerFactory,
- [NotNull] ControllerActionDescriptor descriptor,
- [NotNull] IInputFormattersProvider inputFormattersProvider,
- [NotNull] IControllerActionArgumentBinder controllerActionArgumentBinder)
- : base(actionContext, filterProvider)
+ public ControllerActionInvoker(
+ [NotNull] ActionContext actionContext,
+ [NotNull] INestedProviderManager filterProvider,
+ [NotNull] IControllerFactory controllerFactory,
+ [NotNull] ControllerActionDescriptor descriptor,
+ [NotNull] IModelMetadataProvider modelMetadataProvider,
+ [NotNull] IInputFormattersProvider inputFormatterProvider,
+ [NotNull] IInputFormatterSelector inputFormatterSelector,
+ [NotNull] IControllerActionArgumentBinder controllerActionArgumentBinder,
+ [NotNull] IModelBinderProvider modelBinderProvider,
+ [NotNull] IModelValidatorProviderProvider modelValidatorProviderProvider,
+ [NotNull] IValueProviderFactoryProvider valueProviderFactoryProvider,
+ [NotNull] IScopedInstance actionBindingContextAccessor)
+ : base(
+ actionContext,
+ filterProvider,
+ modelMetadataProvider,
+ inputFormatterProvider,
+ inputFormatterSelector,
+ modelBinderProvider,
+ modelValidatorProviderProvider,
+ valueProviderFactoryProvider,
+ actionBindingContextAccessor)
{
_descriptor = descriptor;
_controllerFactory = controllerFactory;
- _inputFormattersProvider = inputFormattersProvider;
- _actionInvocationProvider = controllerActionArgumentBinder;
+ _argumentBinder = controllerActionArgumentBinder;
+
if (descriptor.MethodInfo == null)
{
throw new ArgumentException(
@@ -40,12 +56,13 @@ namespace Microsoft.AspNet.Mvc
public async override Task InvokeAsync()
{
+ // The binding context is used in activation
+ Debug.Assert(ActionBindingContext != null);
var controller = _controllerFactory.CreateController(ActionContext);
+
try
{
ActionContext.Controller = controller;
- ActionContext.InputFormatters = _inputFormattersProvider.InputFormatters
- .ToList();
await base.InvokeAsync();
}
finally
@@ -68,9 +85,11 @@ namespace Microsoft.AspNet.Mvc
return actionResult;
}
- protected override Task> GetActionArgumentsAsync(ActionContext context)
+ protected override Task> GetActionArgumentsAsync(
+ ActionContext context,
+ ActionBindingContext bindingContext)
{
- return _actionInvocationProvider.GetActionArgumentsAsync(context);
+ return _argumentBinder.GetActionArgumentsAsync(context, bindingContext);
}
// Marking as internal for Unit Testing purposes.
diff --git a/src/Microsoft.AspNet.Mvc.Core/ControllerActionInvokerProvider.cs b/src/Microsoft.AspNet.Mvc.Core/ControllerActionInvokerProvider.cs
index b8b120f901..ea20812a79 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ControllerActionInvokerProvider.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ControllerActionInvokerProvider.cs
@@ -2,26 +2,46 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc
{
public class ControllerActionInvokerProvider : IActionInvokerProvider
{
+ private readonly IControllerActionArgumentBinder _argumentBinder;
private readonly IControllerFactory _controllerFactory;
- private readonly IInputFormattersProvider _inputFormattersProvider;
private readonly INestedProviderManager _filterProvider;
- private readonly IControllerActionArgumentBinder _actionInvocationInfoProvider;
+ private readonly IInputFormattersProvider _inputFormattersProvider;
+ private readonly IInputFormatterSelector _inputFormatterSelector;
+ private readonly IModelBinderProvider _modelBinderProvider;
+ private readonly IModelMetadataProvider _modelMetadataProvider;
+ private readonly IModelValidatorProviderProvider _modelValidationProviderProvider;
+ private readonly IValueProviderFactoryProvider _valueProviderFactoryProvider;
+ private readonly IScopedInstance _actionBindingContextAccessor;
- public ControllerActionInvokerProvider(IControllerFactory controllerFactory,
- IInputFormattersProvider inputFormattersProvider,
- INestedProviderManager filterProvider,
- IControllerActionArgumentBinder actionInvocationInfoProvider)
+ public ControllerActionInvokerProvider(
+ IControllerFactory controllerFactory,
+ IInputFormattersProvider inputFormattersProvider,
+ INestedProviderManager filterProvider,
+ IControllerActionArgumentBinder argumentBinder,
+ IModelMetadataProvider modelMetadataProvider,
+ IInputFormatterSelector inputFormatterSelector,
+ IModelBinderProvider modelBinderProvider,
+ IModelValidatorProviderProvider modelValidationProviderProvider,
+ IValueProviderFactoryProvider valueProviderFactoryProvider,
+ IScopedInstance actionBindingContextAccessor)
{
_controllerFactory = controllerFactory;
_inputFormattersProvider = inputFormattersProvider;
_filterProvider = filterProvider;
- _actionInvocationInfoProvider = actionInvocationInfoProvider;
+ _argumentBinder = argumentBinder;
+ _modelMetadataProvider = modelMetadataProvider;
+ _modelBinderProvider = modelBinderProvider;
+ _inputFormatterSelector = inputFormatterSelector;
+ _modelValidationProviderProvider = modelValidationProviderProvider;
+ _valueProviderFactoryProvider = valueProviderFactoryProvider;
+ _actionBindingContextAccessor = actionBindingContextAccessor;
}
public int Order
@@ -40,8 +60,14 @@ namespace Microsoft.AspNet.Mvc
_filterProvider,
_controllerFactory,
actionDescriptor,
+ _modelMetadataProvider,
_inputFormattersProvider,
- _actionInvocationInfoProvider);
+ _inputFormatterSelector,
+ _argumentBinder,
+ _modelBinderProvider,
+ _modelValidationProviderProvider,
+ _valueProviderFactoryProvider,
+ _actionBindingContextAccessor);
}
callNext();
diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs
index 8dbf3e9cbb..fbdc621602 100644
--- a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs
@@ -15,18 +15,17 @@ namespace Microsoft.AspNet.Mvc
///
public class DefaultControllerActionArgumentBinder : IControllerActionArgumentBinder
{
- private readonly IActionBindingContextProvider _bindingContextProvider;
+ private readonly IModelMetadataProvider _modelMetadataProvider;
- public DefaultControllerActionArgumentBinder(IActionBindingContextProvider bindingContextProvider)
+ public DefaultControllerActionArgumentBinder(IModelMetadataProvider modelMetadataProvider)
{
- _bindingContextProvider = bindingContextProvider;
+ _modelMetadataProvider = modelMetadataProvider;
}
- public async Task> GetActionArgumentsAsync(ActionContext actionContext)
+ public async Task> GetActionArgumentsAsync(
+ ActionContext actionContext,
+ ActionBindingContext actionBindingContext)
{
- var actionBindingContext = await _bindingContextProvider.GetActionBindingContextAsync(actionContext);
- var metadataProvider = actionBindingContext.MetadataProvider;
-
var actionDescriptor = actionContext.ActionDescriptor as ControllerActionDescriptor;
if (actionDescriptor == null)
{
@@ -39,7 +38,7 @@ namespace Microsoft.AspNet.Mvc
var parameterMetadata = new List();
foreach (var parameter in actionDescriptor.Parameters)
{
- var metadata = metadataProvider.GetMetadataForParameter(
+ var metadata = _modelMetadataProvider.GetMetadataForParameter(
modelAccessor: null,
methodInfo: actionDescriptor.MethodInfo,
parameterName: parameter.Name);
@@ -52,7 +51,7 @@ namespace Microsoft.AspNet.Mvc
}
var actionArguments = new Dictionary(StringComparer.Ordinal);
- await PopulateArgumentAsync(actionBindingContext, actionArguments, parameterMetadata);
+ await PopulateArgumentAsync(actionContext, actionBindingContext, actionArguments, parameterMetadata);
return actionArguments;
}
@@ -71,25 +70,25 @@ namespace Microsoft.AspNet.Mvc
}
private async Task PopulateArgumentAsync(
- ActionBindingContext actionBindingContext,
+ ActionContext actionContext,
+ ActionBindingContext bindingContext,
IDictionary arguments,
IEnumerable parameterMetadata)
{
var operationBindingContext = new OperationBindingContext
{
- ModelBinder = actionBindingContext.ModelBinder,
- ValidatorProvider = actionBindingContext.ValidatorProvider,
- MetadataProvider = actionBindingContext.MetadataProvider,
- HttpContext = actionBindingContext.ActionContext.HttpContext,
- ValueProvider = actionBindingContext.ValueProvider,
+ ModelBinder = bindingContext.ModelBinder,
+ ValidatorProvider = bindingContext.ValidatorProvider,
+ MetadataProvider = _modelMetadataProvider,
+ HttpContext = actionContext.HttpContext,
+ ValueProvider = bindingContext.ValueProvider,
};
foreach (var parameter in parameterMetadata)
{
var parameterType = parameter.ModelType;
- var modelBindingContext =
- GetModelBindingContext(parameter, actionBindingContext, operationBindingContext);
- if (await actionBindingContext.ModelBinder.BindModelAsync(modelBindingContext))
+ var modelBindingContext = GetModelBindingContext(parameter, actionContext, operationBindingContext);
+ if (await bindingContext.ModelBinder.BindModelAsync(modelBindingContext))
{
arguments[parameter.PropertyName] = modelBindingContext.Model;
}
@@ -98,18 +97,18 @@ namespace Microsoft.AspNet.Mvc
internal static ModelBindingContext GetModelBindingContext(
ModelMetadata modelMetadata,
- ActionBindingContext actionBindingContext,
+ ActionContext actionContext,
OperationBindingContext operationBindingContext)
{
var modelBindingContext = new ModelBindingContext
{
ModelName = modelMetadata.BinderModelName ?? modelMetadata.PropertyName,
ModelMetadata = modelMetadata,
- ModelState = actionBindingContext.ActionContext.ModelState,
+ ModelState = actionContext.ModelState,
// Fallback only if there is no explicit model name set.
FallbackToEmptyPrefix = modelMetadata.BinderModelName == null,
- ValueProvider = actionBindingContext.ValueProvider,
+ ValueProvider = operationBindingContext.ValueProvider,
OperationBindingContext = operationBindingContext,
};
diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActivator.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActivator.cs
index eb39d84e01..c27c1b9e77 100644
--- a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActivator.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActivator.cs
@@ -75,6 +75,15 @@ namespace Microsoft.AspNet.Mvc
serviceProvider.GetRequiredService(),
context.ModelState);
}
+ },
+ {
+ typeof(ActionBindingContext),
+ (context) =>
+ {
+ var serviceProvider = context.HttpContext.RequestServices;
+ var accessor = serviceProvider.GetRequiredService>();
+ return accessor.Value;
+ }
}
};
return dictionary;
diff --git a/src/Microsoft.AspNet.Mvc.Core/FilterActionInvoker.cs b/src/Microsoft.AspNet.Mvc.Core/FilterActionInvoker.cs
index bba19c8bc3..fa7dbbb171 100644
--- a/src/Microsoft.AspNet.Mvc.Core/FilterActionInvoker.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/FilterActionInvoker.cs
@@ -8,21 +8,32 @@ using System.Linq;
using System.Runtime.ExceptionServices;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core;
+using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc
{
public abstract class FilterActionInvoker : IActionInvoker
{
+ private readonly IModelMetadataProvider _modelMetadataProvider;
private readonly INestedProviderManager _filterProvider;
+ private readonly IInputFormattersProvider _inputFormatterProvider;
+ private readonly IInputFormatterSelector _inputFormatterSelector;
+ private readonly IModelBinderProvider _modelBinderProvider;
+ private readonly IModelValidatorProviderProvider _modelValidatorProviderProvider;
+ private readonly IValueProviderFactoryProvider _valueProviderFactoryProvider;
+ private readonly IScopedInstance _actionBindingContextAccessor;
private IFilter[] _filters;
private FilterCursor _cursor;
- private ExceptionContext _exceptionContext;
-
private AuthorizationContext _authorizationContext;
+ private ResourceExecutingContext _resourceExecutingContext;
+ private ResourceExecutedContext _resourceExecutedContext;
+
+ private ExceptionContext _exceptionContext;
+
private ActionExecutingContext _actionExecutingContext;
private ActionExecutedContext _actionExecutedContext;
@@ -31,17 +42,48 @@ namespace Microsoft.AspNet.Mvc
public FilterActionInvoker(
[NotNull] ActionContext actionContext,
- [NotNull] INestedProviderManager filterProvider)
+ [NotNull] INestedProviderManager filterProvider,
+ [NotNull] IModelMetadataProvider modelMetadataProvider,
+ [NotNull] IInputFormattersProvider inputFormatterProvider,
+ [NotNull] IInputFormatterSelector inputFormatterSelector,
+ [NotNull] IModelBinderProvider modelBinderProvider,
+ [NotNull] IModelValidatorProviderProvider modelValidatorProviderProvider,
+ [NotNull] IValueProviderFactoryProvider valueProviderFactoryProvider,
+ [NotNull] IScopedInstance actionBindingContextAccessor)
{
ActionContext = actionContext;
+
_filterProvider = filterProvider;
+ _modelMetadataProvider = modelMetadataProvider;
+ _inputFormatterProvider = inputFormatterProvider;
+ _inputFormatterSelector = inputFormatterSelector;
+ _modelBinderProvider = modelBinderProvider;
+ _modelValidatorProviderProvider = modelValidatorProviderProvider;
+ _valueProviderFactoryProvider = valueProviderFactoryProvider;
+ _actionBindingContextAccessor = actionBindingContextAccessor;
+
+ ActionBindingContext = new ActionBindingContext();
}
protected ActionContext ActionContext { get; private set; }
+ protected ActionBindingContext ActionBindingContext
+ {
+ get
+ {
+ return _actionBindingContextAccessor.Value;
+ }
+ private set
+ {
+ _actionBindingContextAccessor.Value = value;
+ }
+ }
+
protected abstract Task InvokeActionAsync(ActionExecutingContext actionExecutingContext);
- protected abstract Task> GetActionArgumentsAsync([NotNull] ActionContext context);
+ protected abstract Task> GetActionArgumentsAsync(
+ [NotNull] ActionContext context,
+ [NotNull] ActionBindingContext bindingContext);
public virtual async Task InvokeAsync()
{
@@ -59,38 +101,22 @@ namespace Microsoft.AspNet.Mvc
return;
}
- // >> ExceptionFilters >> ActionFilters >> Action
- await InvokeAllExceptionFiltersAsync();
+ await InvokeAllResourceFiltersAsync();
- // If Exception Filters provide a result, it's a short-circuit due to an exception.
- // We don't execute Result Filters around the result.
- Debug.Assert(_exceptionContext != null);
- if (_exceptionContext.Result != null)
+ // We've reached the end of resource filters. If there's an unhandled exception on the context then
+ // it should be thrown and middleware has a chance to handle it.
+ Debug.Assert(_resourceExecutedContext != null);
+ if (_resourceExecutedContext.Exception != null && !_resourceExecutedContext.ExceptionHandled)
{
- await _exceptionContext.Result.ExecuteResultAsync(ActionContext);
- }
- else if (_exceptionContext.Exception != null)
- {
- // If we get here, this means that we have an unhandled exception
- if (_exceptionContext.ExceptionDispatchInfo != null)
+ if (_resourceExecutedContext.ExceptionDispatchInfo == null)
{
- _exceptionContext.ExceptionDispatchInfo.Throw();
+ throw _resourceExecutedContext.Exception;
}
else
{
- throw _exceptionContext.Exception;
+ _resourceExecutedContext.ExceptionDispatchInfo.Throw();
}
}
- else
- {
- // We have a successful 'result' from the action or an Action Filter, so run
- // Result Filters.
- Debug.Assert(_actionExecutedContext != null);
- var result = _actionExecutedContext.Result;
-
- // >> ResultFilters >> (Result)
- await InvokeAllResultFiltersAsync(result);
- }
}
private IFilter[] GetFilters()
@@ -146,6 +172,148 @@ namespace Microsoft.AspNet.Mvc
}
}
+ private async Task InvokeAllResourceFiltersAsync()
+ {
+ _cursor.SetStage(FilterStage.ResourceFilters);
+
+ var context = new ResourceExecutingContext(ActionContext, _filters);
+
+ context.InputFormatters = new List(_inputFormatterProvider.InputFormatters);
+ context.ModelBinders = new List(_modelBinderProvider.ModelBinders);
+
+ context.ValidatorProviders = new List(
+ _modelValidatorProviderProvider.ModelValidatorProviders);
+
+ context.ValueProviderFactories = new List(
+ _valueProviderFactoryProvider.ValueProviderFactories);
+
+ _resourceExecutingContext = context;
+ await InvokeResourceFilterAsync();
+ }
+
+ private async Task InvokeResourceFilterAsync()
+ {
+ 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);
+ }
+
+ var item = _cursor.GetNextFilter();
+ try
+ {
+ if (item.FilterAsync != null)
+ {
+ await item.FilterAsync.OnResourceExecutionAsync(
+ _resourceExecutingContext,
+ InvokeResourceFilterAsync);
+
+ if (_resourceExecutedContext == null)
+ {
+ // If we get here then the filter didn't call 'next' indicating a short circuit
+ if (_resourceExecutingContext.Result != null)
+ {
+ await _resourceExecutingContext.Result.ExecuteResultAsync(ActionContext);
+ }
+
+ _resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters)
+ {
+ Canceled = true,
+ Result = _resourceExecutingContext.Result,
+ };
+ }
+ }
+ else if (item.Filter != null)
+ {
+ item.Filter.OnResourceExecuting(_resourceExecutingContext);
+
+ if (_resourceExecutingContext.Result != null)
+ {
+ // Short-circuited by setting a result.
+ await _resourceExecutingContext.Result.ExecuteResultAsync(ActionContext);
+
+ _resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters)
+ {
+ Canceled = true,
+ Result = _resourceExecutingContext.Result,
+ };
+ }
+ else
+ {
+ item.Filter.OnResourceExecuted(await InvokeResourceFilterAsync());
+ }
+ }
+ else
+ {
+ // We've reached the end of resource filters, so move on to exception filters.
+
+ // >> ExceptionFilters >> Model Binding >> ActionFilters >> Action
+ await InvokeAllExceptionFiltersAsync();
+
+ // If Exception Filters provide a result, it's a short-circuit due to an exception.
+ // We don't execute Result Filters around the result.
+ Debug.Assert(_exceptionContext != null);
+ if (_exceptionContext.Result != null)
+ {
+ // This means that exception filters returned a result to 'handle' an error.
+ // We're not interested in seeing the exception details since it was handled.
+ await _exceptionContext.Result.ExecuteResultAsync(ActionContext);
+
+ _resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters)
+ {
+ Result = _exceptionContext.Result,
+ };
+ }
+ else if (_exceptionContext.Exception != null)
+ {
+ // If we get here, this means that we have an unhandled exception.
+ // Exception filted didn't handle this, so send it on to resource filters.
+ _resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters);
+
+ // Preserve the stack trace if possible.
+ _resourceExecutedContext.Exception = _exceptionContext.Exception;
+ if (_exceptionContext.ExceptionDispatchInfo != null)
+ {
+ _resourceExecutedContext.ExceptionDispatchInfo = _exceptionContext.ExceptionDispatchInfo;
+ }
+ }
+ else
+ {
+ // We have a successful 'result' from the action or an Action Filter, so run
+ // Result Filters.
+ Debug.Assert(_actionExecutedContext != null);
+ var result = _actionExecutedContext.Result;
+
+ // >> ResultFilters >> (Result)
+ await InvokeAllResultFiltersAsync(result);
+
+ _resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters)
+ {
+ Result = _resultExecutedContext.Result,
+ };
+ }
+ }
+ }
+ catch (Exception exception)
+ {
+ _resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters)
+ {
+ ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception)
+ };
+ }
+
+ Debug.Assert(_resourceExecutedContext != null);
+ return _resourceExecutedContext;
+ }
+
private async Task InvokeAllExceptionFiltersAsync()
{
_cursor.SetStage(FilterStage.ExceptionFilters);
@@ -219,7 +387,25 @@ namespace Microsoft.AspNet.Mvc
private async Task InvokeAllActionFiltersAsync()
{
_cursor.SetStage(FilterStage.ActionFilters);
- var arguments = await GetActionArgumentsAsync(ActionContext);
+
+ Debug.Assert(_resourceExecutingContext != null);
+
+ Debug.Assert(ActionBindingContext != null);
+ ActionBindingContext.InputFormatters = _resourceExecutingContext.InputFormatters;
+ ActionBindingContext.ModelBinder = new CompositeModelBinder(_resourceExecutingContext.ModelBinders);
+ ActionBindingContext.ValidatorProvider = new CompositeModelValidatorProvider(
+ _resourceExecutingContext.ValidatorProviders);
+
+ var valueProviderFactoryContext = new ValueProviderFactoryContext(
+ ActionContext.HttpContext,
+ ActionContext.RouteData.Values);
+
+ ActionBindingContext.ValueProvider = CompositeValueProvider.Create(
+ _resourceExecutingContext.ValueProviderFactories,
+ valueProviderFactoryContext);
+
+ var arguments = await GetActionArgumentsAsync(ActionContext, ActionBindingContext);
+
_actionExecutingContext = new ActionExecutingContext(ActionContext, _filters, arguments);
await InvokeActionFilterAsync();
}
@@ -232,7 +418,7 @@ namespace Microsoft.AspNet.Mvc
// If we get here, it means that an async filter set a result AND called next(). This is forbidden.
var message = Resources.FormatAsyncActionFilter_InvalidShortCircuit(
typeof(IAsyncActionFilter).Name,
- "Result",
+ nameof(ActionExecutingContext.Result),
typeof(ActionExecutingContext).Name,
typeof(ActionExecutionDelegate).Name);
@@ -325,7 +511,7 @@ namespace Microsoft.AspNet.Mvc
// This is forbidden.
var message = Resources.FormatAsyncResultFilter_InvalidShortCircuit(
typeof(IAsyncResultFilter).Name,
- "Cancel",
+ nameof(ResultExecutingContext.Cancel),
typeof(ResultExecutingContext).Name,
typeof(ResultExecutionDelegate).Name);
@@ -423,8 +609,9 @@ namespace Microsoft.AspNet.Mvc
private enum FilterStage
{
Undefined,
- ExceptionFilters,
AuthorizationFilters,
+ ResourceFilters,
+ ExceptionFilters,
ActionFilters,
ActionMethod,
ResultFilters,
diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/IAsyncResourceFilter.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/IAsyncResourceFilter.cs
new file mode 100644
index 0000000000..c8a13bafb9
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Core/Filters/IAsyncResourceFilter.cs
@@ -0,0 +1,29 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNet.Mvc
+{
+ ///
+ /// A filter which surrounds execution of model binding, the action (and filters) and the action result
+ /// (and filters).
+ ///
+ public interface IAsyncResourceFilter : IFilter
+ {
+ ///
+ /// Executes the resource filter.
+ ///
+ /// The .
+ ///
+ /// The . Invoked to execute the next
+ /// resource filter, or the remainder of the pipeline.
+ ///
+ ///
+ /// A which will complete when the remainder of the pipeline completes.
+ ///
+ Task OnResourceExecutionAsync(
+ [NotNull] ResourceExecutingContext context,
+ [NotNull] ResourceExecutionDelegate next);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/IResourceFilter.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/IResourceFilter.cs
new file mode 100644
index 0000000000..8f543de61c
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Core/Filters/IResourceFilter.cs
@@ -0,0 +1,25 @@
+using System;
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNet.Mvc
+{
+ ///
+ /// A filter which surrounds execution of model binding, the action (and filters) and the action result
+ /// (and filters).
+ ///
+ public interface IResourceFilter : IFilter
+ {
+ ///
+ /// Executes the resource filter. Called before execution of the remainder of the pipeline.
+ ///
+ /// The .
+ void OnResourceExecuting([NotNull] ResourceExecutingContext context);
+
+ ///
+ /// Executes the resource filter. Called after execution of the remainder of the pipeline.
+ ///
+ /// The .
+ void OnResourceExecuted([NotNull] ResourceExecutedContext context);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/ResourceExecutedContext.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/ResourceExecutedContext.cs
new file mode 100644
index 0000000000..03aa619b05
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Core/Filters/ResourceExecutedContext.cs
@@ -0,0 +1,108 @@
+// Copyright (c) Microsoft Open Technologies, Inc. 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.Runtime.ExceptionServices;
+
+namespace Microsoft.AspNet.Mvc
+{
+ ///
+ /// A context for resource filters.
+ ///
+ public class ResourceExecutedContext : FilterContext
+ {
+ private Exception _exception;
+ private ExceptionDispatchInfo _exceptionDispatchInfo;
+
+ ///
+ /// Creates a new .
+ ///
+ /// The .
+ /// The list of instances.
+ public ResourceExecutedContext(ActionContext actionContext, IList filters)
+ : base(actionContext, filters)
+ {
+ }
+
+ ///
+ /// Gets or sets a value which indicates whether or not execution was canceled by a resource filter.
+ /// If true, then a resource filter short-circuted execution by setting
+ /// .
+ ///
+ public virtual bool Canceled { get; set; }
+
+ ///
+ /// Gets or set the current .
+ ///
+ ///
+ /// Setting or to null will treat
+ /// the exception as handled, and it will not be rethrown by the runtime.
+ ///
+ /// Setting to true will also mark the exception as handled.
+ ///
+ public virtual Exception Exception
+ {
+ get
+ {
+ if (_exception == null && _exceptionDispatchInfo != null)
+ {
+ return _exceptionDispatchInfo.SourceException;
+ }
+ else
+ {
+ return _exception;
+ }
+ }
+
+ set
+ {
+ _exceptionDispatchInfo = null;
+ _exception = value;
+ }
+ }
+
+ ///
+ /// Gets or set the current .
+ ///
+ ///
+ /// Setting or to null will treat
+ /// the exception as handled, and it will not be rethrown by the runtime.
+ ///
+ /// Setting to true will also mark the exception as handled.
+ ///
+ public virtual ExceptionDispatchInfo ExceptionDispatchInfo
+ {
+ get
+ {
+ return _exceptionDispatchInfo;
+ }
+
+ set
+ {
+ _exception = null;
+ _exceptionDispatchInfo = value;
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether or not the current has been handled.
+ ///
+ /// If false the will be rethrown by the runtime after resource filters
+ /// have executed.
+ ///
+ public virtual bool ExceptionHandled { get; set; }
+
+ ///
+ /// Gets or sets the result.
+ ///
+ ///
+ /// The may be provided by execution of the action itself or by another
+ /// filter.
+ ///
+ /// The has already been written to the response before being made available
+ /// to resource filters.
+ ///
+ public virtual IActionResult Result { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/ResourceExecutingContext.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/ResourceExecutingContext.cs
new file mode 100644
index 0000000000..3618d1dcdd
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Core/Filters/ResourceExecutingContext.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Mvc.ModelBinding;
+
+namespace Microsoft.AspNet.Mvc
+{
+ ///
+ /// A context for resource filters. Allows modification of services and values used for
+ /// model binding.
+ ///
+ public class ResourceExecutingContext : FilterContext
+ {
+ ///
+ /// Creates a new .
+ ///
+ /// The .
+ /// The list of instances.
+ public ResourceExecutingContext(ActionContext actionContext, IList filters)
+ : base(actionContext, filters)
+ {
+ }
+
+ ///
+ /// Gets or sets the list of instances used by model binding.
+ ///
+ public virtual IList InputFormatters { get; set; }
+
+ ///
+ /// Gets or sets the list of instances used by model binding.
+ ///
+ public virtual IList ModelBinders { get; set; }
+
+ ///
+ /// Gets or sets the result of the action to be executed.
+ ///
+ ///
+ /// Setting to a non-null value inside a resource filter will
+ /// short-circuit execution of additional resource filtes and the action itself.
+ ///
+ public virtual IActionResult Result { get; set; }
+
+ ///
+ /// Gets or sets the list of instances used by model binding.
+ ///
+ public IList ValueProviderFactories { get; set; }
+
+ ///
+ /// Gets or sets the list of instances used by model binding.
+ ///
+ public IList ValidatorProviders { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/ResourceExecutionDelegate.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/ResourceExecutionDelegate.cs
new file mode 100644
index 0000000000..773d369e22
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Core/Filters/ResourceExecutionDelegate.cs
@@ -0,0 +1,13 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNet.Mvc
+{
+ ///
+ /// A delegate which asyncronously returns a .
+ ///
+ /// A .
+ public delegate Task ResourceExecutionDelegate();
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/DefaultInputFormatterSelector.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/DefaultInputFormatterSelector.cs
index 3d335122e7..800a35203e 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Formatters/DefaultInputFormatterSelector.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/DefaultInputFormatterSelector.cs
@@ -1,14 +1,18 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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;
+
namespace Microsoft.AspNet.Mvc
{
public class DefaultInputFormatterSelector : IInputFormatterSelector
{
- public IInputFormatter SelectFormatter(InputFormatterContext context)
+
+ public IInputFormatter SelectFormatter(
+ IReadOnlyList inputFormatters,
+ InputFormatterContext context)
{
- var formatters = context.ActionContext.InputFormatters;
- foreach (var formatter in formatters)
+ foreach (var formatter in inputFormatters)
{
if (formatter.CanRead(context))
{
diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/IInputFormatterSelector.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/IInputFormatterSelector.cs
index 982a6d2c0a..3ed12169b6 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Formatters/IInputFormatterSelector.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/IInputFormatterSelector.cs
@@ -1,10 +1,14 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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;
+
namespace Microsoft.AspNet.Mvc
{
public interface IInputFormatterSelector
{
- IInputFormatter SelectFormatter(InputFormatterContext context);
+ IInputFormatter SelectFormatter(
+ [NotNull] IReadOnlyList inputFormatters,
+ [NotNull] InputFormatterContext context);
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Core/IControllerActionArgumentBinder.cs b/src/Microsoft.AspNet.Mvc.Core/IControllerActionArgumentBinder.cs
index 51dc2ed0e8..a9d1610ea7 100644
--- a/src/Microsoft.AspNet.Mvc.Core/IControllerActionArgumentBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/IControllerActionArgumentBinder.cs
@@ -16,6 +16,9 @@ namespace Microsoft.AspNet.Mvc
/// which can be used to invoke the action.
///
/// The action context assoicated with the current action.
- Task> GetActionArgumentsAsync([NotNull] ActionContext context);
+ /// The .
+ Task> GetActionArgumentsAsync(
+ [NotNull] ActionContext context,
+ [NotNull] ActionBindingContext bindingContext);
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs
index 16de4cbe5b..aff83a06c5 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.ModelBinding;
@@ -15,16 +16,19 @@ namespace Microsoft.AspNet.Mvc
public class BodyModelBinder : MetadataAwareBinder
{
private readonly ActionContext _actionContext;
+ private readonly IScopedInstance _bindingContext;
private readonly IInputFormatterSelector _formatterSelector;
private readonly IBodyModelValidator _bodyModelValidator;
private readonly IValidationExcludeFiltersProvider _bodyValidationExcludeFiltersProvider;
public BodyModelBinder([NotNull] IScopedInstance context,
+ [NotNull] IScopedInstance bindingContext,
[NotNull] IInputFormatterSelector selector,
[NotNull] IBodyModelValidator bodyModelValidator,
[NotNull] IValidationExcludeFiltersProvider bodyValidationExcludeFiltersProvider)
{
_actionContext = context.Value;
+ _bindingContext = bindingContext;
_formatterSelector = selector;
_bodyModelValidator = bodyModelValidator;
_bodyValidationExcludeFiltersProvider = bodyValidationExcludeFiltersProvider;
@@ -34,8 +38,10 @@ namespace Microsoft.AspNet.Mvc
ModelBindingContext bindingContext,
IFormatterBinderMetadata metadata)
{
+ var formatters = _bindingContext.Value.InputFormatters;
+
var formatterContext = new InputFormatterContext(_actionContext, bindingContext.ModelType);
- var formatter = _formatterSelector.SelectFormatter(formatterContext);
+ var formatter = _formatterSelector.SelectFormatter(formatters.ToList(), formatterContext);
if (formatter == null)
{
diff --git a/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/ActionBindingContext.cs b/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/ActionBindingContext.cs
index ebefea9629..775f1f7c3c 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/ActionBindingContext.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/ActionBindingContext.cs
@@ -1,37 +1,19 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
{
public class ActionBindingContext
{
- public ActionBindingContext(ActionContext context,
- IModelMetadataProvider metadataProvider,
- IModelBinder modelBinder,
- IValueProvider valueProvider,
- IInputFormatterSelector inputFormatterSelector,
- IModelValidatorProvider validatorProvider)
- {
- ActionContext = context;
- MetadataProvider = metadataProvider;
- ModelBinder = modelBinder;
- ValueProvider = valueProvider;
- InputFormatterSelector = inputFormatterSelector;
- ValidatorProvider = validatorProvider;
- }
+ public IModelBinder ModelBinder { get; set; }
- public ActionContext ActionContext { get; private set; }
+ public IValueProvider ValueProvider { get; set; }
- public IModelMetadataProvider MetadataProvider { get; private set; }
+ public IList InputFormatters { get; set; }
- public IModelBinder ModelBinder { get; private set; }
-
- public IValueProvider ValueProvider { get; private set; }
-
- public IInputFormatterSelector InputFormatterSelector { get; private set; }
-
- public IModelValidatorProvider ValidatorProvider { get; private set; }
+ public IModelValidatorProvider ValidatorProvider { get; set; }
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/DefaultActionBindingContextProvider.cs b/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/DefaultActionBindingContextProvider.cs
deleted file mode 100644
index 2c80132742..0000000000
--- a/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/DefaultActionBindingContextProvider.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (c) Microsoft Open Technologies, Inc. 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.Threading.Tasks;
-using Microsoft.AspNet.Mvc.ModelBinding;
-
-namespace Microsoft.AspNet.Mvc
-{
- public class DefaultActionBindingContextProvider : IActionBindingContextProvider
- {
- private readonly IModelMetadataProvider _modelMetadataProvider;
- private readonly ICompositeModelBinder _compositeModelBinder;
- private readonly IValueProviderFactory _compositeValueProviderFactory;
- private readonly IInputFormatterSelector _inputFormatterSelector;
- private readonly ICompositeModelValidatorProvider _validatorProvider;
- private Tuple _bindingContext;
-
- public DefaultActionBindingContextProvider(IModelMetadataProvider modelMetadataProvider,
- ICompositeModelBinder compositeModelBinder,
- ICompositeValueProviderFactory compositeValueProviderFactory,
- IInputFormatterSelector inputFormatterProvider,
- ICompositeModelValidatorProvider validatorProvider)
- {
- _modelMetadataProvider = modelMetadataProvider;
- _compositeModelBinder = compositeModelBinder;
- _compositeValueProviderFactory = compositeValueProviderFactory;
- _inputFormatterSelector = inputFormatterProvider;
- _validatorProvider = validatorProvider;
- }
-
- public Task GetActionBindingContextAsync(ActionContext actionContext)
- {
- if (_bindingContext != null)
- {
- if (actionContext == _bindingContext.Item1)
- {
- return Task.FromResult(_bindingContext.Item2);
- }
- }
-
- var factoryContext = new ValueProviderFactoryContext(
- actionContext.HttpContext,
- actionContext.RouteData.Values);
-
- var valueProvider = _compositeValueProviderFactory.GetValueProvider(factoryContext);
-
- var context = new ActionBindingContext(
- actionContext,
- _modelMetadataProvider,
- _compositeModelBinder,
- valueProvider,
- _inputFormatterSelector,
- _validatorProvider);
-
- _bindingContext = new Tuple(actionContext, context);
-
- return Task.FromResult(context);
- }
- }
-}
diff --git a/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/IActionBindingContextProvider.cs b/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/IActionBindingContextProvider.cs
deleted file mode 100644
index d03bce089e..0000000000
--- a/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/IActionBindingContextProvider.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System.Threading.Tasks;
-
-namespace Microsoft.AspNet.Mvc
-{
- public interface IActionBindingContextProvider
- {
- Task GetActionBindingContextAsync(ActionContext actionContext);
- }
-}
diff --git a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs
index 4b20727882..dbc9de9fe4 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs
@@ -1530,6 +1530,22 @@ namespace Microsoft.AspNet.Mvc.Core
return GetString("SerializableError_DefaultError");
}
+ ///
+ /// If an {0} provides a result value by setting the {1} property of {2} to a non-null value, then it cannot call the next filter by invoking {3}.
+ ///
+ internal static string AsyncResourceFilter_InvalidShortCircuit
+ {
+ get { return GetString("AsyncResourceFilter_InvalidShortCircuit"); }
+ }
+
+ ///
+ /// If an {0} provides a result value by setting the {1} property of {2} to a non-null value, then it cannot call the next filter by invoking {3}.
+ ///
+ internal static string FormatAsyncResourceFilter_InvalidShortCircuit(object p0, object p1, object p2, object p3)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("AsyncResourceFilter_InvalidShortCircuit"), p0, p1, p2, p3);
+ }
+
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultHtmlGenerator.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultHtmlGenerator.cs
index b36898f2ee..b666940b67 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultHtmlGenerator.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultHtmlGenerator.cs
@@ -12,6 +12,7 @@ using System.Text;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Rendering.Expressions;
+using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc.Rendering
{
@@ -19,8 +20,8 @@ namespace Microsoft.AspNet.Mvc.Rendering
{
private const string HiddenListItem = @"";
- private readonly IActionBindingContextProvider _actionBindingContextProvider;
private readonly AntiForgery _antiForgery;
+ private readonly IScopedInstance _bindingContextAccessor;
private readonly IModelMetadataProvider _metadataProvider;
private readonly IUrlHelper _urlHelper;
@@ -28,13 +29,13 @@ namespace Microsoft.AspNet.Mvc.Rendering
/// Initializes a new instance of the class.
///
public DefaultHtmlGenerator(
- [NotNull] IActionBindingContextProvider actionBindingContextProvider,
[NotNull] AntiForgery antiForgery,
+ [NotNull] IScopedInstance bindingContextAccessor,
[NotNull] IModelMetadataProvider metadataProvider,
[NotNull] IUrlHelper urlHelper)
{
- _actionBindingContextProvider = actionBindingContextProvider;
_antiForgery = antiForgery;
+ _bindingContextAccessor = bindingContextAccessor;
_metadataProvider = metadataProvider;
_urlHelper = urlHelper;
@@ -723,12 +724,13 @@ namespace Microsoft.AspNet.Mvc.Rendering
ModelMetadata metadata,
string name)
{
- var actionBindingContext = _actionBindingContextProvider.GetActionBindingContextAsync(viewContext).Result;
+ var validatorProvider = _bindingContextAccessor.Value.ValidatorProvider;
+
metadata = metadata ??
ExpressionMetadataProvider.FromStringExpression(name, viewContext.ViewData, _metadataProvider);
- return actionBindingContext
- .ValidatorProvider
+ return
+ validatorProvider
.GetValidators(metadata)
.OfType()
.SelectMany(v => v.GetClientValidationRules(
diff --git a/src/Microsoft.AspNet.Mvc.Core/Resources.resx b/src/Microsoft.AspNet.Mvc.Core/Resources.resx
index b9fb2fe066..b42eda3c40 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Resources.resx
+++ b/src/Microsoft.AspNet.Mvc.Core/Resources.resx
@@ -412,4 +412,7 @@
The input was not valid.
+
+ If an {0} provides a result value by setting the {1} property of {2} to a non-null value, then it cannot call the next filter by invoking {3}.
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/BinderTypeBasedModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/BinderTypeBasedModelBinder.cs
index c34c3cfad2..10f81b33a0 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/BinderTypeBasedModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/BinderTypeBasedModelBinder.cs
@@ -43,7 +43,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var modelBinderProvider = instance as IModelBinderProvider;
if (modelBinderProvider != null)
{
- modelBinder = new CompositeModelBinder(modelBinderProvider);
+ modelBinder = new CompositeModelBinder(modelBinderProvider.ModelBinders);
}
else
{
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs
index 8d2ace61f4..d5e87ea7ed 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs
@@ -18,30 +18,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
///
public class CompositeModelBinder : ICompositeModelBinder
{
- private readonly IModelBinderProvider _modelBindersProvider;
- private IReadOnlyList _binders;
-
///
/// Initializes a new instance of the CompositeModelBinder class.
///
- /// Provides a collection of instances.
- public CompositeModelBinder(IModelBinderProvider modelBindersProvider)
+ /// A collection of instances.
+ public CompositeModelBinder([NotNull] IEnumerable modelBinders)
{
- _modelBindersProvider = modelBindersProvider;
+ ModelBinders = new List(modelBinders);
}
///
- public IReadOnlyList ModelBinders
- {
- get
- {
- if (_binders == null)
- {
- _binders = _modelBindersProvider.ModelBinders;
- }
- return _binders;
- }
- }
+ public IReadOnlyList ModelBinders { get; }
public virtual async Task BindModelAsync([NotNull] ModelBindingContext bindingContext)
{
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/CompositeModelValidatorProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/CompositeModelValidatorProvider.cs
index 74d4b9b853..2169db2bba 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/CompositeModelValidatorProvider.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/CompositeModelValidatorProvider.cs
@@ -14,14 +14,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
///
/// Initializes a new instance of .
///
- /// The instance used to get the list of
- /// activated that this instance delegates to.
- public CompositeModelValidatorProvider(IModelValidatorProviderProvider provider)
+ ///
+ /// A collection of instances.
+ ///
+ public CompositeModelValidatorProvider([NotNull] IEnumerable providers)
{
- ValidatorProviders = provider.ModelValidatorProviders;
+ ValidatorProviders = new List(providers);
}
- public IReadOnlyList ValidatorProviders { get; private set; }
+ public IReadOnlyList ValidatorProviders { get; }
public IEnumerable GetValidators(ModelMetadata metadata)
{
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/CompositeValueProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/CompositeValueProvider.cs
index d7654c7dd5..71d8a14916 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/CompositeValueProvider.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/CompositeValueProvider.cs
@@ -33,6 +33,33 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
}
+ ///
+ /// Creates a new from the provided
+ /// and .
+ ///
+ /// The set of instances.
+ /// The .
+ ///
+ /// A containing all instances
+ /// created.
+ ///
+ public static CompositeValueProvider Create(
+ [NotNull] IEnumerable factories,
+ [NotNull] ValueProviderFactoryContext context)
+ {
+ var composite = new CompositeValueProvider();
+ foreach (var valueProvidersFactory in factories)
+ {
+ var valueProvider = valueProvidersFactory.GetValueProvider(context);
+ if (valueProvider != null)
+ {
+ composite.Add(valueProvider);
+ }
+ }
+
+ return composite;
+ }
+
///
public virtual async Task ContainsPrefixAsync(string prefix)
{
diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs
index 7c908794e7..1e3e594e0a 100644
--- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs
+++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs
@@ -29,6 +29,13 @@ namespace System.Web.Http
[Activate]
public ActionContext ActionContext { get; set; }
+ ///
+ /// Gets the .
+ ///
+ /// The setter is intended for unit testing purposes only.
+ [Activate]
+ public ActionBindingContext BindingContext { get; set; }
+
///
/// Gets the http context.
///
@@ -40,6 +47,13 @@ namespace System.Web.Http
}
}
+ ///
+ /// Gets the .
+ ///
+ /// The setter is intended for unit testing purposes only.
+ [Activate]
+ public IModelMetadataProvider MetadataProvider { get; set; }
+
///
/// Gets model state after the model binding process. This ModelState will be empty before model binding
/// happens.
@@ -398,15 +412,15 @@ namespace System.Web.Http
///
public void Validate(TEntity entity, string keyPrefix)
{
+ var modelMetadata = MetadataProvider.GetMetadataForType(() => entity, typeof(TEntity));
+
var bodyValidationExcludeFiltersProvider = Context.RequestServices
.GetRequiredService();
var validator = Context.RequestServices.GetRequiredService();
- var metadataProvider = Context.RequestServices.GetRequiredService();
- var modelMetadata = metadataProvider.GetMetadataForType(() => entity, typeof(TEntity));
- var validatorProvider = Context.RequestServices.GetRequiredService();
+
var modelValidationContext = new ModelValidationContext(
- metadataProvider,
- validatorProvider,
+ MetadataProvider,
+ BindingContext.ValidatorProvider,
ModelState,
modelMetadata,
containerMetadata: null,
diff --git a/src/Microsoft.AspNet.Mvc/MvcServices.cs b/src/Microsoft.AspNet.Mvc/MvcServices.cs
index 64c9ec3d3d..90de0a7cb0 100644
--- a/src/Microsoft.AspNet.Mvc/MvcServices.cs
+++ b/src/Microsoft.AspNet.Mvc/MvcServices.cs
@@ -77,20 +77,16 @@ namespace Microsoft.AspNet.Mvc
// Dataflow - ModelBinding, Validation and Formatting
yield return describe.Transient();
- yield return describe.Scoped();
yield return describe.Transient();
yield return describe.Scoped();
yield return describe.Transient();
- yield return describe.Scoped();
yield return describe.Transient();
- yield return describe.Scoped();
yield return describe.Transient();
yield return describe.Instance(new JsonOutputFormatter());
yield return describe.Transient();
- yield return describe.Scoped();
yield return describe.Transient();
yield return describe.Transient();
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/BodyModelBinderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/BodyModelBinderTests.cs
index ed0ad628ff..f97c794fa2 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/BodyModelBinderTests.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/BodyModelBinderTests.cs
@@ -89,7 +89,7 @@ namespace Microsoft.AspNet.Mvc
HttpContext = new DefaultHttpContext(),
};
- ModelBindingContext bindingContext = new ModelBindingContext
+ var bindingContext = new ModelBindingContext
{
ModelMetadata = metadataProvider.GetMetadataForType(null, modelType),
ModelName = "someName",
@@ -106,7 +106,11 @@ namespace Microsoft.AspNet.Mvc
{
var actionContext = CreateActionContext(new DefaultHttpContext());
var inputFormatterSelector = new Mock();
- inputFormatterSelector.Setup(o => o.SelectFormatter(It.IsAny())).Returns(inputFormatter);
+ inputFormatterSelector
+ .Setup(o => o.SelectFormatter(
+ It.IsAny>(),
+ It.IsAny()))
+ .Returns(inputFormatter);
if (validator == null)
{
@@ -117,14 +121,27 @@ namespace Microsoft.AspNet.Mvc
validator = mockValidator.Object;
}
- var bodyValidationPredicatesProvidwer = new Mock();
- bodyValidationPredicatesProvidwer.SetupGet(o => o.ExcludeFilters)
+ var bodyValidationPredicatesProvider = new Mock();
+ bodyValidationPredicatesProvider.SetupGet(o => o.ExcludeFilters)
.Returns(new List());
- var binder = new BodyModelBinder(actionContext,
- inputFormatterSelector.Object,
- validator,
- bodyValidationPredicatesProvidwer.Object);
+ var bindingContext = new ActionBindingContext()
+ {
+ InputFormatters = new List(),
+ };
+
+ var bindingContextAccessor = new MockScopedInstance()
+ {
+ Value = bindingContext,
+ };
+
+ var binder = new BodyModelBinder(
+ actionContext,
+ bindingContextAccessor,
+ inputFormatterSelector.Object,
+ validator,
+ bodyValidationPredicatesProvider.Object);
+
return binder;
}
@@ -146,5 +163,10 @@ namespace Microsoft.AspNet.Mvc
.Returns(actionContext);
return contextAccessor.Object;
}
+
+ private class Person
+ {
+ public string Name { get; set; }
+ }
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionInvokerTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionInvokerTest.cs
index 6e3958de3b..3831ededa6 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionInvokerTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionInvokerTest.cs
@@ -326,7 +326,7 @@ namespace Microsoft.AspNet.Mvc
}
[Fact]
- public async Task InvokeAction_ExceptionInAuthorizationFilterCannotBeHandledByExceptionFilters()
+ public async Task InvokeAction_ExceptionInAuthorizationFilterCannotBeHandledByOtherFilters()
{
// Arrange
var expected = new InvalidCastException();
@@ -349,6 +349,7 @@ namespace Microsoft.AspNet.Mvc
// None of these filters should run
var authorizationFilter2 = new Mock(MockBehavior.Strict);
+ var resourceFilter = new Mock(MockBehavior.Strict);
var actionFilter = new Mock(MockBehavior.Strict);
var resultFilter = new Mock(MockBehavior.Strict);
@@ -357,6 +358,7 @@ namespace Microsoft.AspNet.Mvc
exceptionFilter.Object,
authorizationFilter1.Object,
authorizationFilter2.Object,
+ resourceFilter.Object,
actionFilter.Object,
resultFilter.Object,
});
@@ -1202,6 +1204,649 @@ namespace Microsoft.AspNet.Mvc
resultFilter2.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once());
}
+ [Fact]
+ public async Task InvokeAction_InvokesAsyncResourceFilter()
+ {
+ // Arrange
+ var resourceFilter = new Mock(MockBehavior.Strict);
+ resourceFilter
+ .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()))
+ .Returns(async (c, next) =>
+ {
+ await next();
+ })
+ .Verifiable();
+
+ var invoker = CreateInvoker(new IFilter[] { resourceFilter.Object });
+
+ // Act
+ await invoker.InvokeAsync();
+
+ // Assert
+ resourceFilter.Verify(
+ f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()),
+ Times.Once());
+ }
+
+ [Fact]
+ public async Task InvokeAction_InvokesResourceFilter()
+ {
+ // Arrange
+ var resourceFilter = new Mock(MockBehavior.Strict);
+ resourceFilter
+ .Setup(f => f.OnResourceExecuting(It.IsAny()))
+ .Verifiable();
+ resourceFilter
+ .Setup(f => f.OnResourceExecuted(It.IsAny()))
+ .Verifiable();
+
+ var invoker = CreateInvoker(resourceFilter.Object);
+
+ // Act
+ await invoker.InvokeAsync();
+
+ // Assert
+ resourceFilter.Verify(
+ f => f.OnResourceExecuted(It.IsAny()),
+ Times.Once());
+ resourceFilter.Verify(
+ f => f.OnResourceExecuted(It.IsAny()),
+ Times.Once());
+
+ }
+
+ [Fact]
+ public async Task InvokeAction_InvokesAsyncResourceFilter_WithActionResult_FromAction()
+ {
+ // Arrange
+ ResourceExecutedContext context = null;
+ var resourceFilter = new Mock(MockBehavior.Strict);
+ resourceFilter
+ .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()))
+ .Returns(async (c, next) =>
+ {
+ context = await next();
+ })
+ .Verifiable();
+
+ var invoker = CreateInvoker(resourceFilter.Object);
+
+ // Act
+ await invoker.InvokeAsync();
+
+ // Assert
+ Assert.Same(_result, context.Result);
+
+ resourceFilter.Verify(
+ f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()),
+ Times.Once());
+ }
+
+ [Fact]
+ public async Task InvokeAction_InvokesAsyncResourceFilter_WithActionResult_FromActionFilter()
+ {
+ // Arrange
+ var expected = Mock.Of();
+
+ ResourceExecutedContext context = null;
+ var resourceFilter = new Mock(MockBehavior.Strict);
+ resourceFilter
+ .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()))
+ .Returns(async (c, next) =>
+ {
+ context = await next();
+ })
+ .Verifiable();
+
+ var actionFilter = new Mock(MockBehavior.Strict);
+ actionFilter
+ .Setup(f => f.OnActionExecuting(It.IsAny()))
+ .Callback((c) =>
+ {
+ c.Result = expected;
+ });
+
+ var invoker = CreateInvoker(new IFilter[] { resourceFilter.Object, actionFilter.Object });
+
+ // Act
+ await invoker.InvokeAsync();
+
+ // Assert
+ Assert.Same(expected, context.Result);
+
+ resourceFilter.Verify(
+ f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()),
+ Times.Once());
+ }
+
+ [Fact]
+ public async Task InvokeAction_InvokesAsyncResourceFilter_WithActionResult_FromExceptionFilter()
+ {
+ // Arrange
+ var expected = Mock.Of();
+
+ ResourceExecutedContext context = null;
+ var resourceFilter = new Mock(MockBehavior.Strict);
+ resourceFilter
+ .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()))
+ .Returns(async (c, next) =>
+ {
+ context = await next();
+ })
+ .Verifiable();
+
+ var exceptionFilter = new Mock(MockBehavior.Strict);
+ exceptionFilter
+ .Setup(f => f.OnException(It.IsAny()))
+ .Callback((c) =>
+ {
+ c.Result = expected;
+ });
+
+ var invoker = CreateInvoker(new IFilter[] { resourceFilter.Object, exceptionFilter.Object }, actionThrows: true);
+
+ // Act
+ await invoker.InvokeAsync();
+
+ // Assert
+ Assert.Same(expected, context.Result);
+ Assert.Null(context.Exception);
+ Assert.Null(context.ExceptionDispatchInfo);
+
+ resourceFilter.Verify(
+ f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()),
+ Times.Once());
+ }
+
+ [Fact]
+ public async Task InvokeAction_InvokesAsyncResourceFilter_WithActionResult_FromResultFilter()
+ {
+ // Arrange
+ var expected = Mock.Of();
+
+ ResourceExecutedContext context = null;
+ var resourceFilter = new Mock(MockBehavior.Strict);
+ resourceFilter
+ .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()))
+ .Returns(async (c, next) =>
+ {
+ context = await next();
+ })
+ .Verifiable();
+
+ var resultFilter = new Mock(MockBehavior.Loose);
+ resultFilter
+ .Setup(f => f.OnResultExecuting(It.IsAny()))
+ .Callback((c) =>
+ {
+ c.Result = expected;
+ });
+
+ var invoker = CreateInvoker(new IFilter[] { resourceFilter.Object, resultFilter.Object });
+
+ // Act
+ await invoker.InvokeAsync();
+
+ // Assert
+ Assert.Same(expected, context.Result);
+
+ resourceFilter.Verify(
+ f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()),
+ Times.Once());
+ }
+
+ [Fact]
+ public async Task InvokeAction_InvokesAsyncResourceFilter_HandleException_FromAction()
+ {
+ // Arrange
+ ResourceExecutedContext context = null;
+ var resourceFilter = new Mock(MockBehavior.Strict);
+ resourceFilter
+ .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()))
+ .Returns(async (c, next) =>
+ {
+ context = await next();
+ context.ExceptionHandled = true;
+ })
+ .Verifiable();
+
+ var invoker = CreateInvoker(new IFilter[] { resourceFilter.Object }, actionThrows: true);
+
+ // Act
+ await invoker.InvokeAsync();
+
+ // Assert
+ Assert.Same(_actionException, context.Exception);
+ Assert.Same(_actionException, context.ExceptionDispatchInfo.SourceException);
+
+ resourceFilter.Verify(
+ f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()),
+ Times.Once());
+ }
+
+ [Fact]
+ public async Task InvokeAction_InvokesAsyncResourceFilter_HandleException_FromActionFilter()
+ {
+ // Arrange
+ var expected = new DataMisalignedException();
+
+ ResourceExecutedContext context = null;
+ var resourceFilter = new Mock(MockBehavior.Strict);
+ resourceFilter
+ .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()))
+ .Returns(async (c, next) =>
+ {
+ context = await next();
+ context.ExceptionHandled = true;
+ })
+ .Verifiable();
+
+ var actionFilter = new Mock(MockBehavior.Strict);
+ actionFilter
+ .Setup(f => f.OnActionExecuting(It.IsAny()))
+ .Callback((c) =>
+ {
+ throw expected;
+ });
+
+ var invoker = CreateInvoker(new IFilter[] { resourceFilter.Object, actionFilter.Object });
+
+ // Act
+ await invoker.InvokeAsync();
+
+ // Assert
+ Assert.Same(expected, context.Exception);
+ Assert.Same(expected, context.ExceptionDispatchInfo.SourceException);
+
+ resourceFilter.Verify(
+ f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()),
+ Times.Once());
+ }
+
+ [Fact]
+ public async Task InvokeAction_InvokesAsyncResourceFilter_HandlesException_FromExceptionFilter()
+ {
+ // Arrange
+ var expected = new DataMisalignedException();
+
+ ResourceExecutedContext context = null;
+ var resourceFilter = new Mock(MockBehavior.Strict);
+ resourceFilter
+ .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()))
+ .Returns(async (c, next) =>
+ {
+ context = await next();
+ context.ExceptionHandled = true;
+ })
+ .Verifiable();
+
+ var exceptionFilter = new Mock(MockBehavior.Strict);
+ exceptionFilter
+ .Setup(f => f.OnException(It.IsAny()))
+ .Callback((c) =>
+ {
+ throw expected;
+ });
+
+ var invoker = CreateInvoker(new IFilter[] { resourceFilter.Object, exceptionFilter.Object }, actionThrows: true);
+
+ // Act
+ await invoker.InvokeAsync();
+
+ // Assert
+ Assert.Same(expected, context.Exception);
+ Assert.Same(expected, context.ExceptionDispatchInfo.SourceException);
+
+ resourceFilter.Verify(
+ f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()),
+ Times.Once());
+ }
+
+ [Fact]
+ public async Task InvokeAction_InvokesAsyncResourceFilter_HandlesException_FromResultFilter()
+ {
+ // Arrange
+ var expected = new DataMisalignedException();
+
+ ResourceExecutedContext context = null;
+ var resourceFilter = new Mock(MockBehavior.Strict);
+ resourceFilter
+ .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()))
+ .Returns(async (c, next) =>
+ {
+ context = await next();
+ context.ExceptionHandled = true;
+ })
+ .Verifiable();
+
+ var resultFilter = new Mock(MockBehavior.Loose);
+ resultFilter
+ .Setup(f => f.OnResultExecuting(It.IsAny()))
+ .Callback((c) =>
+ {
+ throw expected;
+ });
+
+ var invoker = CreateInvoker(new IFilter[] { resourceFilter.Object, resultFilter.Object });
+
+ // Act
+ await invoker.InvokeAsync();
+
+ // Assert
+ Assert.Same(expected, context.Exception);
+ Assert.Same(expected, context.ExceptionDispatchInfo.SourceException);
+
+ resourceFilter.Verify(
+ f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()),
+ Times.Once());
+ }
+
+ [Fact]
+ public async Task InvokeAction_InvokesAsyncResourceFilter_HandleException_BySettingNull()
+ {
+ // Arrange
+ ResourceExecutedContext context = null;
+ var resourceFilter = new Mock(MockBehavior.Strict);
+ resourceFilter
+ .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()))
+ .Returns(async (c, next) =>
+ {
+ context = await next();
+
+ Assert.Same(_actionException, context.Exception);
+ Assert.Same(_actionException, context.ExceptionDispatchInfo.SourceException);
+
+ context.Exception = null;
+ })
+ .Verifiable();
+
+ var invoker = CreateInvoker(new IFilter[] { resourceFilter.Object }, actionThrows: true);
+
+ // Act
+ await invoker.InvokeAsync();
+
+ // Assert
+ resourceFilter.Verify(
+ f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()),
+ Times.Once());
+ }
+
+ [Fact]
+ public async Task InvokeAction_InvokesAsyncResourceFilter_ThrowsUnhandledException()
+ {
+ // Arrange
+ var expected = new DataMisalignedException();
+
+ ResourceExecutedContext context = null;
+ var resourceFilter1 = new Mock(MockBehavior.Strict);
+ resourceFilter1
+ .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()))
+ .Returns(async (c, next) =>
+ {
+ context = await next();
+ })
+ .Verifiable();
+
+ var resourceFilter2 = new Mock(MockBehavior.Strict);
+ resourceFilter2
+ .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()))
+ .Returns((c, next) =>
+ {
+ throw expected;
+ })
+ .Verifiable();
+
+ var invoker = CreateInvoker(new IFilter[] { resourceFilter1.Object, resourceFilter2.Object }, actionThrows: true);
+
+ // Act
+ var exception = await Assert.ThrowsAsync(invoker.InvokeAsync);
+
+ // Assert
+ Assert.Same(expected, exception);
+ Assert.Same(expected, context.Exception);
+ Assert.Same(expected, context.ExceptionDispatchInfo.SourceException);
+ }
+
+ [Fact]
+ public async Task InvokeAction_InvokesResourceFilter_OnResourceExecuting_ThrowsUnhandledException()
+ {
+ // Arrange
+ var expected = new DataMisalignedException();
+
+ ResourceExecutedContext context = null;
+ var resourceFilter1 = new Mock(MockBehavior.Strict);
+ resourceFilter1
+ .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()))
+ .Returns(async (c, next) =>
+ {
+ context = await next();
+ })
+ .Verifiable();
+
+ var resourceFilter2 = new Mock(MockBehavior.Strict);
+ resourceFilter2
+ .Setup(f => f.OnResourceExecuting(It.IsAny()))
+ .Callback((c) =>
+ {
+ throw expected;
+ })
+ .Verifiable();
+
+ var invoker = CreateInvoker(new IFilter[] { resourceFilter1.Object, resourceFilter2.Object }, actionThrows: true);
+
+ // Act
+ var exception = await Assert.ThrowsAsync(invoker.InvokeAsync);
+
+ // Assert
+ Assert.Same(expected, exception);
+ Assert.Same(expected, context.Exception);
+ Assert.Same(expected, context.ExceptionDispatchInfo.SourceException);
+ }
+
+ [Fact]
+ public async Task InvokeAction_InvokesResourceFilter_OnResourceExecuted_ThrowsUnhandledException()
+ {
+ // Arrange
+ var expected = new DataMisalignedException();
+
+ ResourceExecutedContext context = null;
+ var resourceFilter1 = new Mock(MockBehavior.Strict);
+ resourceFilter1
+ .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()))
+ .Returns(async (c, next) =>
+ {
+ context = await next();
+ })
+ .Verifiable();
+
+ var resourceFilter2 = new Mock(MockBehavior.Loose);
+ resourceFilter2
+ .Setup(f => f.OnResourceExecuted(It.IsAny()))
+ .Callback((c) =>
+ {
+ throw expected;
+ })
+ .Verifiable();
+
+ var invoker = CreateInvoker(new IFilter[] { resourceFilter1.Object, resourceFilter2.Object }, actionThrows: true);
+
+ // Act
+ var exception = await Assert.ThrowsAsync(invoker.InvokeAsync);
+
+ // Assert
+ Assert.Same(expected, exception);
+ Assert.Same(expected, context.Exception);
+ Assert.Same(expected, context.ExceptionDispatchInfo.SourceException);
+ }
+
+ [Fact]
+ public async Task InvokeAction_InvokesAsyncResourceFilter_ShortCircuit()
+ {
+ // Arrange
+ var expected = new Mock(MockBehavior.Strict);
+ expected
+ .Setup(r => r.ExecuteResultAsync(It.IsAny()))
+ .Returns(Task.FromResult(true))
+ .Verifiable();
+
+ ResourceExecutedContext context = null;
+ var resourceFilter1 = new Mock(MockBehavior.Strict);
+ resourceFilter1
+ .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()))
+ .Returns(async (c, next) =>
+ {
+ context = await next();
+ })
+ .Verifiable();
+
+ var resourceFilter2 = new Mock(MockBehavior.Strict);
+ resourceFilter2
+ .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()))
+ .Returns((c, next) =>
+ {
+ c.Result = expected.Object;
+ return Task.FromResult(true);
+ })
+ .Verifiable();
+
+ var resourceFilter3 = new Mock(MockBehavior.Strict);
+ var exceptionFilter = new Mock(MockBehavior.Strict);
+ var actionFilter = new Mock(MockBehavior.Strict);
+ var resultFilter = new Mock(MockBehavior.Strict);
+
+ var invoker = CreateInvoker(
+ new IFilter[]
+ {
+ resourceFilter1.Object, // This filter should see the result retured from resourceFilter2
+ resourceFilter2.Object, // This filter will short circuit
+ resourceFilter3.Object, // This shouldn't run - it will throw if it does
+ exceptionFilter.Object, // This shouldn't run - it will throw if it does
+ actionFilter.Object, // This shouldn't run - it will throw if it does
+ resultFilter.Object // This shouldn't run - it will throw if it does
+ },
+ // The action won't run
+ actionThrows: true);
+
+ // Act
+ await invoker.InvokeAsync();
+
+ // Assert
+ Assert.Same(expected.Object, context.Result);
+ Assert.True(context.Canceled);
+ }
+
+ [Fact]
+ public async Task InvokeAction_InvokesResourceFilter_ShortCircuit()
+ {
+ // Arrange
+ var expected = new Mock(MockBehavior.Strict);
+ expected
+ .Setup(r => r.ExecuteResultAsync(It.IsAny()))
+ .Returns(Task.FromResult(true))
+ .Verifiable();
+
+ ResourceExecutedContext context = null;
+ var resourceFilter1 = new Mock(MockBehavior.Strict);
+ resourceFilter1
+ .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()))
+ .Returns(async (c, next) =>
+ {
+ context = await next();
+ });
+
+ var resourceFilter2 = new Mock(MockBehavior.Strict);
+ resourceFilter2
+ .Setup(f => f.OnResourceExecuting(It.IsAny()))
+ .Callback((c) =>
+ {
+ c.Result = expected.Object;
+ });
+
+ var resourceFilter3 = new Mock(MockBehavior.Strict);
+ var actionFilter = new Mock(MockBehavior.Strict);
+ var resultFilter = new Mock(MockBehavior.Strict);
+
+ var invoker = CreateInvoker(
+ new IFilter[]
+ {
+ resourceFilter1.Object, // This filter should see the result retured from resourceFilter2
+ resourceFilter2.Object,
+ resourceFilter3.Object, // This shouldn't run - it will throw if it does
+ actionFilter.Object, // This shouldn't run - it will throw if it does
+ resultFilter.Object // This shouldn't run - it will throw if it does
+ },
+ // The action won't run
+ actionThrows: true);
+
+ // Act
+ await invoker.InvokeAsync();
+
+ // Assert
+ Assert.Same(expected.Object, context.Result);
+ Assert.True(context.Canceled);
+ }
+
+ [Fact]
+ public async Task InvokeAction_InvokesAsyncResourceFilter_InvalidShortCircuit()
+ {
+ // Arrange
+ var message =
+ "If an IAsyncResourceFilter provides a result value by setting the Result property of " +
+ "ResourceExecutingContext to a non-null value, then it cannot call the next filter by invoking " +
+ "ResourceExecutionDelegate.";
+
+ ResourceExecutedContext context = null;
+ var resourceFilter = new Mock(MockBehavior.Strict);
+ resourceFilter
+ .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()))
+ .Returns(async (c, next) =>
+ {
+ // This is not valid.
+ c.Result = Mock.Of();
+ context = await next();
+ });
+
+ var invoker = CreateInvoker(new IFilter[] { resourceFilter.Object, });
+
+ // Act
+ var exception = await Assert.ThrowsAsync(invoker.InvokeAsync);
+
+ // Assert
+ Assert.Equal(message, exception.Message);
+ }
+
+ [Fact]
+ public async Task InvokeAction_AuthorizationFilter_ChallengePreventsResourceFiltersFromRunning()
+ {
+ // Arrange
+ var resourceFilter = new Mock(MockBehavior.Strict);
+ resourceFilter
+ .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()))
+ .Returns(async (c, next) =>
+ {
+ await next();
+ })
+ .Verifiable();
+
+ var authorizationFilter = new Mock(MockBehavior.Strict);
+ authorizationFilter
+ .Setup(f => f.OnAuthorization(It.IsAny()))
+ .Callback((c) =>
+ {
+ c.Result = _result;
+ });
+
+ var invoker = CreateInvoker(new IFilter[] { authorizationFilter.Object, resourceFilter.Object, });
+
+ // Act
+ await invoker.InvokeAsync();
+
+ // Assert
+ resourceFilter.Verify(
+ f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()),
+ Times.Never());
+ }
+
[Fact]
public void CreateActionResult_ReturnsSameActionResult()
{
@@ -1320,11 +1965,6 @@ namespace Microsoft.AspNet.Mvc
controllerFactory.Setup(c => c.CreateController(It.IsAny())).Returns(this);
controllerFactory.Setup(m => m.ReleaseController(this)).Verifiable();
- var actionBindingContextProvider = new Mock(MockBehavior.Strict);
- actionBindingContextProvider
- .Setup(abcp => abcp.GetActionBindingContextAsync(It.IsAny()))
- .Returns(Task.FromResult(new ActionBindingContext(null, null, null, null, null, null)));
-
var filterProvider = new Mock>(MockBehavior.Strict);
filterProvider
.Setup(fp => fp.Invoke(It.IsAny()))
@@ -1339,8 +1979,14 @@ namespace Microsoft.AspNet.Mvc
filterProvider.Object,
controllerFactory,
actionDescriptor,
+ new EmptyModelMetadataProvider(),
inputFormattersProvider.Object,
- Mock.Of());
+ new DefaultInputFormatterSelector(),
+ Mock.Of(),
+ new MockModelBinderProvider(),
+ new MockModelValidatorProviderProvider(),
+ new MockValueProviderFactoryProvider(),
+ new MockScopedInstance());
return invoker;
}
@@ -1354,8 +2000,9 @@ namespace Microsoft.AspNet.Mvc
var actionDescriptor = new ControllerActionDescriptor
{
MethodInfo = typeof(TestController).GetTypeInfo()
- .DeclaredMethods
- .First(m => m.Name.Equals("ActionMethodWithDefaultValues", StringComparison.Ordinal)),
+ .DeclaredMethods
+ .First(m => m.Name.Equals("ActionMethodWithDefaultValues", StringComparison.Ordinal)),
+
Parameters = new List
{
new ParameterDescriptor
@@ -1375,31 +2022,29 @@ namespace Microsoft.AspNet.Mvc
context.SetupGet(c => c.Items)
.Returns(new Dictionary