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()); var routeContext = new RouteContext(context.Object); - var actionContext = new ActionContext(routeContext, - actionDescriptor); - var bindingContext = new ActionBindingContext(actionContext, - Mock.Of(), - binder.Object, - Mock.Of(), - Mock.Of(), - Mock.Of()); - var actionBindingContextProvider = new Mock(); - actionBindingContextProvider.Setup(p => p.GetActionBindingContextAsync(It.IsAny())) - .Returns(Task.FromResult(bindingContext)); + var actionContext = new ActionContext(routeContext, actionDescriptor); + var controllerFactory = new Mock(); controllerFactory.Setup(c => c.CreateController(It.IsAny())) .Returns(new TestController()); var inputFormattersProvider = new Mock(); inputFormattersProvider.SetupGet(o => o.InputFormatters) .Returns(new List()); - var invoker = new ControllerActionInvoker(actionContext, - Mock.Of>(), - controllerFactory.Object, - actionDescriptor, - inputFormattersProvider.Object, - new DefaultControllerActionArgumentBinder( - actionBindingContextProvider.Object)); + + var invoker = new ControllerActionInvoker( + actionContext, + Mock.Of>(), + controllerFactory.Object, + actionDescriptor, + metadataProvider, + inputFormattersProvider.Object, + new DefaultInputFormatterSelector(), + new DefaultControllerActionArgumentBinder(metadataProvider), + new MockModelBinderProvider() { ModelBinders = new List() { binder.Object } }, + new MockModelValidatorProviderProvider(), + new MockValueProviderFactoryProvider(), + new MockScopedInstance()); // Act await invoker.InvokeAsync(); @@ -1458,14 +2103,27 @@ namespace Microsoft.AspNet.Mvc INestedProviderManager filterProvider, Mock controllerFactoryMock, ControllerActionDescriptor descriptor, + IModelMetadataProvider modelMetadataProvider, IInputFormattersProvider inputFormattersProvider, - IControllerActionArgumentBinder controllerActionArgumentBinder) : - base(actionContext, - filterProvider, - controllerFactoryMock.Object, - descriptor, - inputFormattersProvider, - controllerActionArgumentBinder) + IInputFormatterSelector inputFormatterSelector, + IControllerActionArgumentBinder controllerActionArgumentBinder, + IModelBinderProvider modelBinderProvider, + IModelValidatorProviderProvider modelValidatorProviderProvider, + IValueProviderFactoryProvider valueProviderFactoryProvider, + IScopedInstance actionBindingContext) + : base( + actionContext, + filterProvider, + controllerFactoryMock.Object, + descriptor, + modelMetadataProvider, + inputFormattersProvider, + inputFormatterSelector, + controllerActionArgumentBinder, + modelBinderProvider, + modelValidatorProviderProvider, + valueProviderFactoryProvider, + actionBindingContext) { _factoryMock = controllerFactoryMock; } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs index 91e33776d9..09c522084b 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs @@ -1301,21 +1301,20 @@ namespace Microsoft.AspNet.Mvc.Test { var metadataProvider = new DataAnnotationsModelMetadataProvider(); var actionContext = new ActionContext(Mock.Of(), new RouteData(), new ActionDescriptor()); - var bindingContext = new ActionBindingContext(actionContext, - metadataProvider, - binder, - provider ?? Mock.Of(), - Mock.Of(), - Mock.Of()); - var bindingContextProvider = new Mock(); - bindingContextProvider.Setup(b => b.GetActionBindingContextAsync(actionContext)) - .Returns(Task.FromResult(bindingContext)); var viewData = new ViewDataDictionary(metadataProvider, new ModelStateDictionary()); - return new Controller + + var bindingContext = new ActionBindingContext() + { + ModelBinder = binder, + ValueProvider = provider, + }; + + return new Controller() { ActionContext = actionContext, - BindingContextProvider = bindingContextProvider.Object, + BindingContext = bindingContext, + MetadataProvider = metadataProvider, ViewData = viewData }; } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultAssemblyProviderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultAssemblyProviderTests.cs index 7260a1aec8..82ded16026 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultAssemblyProviderTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultAssemblyProviderTests.cs @@ -6,6 +6,7 @@ using System.Linq; using Microsoft.Framework.Runtime; using Moq; using Xunit; +using Microsoft.AspNet.Http; namespace Microsoft.AspNet.Mvc.Core { @@ -32,6 +33,8 @@ namespace Microsoft.AspNet.Mvc.Core // Assert Assert.Equal(new[] { "SomeRandomAssembly" }, candidates.Select(a => a.Name)); + + var context = new Mock(); } [Fact] diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultControllerActivatorTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultControllerActivatorTest.cs index 3c2846117b..d19d772270 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultControllerActivatorTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultControllerActivatorTest.cs @@ -7,6 +7,7 @@ using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Routing; +using Microsoft.Framework.DependencyInjection; using Moq; using Xunit; @@ -18,18 +19,14 @@ namespace Microsoft.AspNet.Mvc.Core.Test public void Activate_SetsPropertiesFromActionContextHierarchy() { // Arrange - var services = new Mock(); - services.Setup(s => s.GetService(typeof(IUrlHelper))) - .Returns(Mock.Of()); - services.Setup(s => s.GetService(typeof(IModelMetadataProvider))) - .Returns(new EmptyModelMetadataProvider()); + var services = GetServices(); var httpRequest = Mock.Of(); var httpContext = new Mock(); httpContext.SetupGet(c => c.Request) .Returns(httpRequest); httpContext.SetupGet(c => c.RequestServices) - .Returns(services.Object); + .Returns(services); var routeContext = new RouteContext(httpContext.Object); var controller = new TestController(); var context = new ActionContext(routeContext, new ActionDescriptor()) @@ -51,17 +48,11 @@ namespace Microsoft.AspNet.Mvc.Core.Test public void Activate_SetsViewDatDictionary() { // Arrange - var services = new Mock(); - services.Setup(s => s.GetService(typeof(IModelMetadataProvider))) - .Returns(new EmptyModelMetadataProvider()); - services.Setup(s => s.GetService(typeof(ICompositeViewEngine))) - .Returns(Mock.Of()); - services.Setup(s => s.GetService(typeof(IUrlHelper))) - .Returns(Mock.Of()); + var services = GetServices(); var httpContext = new Mock(); httpContext.SetupGet(c => c.RequestServices) - .Returns(services.Object); + .Returns(services); var routeContext = new RouteContext(httpContext.Object); var controller = new TestController(); var context = new ActionContext(routeContext, new ActionDescriptor()) @@ -78,19 +69,44 @@ namespace Microsoft.AspNet.Mvc.Core.Test } [Fact] - public void Activate_PopulatesServicesFromServiceContainer() + public void Activate_SetsBindingContext() { // Arrange - var urlHelper = Mock.Of(); - var services = new Mock(); - services.Setup(s => s.GetService(typeof(IUrlHelper))) - .Returns(urlHelper); - services.Setup(s => s.GetService(typeof(IModelMetadataProvider))) - .Returns(new EmptyModelMetadataProvider()); + var bindingContext = new ActionBindingContext(); + + var services = GetServices(); + services.GetRequiredService>().Value = bindingContext; var httpContext = new Mock(); httpContext.SetupGet(c => c.RequestServices) - .Returns(services.Object); + .Returns(services); + var routeContext = new RouteContext(httpContext.Object); + + var controller = new TestController(); + var context = new ActionContext(routeContext, new ActionDescriptor()) + { + Controller = controller + }; + + var activator = new DefaultControllerActivator(); + + // Act + activator.Activate(controller, context); + + // Assert + Assert.Same(bindingContext, controller.BindingContext); + } + + [Fact] + public void Activate_PopulatesServicesFromServiceContainer() + { + // Arrange + var services = GetServices(); + var urlHelper = services.GetRequiredService(); + + var httpContext = new Mock(); + httpContext.SetupGet(c => c.RequestServices) + .Returns(services); var routeContext = new RouteContext(httpContext.Object); var controller = new TestController(); var context = new ActionContext(routeContext, new ActionDescriptor()) @@ -110,17 +126,13 @@ namespace Microsoft.AspNet.Mvc.Core.Test public void Activate_IgnoresPropertiesThatAreNotDecoratedWithActivateAttribute() { // Arrange - var services = new Mock(); - services.Setup(s => s.GetService(typeof(IUrlHelper))) - .Returns(Mock.Of()); - services.Setup(s => s.GetService(typeof(IModelMetadataProvider))) - .Returns(new EmptyModelMetadataProvider()); + var services = GetServices(); var httpContext = new Mock(); httpContext.SetupGet(c => c.Response) .Returns(Mock.Of()); httpContext.SetupGet(c => c.RequestServices) - .Returns(services.Object); + .Returns(services); var routeContext = new RouteContext(httpContext.Object); var controller = new TestController(); var context = new ActionContext(routeContext, new ActionDescriptor()) @@ -136,11 +148,27 @@ namespace Microsoft.AspNet.Mvc.Core.Test Assert.Null(controller.Response); } + private IServiceProvider GetServices() + { + var services = new Mock(); + services.Setup(s => s.GetService(typeof(IUrlHelper))) + .Returns(Mock.Of()); + services.Setup(s => s.GetService(typeof(IModelMetadataProvider))) + .Returns(new EmptyModelMetadataProvider()); + services + .Setup(s => s.GetService(typeof(IScopedInstance))) + .Returns(new MockScopedInstance()); + return services.Object; + } + public class TestController { [Activate] public ActionContext ActionContext { get; set; } + [Activate] + public ActionBindingContext BindingContext { get; set; } + [Activate] public HttpContext HttpContext { get; set; } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultInputFormatterSelectorTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultInputFormatterSelectorTests.cs index 4c47ad6f14..4bfd03e724 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultInputFormatterSelectorTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultInputFormatterSelectorTests.cs @@ -17,19 +17,19 @@ namespace Microsoft.AspNet.Mvc.Core.Test { // Arrange var actionContext = GetActionContext(); - var context = new InputFormatterContext(actionContext, typeof(int)); - actionContext.InputFormatters = new List() - { - new TestInputFormatter(false, 0), - new TestInputFormatter(false, 1), - new TestInputFormatter(true, 2), - new TestInputFormatter(true, 3) - }; + var inputFormatters = new List() + { + new TestInputFormatter(false, 0), + new TestInputFormatter(false, 1), + new TestInputFormatter(true, 2), + new TestInputFormatter(true, 3) + }; + var context = new InputFormatterContext(actionContext, typeof(int)); var selector = new DefaultInputFormatterSelector(); // Act - var selectedFormatter = selector.SelectFormatter(context); + var selectedFormatter = selector.SelectFormatter(inputFormatters, context); // Assert var testFormatter = Assert.IsType(selectedFormatter); @@ -38,8 +38,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test private static ActionContext GetActionContext() { - var httpContext = Mock.Of(); - return new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); + return new ActionContext(Mock.Of(), new RouteData(), new ActionDescriptor()); } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/InputObjectBindingTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/InputObjectBindingTests.cs deleted file mode 100644 index e07dca5054..0000000000 --- a/test/Microsoft.AspNet.Mvc.Core.Test/InputObjectBindingTests.cs +++ /dev/null @@ -1,119 +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.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.IO; -using System.Threading.Tasks; -using Microsoft.AspNet.Http; -using Microsoft.AspNet.Mvc.ModelBinding; -using Microsoft.AspNet.Mvc.OptionDescriptors; -using Microsoft.AspNet.Routing; -using Microsoft.Framework.DependencyInjection; -using Microsoft.Framework.OptionsModel; -using Moq; - -namespace Microsoft.AspNet.Mvc -{ - public class InputObjectBindingTests - { - private static ControllerActionInvoker GetControllerActionInvoker( - string input, Type parameterType, IInputFormatter selectedFormatter, string contentType) - { - var mvcOptions = new MvcOptions(); - var setup = new MvcOptionsSetup(); - - setup.Configure(mvcOptions); - var accessor = new Mock>(); - accessor.SetupGet(a => a.Options) - .Returns(mvcOptions); - var validatorProvider = new DefaultModelValidatorProviderProvider( - accessor.Object, Mock.Of(), Mock.Of()); - - Func method = x => 1; - var actionDescriptor = new ControllerActionDescriptor - { - MethodInfo = method.Method, - Parameters = new List - { - new ParameterDescriptor - { - BinderMetadata = new FromBodyAttribute(), - Name = "foo", - ParameterType = parameterType, - } - } - }; - - var metadataProvider = new EmptyModelMetadataProvider(); - var actionContext = GetActionContext( - Encodings.UTF8EncodingWithoutBOM.GetBytes(input), actionDescriptor, contentType); - - var inputFormatterSelector = new Mock(); - inputFormatterSelector.Setup(a => a.SelectFormatter(It.IsAny())) - .Returns(selectedFormatter); - var bindingContext = new ActionBindingContext(actionContext, - metadataProvider, - Mock.Of(), - Mock.Of(), - inputFormatterSelector.Object, - new CompositeModelValidatorProvider(validatorProvider)); - - var actionBindingContextProvider = new Mock(); - actionBindingContextProvider.Setup(p => p.GetActionBindingContextAsync(It.IsAny())) - .Returns(Task.FromResult(bindingContext)); - - var inputFormattersProvider = new Mock(); - inputFormattersProvider.SetupGet(o => o.InputFormatters) - .Returns(new List()); - return new ControllerActionInvoker(actionContext, - Mock.Of>(), - Mock.Of(), - actionDescriptor, - inputFormattersProvider.Object, - Mock.Of()); - } - - private static ActionContext GetActionContext(byte[] contentBytes, - ActionDescriptor actionDescriptor, - string contentType) - { - return new ActionContext(GetHttpContext(contentBytes, contentType), - new RouteData(), - actionDescriptor); - } - - private static HttpContext GetHttpContext(byte[] contentBytes, - string contentType) - { - var request = new Mock(); - var headers = new Mock(); - request.SetupGet(r => r.Headers).Returns(headers.Object); - request.SetupGet(f => f.Body).Returns(new MemoryStream(contentBytes)); - request.SetupGet(f => f.ContentType).Returns(contentType); - - var httpContext = new Mock(); - httpContext.SetupGet(c => c.Request).Returns(request.Object); - httpContext.SetupGet(c => c.Request).Returns(request.Object); - return httpContext.Object; - } - } - - public class Person - { - public string Name { get; set; } - } - - public class User : Person - { - [MinLength(5)] - public string UserName { get; set; } - } - - public class Customers - { - [Required] - public List Users { get; set; } - } -} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/MockModelBinderProvider.cs b/test/Microsoft.AspNet.Mvc.Core.Test/MockModelBinderProvider.cs new file mode 100644 index 0000000000..75f223686b --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/MockModelBinderProvider.cs @@ -0,0 +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.ModelBinding +{ + public class MockModelBinderProvider : IModelBinderProvider + { + public List ModelBinders { get; set; } = new List(); + + IReadOnlyList IModelBinderProvider.ModelBinders { get { return ModelBinders; } } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/MockModelValidatorProviderProvider.cs b/test/Microsoft.AspNet.Mvc.Core.Test/MockModelValidatorProviderProvider.cs new file mode 100644 index 0000000000..8c9b8612df --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/MockModelValidatorProviderProvider.cs @@ -0,0 +1,17 @@ +// 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.ModelBinding +{ + public class MockModelValidatorProviderProvider: IModelValidatorProviderProvider + { + public List ModelValidatorProviders { get; } = new List(); + + IReadOnlyList IModelValidatorProviderProvider.ModelValidatorProviders + { + get { return ModelValidatorProviders; } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/MockScopedInstance.cs b/test/Microsoft.AspNet.Mvc.Core.Test/MockScopedInstance.cs new file mode 100644 index 0000000000..307b14b45e --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/MockScopedInstance.cs @@ -0,0 +1,12 @@ +// 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 Microsoft.Framework.DependencyInjection; + +namespace Microsoft.AspNet.Mvc +{ + public class MockScopedInstance : IScopedInstance + { + public T Value { get; set; } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/MockValueProviderFactoryProvider.cs b/test/Microsoft.AspNet.Mvc.Core.Test/MockValueProviderFactoryProvider.cs new file mode 100644 index 0000000000..f287f4078e --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/MockValueProviderFactoryProvider.cs @@ -0,0 +1,17 @@ +// 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.ModelBinding +{ + public class MockValueProviderFactoryProvider : IValueProviderFactoryProvider + { + public List ValueProviderFactories { get; } = new List(); + + IReadOnlyList IValueProviderFactoryProvider.ValueProviderFactories + { + get { return ValueProviderFactories; } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs index 44f17d7bd1..38fa995e78 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs @@ -52,22 +52,21 @@ namespace Microsoft.AspNet.Mvc.Core.Test public void GetModelBindingContext_ReturnsOnlyIncludedProperties_UsingBindAttributeInclude() { // Arrange - var actionContext = new ActionContext(new RouteContext(Mock.Of()), - Mock.Of()); + var actionContext = new ActionContext( + new RouteContext(Mock.Of()), + Mock.Of()); var metadataProvider = new DataAnnotationsModelMetadataProvider(); var modelMetadata = metadataProvider.GetMetadataForType( modelAccessor: null, modelType: typeof(TypeWithIncludedPropertiesUsingBindAttribute)); - var actionBindingContext = new ActionBindingContext(actionContext, - Mock.Of(), - Mock.Of(), - Mock.Of(), - Mock.Of(), - Mock.Of()); + var actionBindingContext = new ActionBindingContext(); + // Act var context = DefaultControllerActionArgumentBinder.GetModelBindingContext( - modelMetadata, actionBindingContext, Mock.Of()); + modelMetadata, + actionContext, + Mock.Of()); // Assert Assert.True(context.PropertyFilter(context, "IncludedExplicitly1")); @@ -80,23 +79,21 @@ namespace Microsoft.AspNet.Mvc.Core.Test // Arrange var type = typeof(ControllerActionArgumentBinderTests); var methodInfo = type.GetMethod("ParameterWithNoBindAttribute"); - var actionContext = new ActionContext(new RouteContext(Mock.Of()), - Mock.Of()); + + var actionContext = new ActionContext( + new RouteContext(Mock.Of()), + Mock.Of()); var metadataProvider = new DataAnnotationsModelMetadataProvider(); var modelMetadata = metadataProvider.GetMetadataForParameter(modelAccessor: null, methodInfo: methodInfo, parameterName: "parameter"); - var actionBindingContext = new ActionBindingContext(actionContext, - Mock.Of(), - Mock.Of(), - Mock.Of(), - Mock.Of(), - Mock.Of()); // Act var context = DefaultControllerActionArgumentBinder.GetModelBindingContext( - modelMetadata, actionBindingContext, Mock.Of()); + modelMetadata, + actionContext, + Mock.Of()); // Assert Assert.Equal("TypePrefix", context.ModelName); @@ -113,24 +110,21 @@ namespace Microsoft.AspNet.Mvc.Core.Test // Arrange var type = typeof(ControllerActionArgumentBinderTests); var methodInfo = type.GetMethod(actionMethodName); - var actionContext = new ActionContext(new RouteContext(Mock.Of()), - Mock.Of()); + + var actionContext = new ActionContext( + new RouteContext(Mock.Of()), + Mock.Of()); var metadataProvider = new DataAnnotationsModelMetadataProvider(); var modelMetadata = metadataProvider.GetMetadataForParameter(modelAccessor: null, methodInfo: methodInfo, parameterName: "parameter"); - - var actionBindingContext = new ActionBindingContext(actionContext, - Mock.Of(), - Mock.Of(), - Mock.Of(), - Mock.Of(), - Mock.Of()); // Act var context = DefaultControllerActionArgumentBinder.GetModelBindingContext( - modelMetadata, actionBindingContext, Mock.Of()); + modelMetadata, + actionContext, + Mock.Of()); // Assert Assert.Equal(expectedFallToEmptyPrefix, context.FallbackToEmptyPrefix); @@ -147,24 +141,21 @@ namespace Microsoft.AspNet.Mvc.Core.Test // Arrange var type = typeof(ControllerActionArgumentBinderTests); var methodInfo = type.GetMethod(actionMethodName); - var actionContext = new ActionContext(new RouteContext(Mock.Of()), - Mock.Of()); + + var actionContext = new ActionContext( + new RouteContext(Mock.Of()), + Mock.Of()); var metadataProvider = new DataAnnotationsModelMetadataProvider(); var modelMetadata = metadataProvider.GetMetadataForParameter(modelAccessor: null, methodInfo: methodInfo, parameterName: "parameter1"); - - var actionBindingContext = new ActionBindingContext(actionContext, - Mock.Of(), - Mock.Of(), - Mock.Of(), - Mock.Of(), - Mock.Of()); // Act var context = DefaultControllerActionArgumentBinder.GetModelBindingContext( - modelMetadata, actionBindingContext, Mock.Of()); + modelMetadata, + actionContext, + Mock.Of()); // Assert Assert.Equal(expectedFallToEmptyPrefix, context.FallbackToEmptyPrefix); @@ -188,30 +179,33 @@ namespace Microsoft.AspNet.Mvc.Core.Test } } }; - var binder = new Mock(); - binder.Setup(b => b.BindModelAsync(It.IsAny())) - .Returns(Task.FromResult(result: false)); - var actionContext = new ActionContext(new RouteContext(Mock.Of()), - actionDescriptor); - actionContext.Controller = Mock.Of(); - var bindingContext = new ActionBindingContext(actionContext, - Mock.Of(), - binder.Object, - Mock.Of(), - Mock.Of(), - Mock.Of()); - var inputFormattersProvider = new Mock(); - inputFormattersProvider.SetupGet(o => o.InputFormatters) - .Returns(new List()); - var actionBindingContextProvider = new Mock(); - actionBindingContextProvider.Setup(p => p.GetActionBindingContextAsync(It.IsAny())) - .Returns(Task.FromResult(bindingContext)); - var invoker = new DefaultControllerActionArgumentBinder( - actionBindingContextProvider.Object); + var binder = new Mock(); + binder + .Setup(b => b.BindModelAsync(It.IsAny())) + .Returns(Task.FromResult(result: false)); + + var actionContext = new ActionContext( + new RouteContext(Mock.Of()), + actionDescriptor) + { + Controller = Mock.Of(), + }; + + var actionBindingContext = new ActionBindingContext() + { + ModelBinder = binder.Object, + }; + + var inputFormattersProvider = new Mock(); + inputFormattersProvider + .SetupGet(o => o.InputFormatters) + .Returns(new List()); + + var invoker = new DefaultControllerActionArgumentBinder(new DataAnnotationsModelMetadataProvider()); // Act - var result = await invoker.GetActionArgumentsAsync(actionContext); + var result = await invoker.GetActionArgumentsAsync(actionContext, actionBindingContext); // Assert Assert.Empty(result); @@ -226,44 +220,47 @@ namespace Microsoft.AspNet.Mvc.Core.Test { MethodInfo = method.Method, Parameters = new List - { - new ParameterDescriptor - { - Name = "foo", - ParameterType = typeof(string), - } - } + { + new ParameterDescriptor + { + Name = "foo", + ParameterType = typeof(string), + } + }, }; + var value = "Hello world"; - var binder = new Mock(); var metadataProvider = new EmptyModelMetadataProvider(); - binder.Setup(b => b.BindModelAsync(It.IsAny())) - .Callback((ModelBindingContext context) => - { - context.ModelMetadata = metadataProvider.GetMetadataForType(modelAccessor: null, - modelType: typeof(string)); - context.Model = value; - }) - .Returns(Task.FromResult(result: true)); - var actionContext = new ActionContext(new RouteContext(Mock.Of()), - actionDescriptor); - actionContext.Controller = Mock.Of(); - var bindingContext = new ActionBindingContext(actionContext, - metadataProvider, - binder.Object, - Mock.Of(), - Mock.Of(), - Mock.Of()); - var actionBindingContextProvider = new Mock(); - actionBindingContextProvider.Setup(p => p.GetActionBindingContextAsync(It.IsAny())) - .Returns(Task.FromResult(bindingContext)); + var binder = new Mock(); + binder + .Setup(b => b.BindModelAsync(It.IsAny())) + .Callback((ModelBindingContext context) => + { + context.ModelMetadata = metadataProvider.GetMetadataForType( + modelAccessor: null, + modelType: typeof(string)); - var invoker = new DefaultControllerActionArgumentBinder( - actionBindingContextProvider.Object); + context.Model = value; + }) + .Returns(Task.FromResult(result: true)); + + var actionContext = new ActionContext( + new RouteContext(Mock.Of()), + actionDescriptor) + { + Controller = Mock.Of(), + }; + + var actionBindingContext = new ActionBindingContext() + { + ModelBinder = binder.Object, + }; + + var invoker = new DefaultControllerActionArgumentBinder(metadataProvider); // Act - var result = await invoker.GetActionArgumentsAsync(actionContext); + var result = await invoker.GetActionArgumentsAsync(actionContext, actionBindingContext); // Assert Assert.Equal(1, result.Count); @@ -291,6 +288,11 @@ namespace Microsoft.AspNet.Mvc.Core.Test } } + private class Person + { + public string Name { get; set; } + } + private class NonValueProviderBinderMetadataAttribute : Attribute, IBinderMetadata { } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ModelBindingHelperTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ModelBindingHelperTest.cs index 80b2e5c75c..293c6bb8e0 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ModelBindingHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ModelBindingHelperTest.cs @@ -468,10 +468,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test private static IModelBinder GetCompositeBinder(params IModelBinder[] binders) { - var binderProvider = new Mock(); - binderProvider.SetupGet(p => p.ModelBinders) - .Returns(binders); - return new CompositeModelBinder(binderProvider.Object); + return new CompositeModelBinder(binders); } public class User diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultTemplatesUtilities.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultTemplatesUtilities.cs index 693d785edb..ab2eeecebf 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultTemplatesUtilities.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultTemplatesUtilities.cs @@ -12,6 +12,7 @@ using Microsoft.AspNet.Routing; using Microsoft.AspNet.Security.DataProtection; using Microsoft.Framework.OptionsModel; using Moq; +using Microsoft.Framework.DependencyInjection; namespace Microsoft.AspNet.Mvc.Rendering { @@ -143,6 +144,14 @@ namespace Microsoft.AspNet.Mvc.Rendering var httpContext = new DefaultHttpContext(); var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); + var bindingContext = new ActionBindingContext() + { + ValidatorProvider = new DataAnnotationsModelValidatorProvider(), + }; + + var bindingContextAccessor = new MockScopedInstance(); + bindingContextAccessor.Value = bindingContext; + var serviceProvider = new Mock(); serviceProvider .Setup(s => s.GetService(typeof(ICompositeViewEngine))) @@ -157,21 +166,9 @@ namespace Microsoft.AspNet.Mvc.Rendering httpContext.RequestServices = serviceProvider.Object; if (htmlGenerator == null) { - var actionBindingContext = new ActionBindingContext( - actionContext, - provider, - Mock.Of(), - Mock.Of(), - Mock.Of(), - new DataAnnotationsModelValidatorProvider()); - var actionBindingContextProvider = new Mock(); - actionBindingContextProvider - .Setup(c => c.GetActionBindingContextAsync(It.IsAny())) - .Returns(Task.FromResult(actionBindingContext)); - htmlGenerator = new DefaultHtmlGenerator( - actionBindingContextProvider.Object, GetAntiForgeryInstance(), + bindingContextAccessor, provider, urlHelper); } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ViewDataDictionaryTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ViewDataDictionaryTest.cs index 07b01e3904..79edb42df4 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ViewDataDictionaryTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ViewDataDictionaryTest.cs @@ -711,6 +711,11 @@ namespace Microsoft.AspNet.Mvc.Core { } + private class Person + { + public string Name { get; set; } + } + private class TestViewDataDictionary : ViewDataDictionary { public TestViewDataDictionary(IModelMetadataProvider modelMetadataProvider, diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/FiltersTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/FiltersTest.cs index b25743957e..75aa9a7f74 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/FiltersTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/FiltersTest.cs @@ -2,7 +2,10 @@ // 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.Net; +using System.Net.Http; +using System.Text; using System.Threading.Tasks; using Microsoft.AspNet.Builder; using Microsoft.AspNet.TestHost; @@ -22,6 +25,39 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var server = TestServer.Create(_services, _app); var client = server.CreateClient(); + var expected = new string[] + { + "Controller Override - OnAuthorization", + "Global Authorization Filter - OnAuthorization", + "On Controller Authorization Filter - OnAuthorization", + "Authorize Filter On Action - OnAuthorization", + "Controller Override Resource Filter - OnResourceExecuting", + "Global Resource Filter - OnResourceExecuting", + "Controller Resource Filter - OnResourceExecuting", + "Action Resource Filter - OnResourceExecuting", + "Controller Override - OnActionExecuting", + "Global Action Filter - OnActionExecuting", + "On Controller Action Filter - OnActionExecuting", + "On Action Action Filter - OnActionExecuting", + "Executing Action", + "On Action Action Filter - OnActionExecuted", + "On Controller Action Filter - OnActionExecuted", + "Global Action Filter - OnActionExecuted", + "Controller Override - OnActionExecuted", + "Controller Override - OnResultExecuting", + "Global Result Filter - OnResultExecuted", + "On Controller Result Filter - OnResultExecuting", + "On Action Result Filter - OnResultExecuting", + "On Action Result Filter - OnResultExecuted", + "On Controller Result Filter - OnResultExecuted", + "Global Result Filter - OnResultExecuted", + "Controller Override - OnResultExecuted", + "Action Resource Filter - OnResourceExecuted", + "Controller Resource Filter - OnResourceExecuted", + "Global Resource Filter - OnResourceExecuted", + "Controller Override Resource Filter - OnResourceExecuted", + }; + // Act var response = await client.GetAsync("http://localhost/Products/GetPrice/5"); @@ -30,30 +66,15 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var body = await response.Content.ReadAsStringAsync(); - var filters = response.Headers.GetValues("filters"); - Assert.Equal( - "Controller Override - OnAuthorization," + - "Global Authorization Filter - OnAuthorization," + - "On Controller Authorization Filter - OnAuthorization," + - "Authorize Filter On Action - OnAuthorization," + - "Controller Override - OnActionExecuting," + - "Global Action Filter - OnActionExecuting," + - "On Controller Action Filter - OnActionExecuting," + - "On Action Action Filter - OnActionExecuting," + - "Executing Action," + - "On Action Action Filter - OnActionExecuted," + - "On Controller Action Filter - OnActionExecuted," + - "Global Action Filter - OnActionExecuted," + - "Controller Override - OnActionExecuted," + - "Controller Override - OnResultExecuting," + - "Global Result Filter - OnResultExecuted," + - "On Controller Result Filter - OnResultExecuting," + - "On Action Result Filter - OnResultExecuting," + - "On Action Result Filter - OnResultExecuted," + - "On Controller Result Filter - OnResultExecuted," + - "Global Result Filter - OnResultExecuted," + - "Controller Override - OnResultExecuted", - (filters as string[])[0]); + var filters = response.Headers.GetValues("filters").Single().Split(','); + + var i = 0; + foreach (var filter in filters) + { + Assert.Equal(filter, expected[i++]); + } + + Assert.Equal(expected.Length, filters.Length); } [Fact] @@ -531,5 +552,50 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal("Throwing Exception Filter", await response.Content.ReadAsStringAsync()); } + + [Fact] + public async Task ResourceFilter_ChangesInputFormatters_JsonAccepted() + { + // Arrange + var input = "{ sampleInt: 10 }"; + + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + + var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Json"); + request.Content = new StringContent(input, Encoding.UTF8, "application/json"); + + // Act + var response = await client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("10", await response.Content.ReadAsStringAsync()); + } + + [Fact] + public async Task ResourceFilter_ChangesInputFormatters_XmlDenied() + { + // Arrange + var input = + "" + + "10" + + ""; + + // There's nothing that can deserialize the body, so the result contains the default + // value. + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + + var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Json"); + request.Content = new StringContent(input, Encoding.UTF8, "application/xml"); + + // Act + var response = await client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("0", await response.Content.ReadAsStringAsync()); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/CompositeModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/CompositeModelBinderTest.cs index ebc14c4571..cd93e04736 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/CompositeModelBinderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/CompositeModelBinderTest.cs @@ -343,20 +343,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test new TypeConverterModelBinder(), new MutableObjectModelBinder() }; - var binderProviders = new Mock(); - binderProviders.SetupGet(p => p.ModelBinders) - .Returns(binders); - var binder = new CompositeModelBinder(binderProviders.Object); + + var binder = new CompositeModelBinder(binders); return binder; } private static CompositeModelBinder CreateCompositeBinder(IModelBinder mockIntBinder) { - var binderProvider = new Mock(); - binderProvider.SetupGet(p => p.ModelBinders) - .Returns(new[] { mockIntBinder }); - - var shimBinder = new CompositeModelBinder(binderProvider.Object); + var shimBinder = new CompositeModelBinder(new[] { mockIntBinder }); return shimBinder; } diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/KeyValuePairModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/KeyValuePairModelBinderTest.cs index 65e805964c..448f606a5e 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/KeyValuePairModelBinderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/KeyValuePairModelBinderTest.cs @@ -50,10 +50,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test public async Task BindModel_SubBindingSucceeds() { // Arrange - var binderProviders = new Mock(); - binderProviders.SetupGet(b => b.ModelBinders) - .Returns(new[] { CreateStringBinder(), CreateIntBinder() }); - var innerBinder = new CompositeModelBinder(binderProviders.Object); + var innerBinder = new CompositeModelBinder(new[] { CreateStringBinder(), CreateIntBinder() }); var valueProvider = new SimpleHttpValueProvider(); var bindingContext = GetBindingContext(valueProvider, innerBinder); diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/MutableObjectModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/MutableObjectModelBinderTest.cs index 8ac8256f55..c9cc55c768 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/MutableObjectModelBinderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/MutableObjectModelBinderTest.cs @@ -1205,13 +1205,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding private static ModelBindingContext CreateContext(ModelMetadata metadata) { - var provider = new Mock(); - provider.SetupGet(p => p.ModelValidatorProviders) - .Returns(new IModelValidatorProvider[] - { - new DataAnnotationsModelValidatorProvider(), - new DataMemberModelValidatorProvider() - }); + var providers = new IModelValidatorProvider[] + { + new DataAnnotationsModelValidatorProvider(), + new DataMemberModelValidatorProvider() + }; return new ModelBindingContext { @@ -1220,7 +1218,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding ModelName = "theModel", OperationBindingContext = new OperationBindingContext { - ValidatorProvider = new CompositeModelValidatorProvider(provider.Object) + ValidatorProvider = new CompositeModelValidatorProvider(providers) } }; } diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/CompositeModelValidatorProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/CompositeModelValidatorProviderTest.cs index 789e2410e0..4d01252ad7 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/CompositeModelValidatorProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/CompositeModelValidatorProviderTest.cs @@ -22,10 +22,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test var provider2 = new Mock(); provider2.Setup(p => p.GetValidators(It.IsAny())) .Returns(new[] { validator3 }); - var providerProvider = new Mock(); - providerProvider.Setup(p => p.ModelValidatorProviders) - .Returns(new[] { provider1.Object, provider2.Object }); - var compositeModelValidator = new CompositeModelValidatorProvider(providerProvider.Object); + var compositeModelValidator = new CompositeModelValidatorProvider(new[] { provider1.Object, provider2.Object }); var modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType( modelAccessor: null, modelType: typeof(string)); diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultBodyModelValidatorTests.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultBodyModelValidatorTests.cs index 6234649072..67a811f600 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultBodyModelValidatorTests.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultBodyModelValidatorTests.cs @@ -336,13 +336,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding object model, Type type, List excludedTypes = null) { var modelStateDictionary = new ModelStateDictionary(); - var provider = new Mock(); - provider.SetupGet(p => p.ModelValidatorProviders) - .Returns(new IModelValidatorProvider[] - { - new DataAnnotationsModelValidatorProvider(), - new DataMemberModelValidatorProvider() - }); + var providers = new IModelValidatorProvider[] + { + new DataAnnotationsModelValidatorProvider(), + new DataMemberModelValidatorProvider() + }; var modelMetadataProvider = new EmptyModelMetadataProvider(); var excludedValidationTypesPredicate = new List(); @@ -358,7 +356,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding return new ModelValidationContext( modelMetadataProvider, - new CompositeModelValidatorProvider(provider.Object), + new CompositeModelValidatorProvider(providers), modelStateDictionary, new ModelMetadata( provider: modelMetadataProvider, diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/ModelValidationNodeTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/ModelValidationNodeTest.cs index 5bb344a2c6..b9138d6ba4 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/ModelValidationNodeTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/ModelValidationNodeTest.cs @@ -356,12 +356,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding new DataMemberModelValidatorProvider() }; - var provider = new Mock(); - provider.SetupGet(p => p.ModelValidatorProviders) - .Returns(providers); - return new ModelValidationContext(new EmptyModelMetadataProvider(), - new CompositeModelValidatorProvider(provider.Object), + new CompositeModelValidatorProvider(providers), new ModelStateDictionary(), metadata, null); diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TestableHtmlGenerator.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TestableHtmlGenerator.cs index d4c2499831..c1a7b9e30f 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TestableHtmlGenerator.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TestableHtmlGenerator.cs @@ -9,6 +9,7 @@ using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.PipelineCore; using Microsoft.AspNet.Routing; using Microsoft.AspNet.Security.DataProtection; +using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.OptionsModel; using Moq; @@ -26,6 +27,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers public TestableHtmlGenerator(IModelMetadataProvider metadataProvider, IUrlHelper urlHelper) : this( metadataProvider, + Mock.Of>(), urlHelper, validationAttributes: new Dictionary(StringComparer.OrdinalIgnoreCase)) { @@ -33,9 +35,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers public TestableHtmlGenerator( IModelMetadataProvider metadataProvider, + IContextAccessor bindingContextAccessor, IUrlHelper urlHelper, IDictionary validationAttributes) - : base(Mock.Of(), GetAntiForgery(), metadataProvider, urlHelper) + : base(GetAntiForgery(), bindingContextAccessor, metadataProvider, urlHelper) { _validationAttributes = validationAttributes; } diff --git a/test/WebSites/FiltersWebSite/Controllers/JsonOnlyController.cs b/test/WebSites/FiltersWebSite/Controllers/JsonOnlyController.cs new file mode 100644 index 0000000000..5b549c1cff --- /dev/null +++ b/test/WebSites/FiltersWebSite/Controllers/JsonOnlyController.cs @@ -0,0 +1,30 @@ +// 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 Microsoft.AspNet.Mvc; + +namespace FiltersWebSite.Controllers +{ + [Route("Json")] + public class JsonOnlyController : Controller, IResourceFilter + { + [HttpPost] + public string Post([FromBody] DummyClass dummy) + { + return (dummy?.SampleInt ?? 0).ToString(); + } + + public void OnResourceExecuted(ResourceExecutedContext context) + { + } + + public void OnResourceExecuting(ResourceExecutingContext context) + { + var jsonFormatter = context.InputFormatters.OfType().Single(); + + context.InputFormatters.Clear(); + context.InputFormatters.Add(jsonFormatter); + } + } +} \ No newline at end of file diff --git a/test/WebSites/FiltersWebSite/Controllers/ProductsController.cs b/test/WebSites/FiltersWebSite/Controllers/ProductsController.cs index 1730c2e79c..a039fefa6c 100644 --- a/test/WebSites/FiltersWebSite/Controllers/ProductsController.cs +++ b/test/WebSites/FiltersWebSite/Controllers/ProductsController.cs @@ -10,11 +10,13 @@ namespace FiltersWebSite [ControllerResultFilter] [ControllerActionFilter] [ControllerAuthorizationFilter] - public class ProductsController : Controller, IResultFilter, IAuthorizationFilter + [TracingResourceFilter("Controller Resource Filter")] + public class ProductsController : Controller, IResultFilter, IAuthorizationFilter, IResourceFilter { [PassThroughResultFilter] [PassThroughActionFilter] [AuthorizeUser] + [TracingResourceFilter("Action Resource Filter")] public IActionResult GetPrice(int id) { Response.Headers.Append("filters", "Executing Action"); @@ -47,5 +49,19 @@ namespace FiltersWebSite { context.HttpContext.Response.Headers.Append("filters", "Controller Override - OnAuthorization"); } + + public void OnResourceExecuting(ResourceExecutingContext context) + { + context.HttpContext.Response.Headers.Append( + "filters", + "Controller Override Resource Filter - OnResourceExecuting"); + } + + public void OnResourceExecuted(ResourceExecutedContext context) + { + context.HttpContext.Response.Headers.Append( + "filters", + "Controller Override Resource Filter - OnResourceExecuted"); + } } } \ No newline at end of file diff --git a/test/WebSites/FiltersWebSite/Filters/TracingResourceFilter.cs b/test/WebSites/FiltersWebSite/Filters/TracingResourceFilter.cs new file mode 100644 index 0000000000..ae60adc861 --- /dev/null +++ b/test/WebSites/FiltersWebSite/Filters/TracingResourceFilter.cs @@ -0,0 +1,32 @@ +// 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 Microsoft.AspNet.Mvc; + +namespace FiltersWebSite +{ + public class TracingResourceFilter : Attribute, IResourceFilter + { + public TracingResourceFilter(string name) + { + Name = name; + } + + public string Name { get; } + + public void OnResourceExecuted(ResourceExecutedContext context) + { + context.HttpContext.Response.Headers.Append( + "filters", + Name + " - OnResourceExecuted"); + } + + public void OnResourceExecuting(ResourceExecutingContext context) + { + context.HttpContext.Response.Headers.Append( + "filters", + Name + " - OnResourceExecuting"); + } + } +} \ No newline at end of file diff --git a/test/WebSites/FiltersWebSite/Startup.cs b/test/WebSites/FiltersWebSite/Startup.cs index 6449d67012..f95b7b6644 100644 --- a/test/WebSites/FiltersWebSite/Startup.cs +++ b/test/WebSites/FiltersWebSite/Startup.cs @@ -25,6 +25,7 @@ namespace FiltersWebSite options.Filters.Add(new GlobalActionFilter()); options.Filters.Add(new GlobalResultFilter()); options.Filters.Add(new GlobalAuthorizationFilter()); + options.Filters.Add(new TracingResourceFilter("Global Resource Filter")); }); });