diff --git a/src/Microsoft.AspNet.Mvc.Core/ControllerActionArgumentBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ControllerActionArgumentBinder.cs new file mode 100644 index 0000000000..b3d854a079 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/ControllerActionArgumentBinder.cs @@ -0,0 +1,85 @@ +// 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.Linq; +using System.Threading.Tasks; +using Microsoft.AspNet.Mvc.Core; +using Microsoft.AspNet.Mvc.ModelBinding; + +namespace Microsoft.AspNet.Mvc +{ + /// + /// Provides a default implementation of . + /// Uses ModelBinding to populate action parameters. + /// + public class DefaultControllerActionArgumentBinder : IControllerActionArgumentBinder + { + private readonly IBodyModelValidator _modelValidator; + private readonly IActionBindingContextProvider _bindingContextProvider; + + public DefaultControllerActionArgumentBinder(IActionBindingContextProvider bindingContextProvider, + IBodyModelValidator modelValidator) + { + _bindingContextProvider = bindingContextProvider; + _modelValidator = modelValidator; + } + + public async Task> GetActionArgumentsAsync(ActionContext actionContext) + { + var actionBindingContext = await _bindingContextProvider.GetActionBindingContextAsync(actionContext); + var metadataProvider = actionBindingContext.MetadataProvider; + var parameters = actionContext.ActionDescriptor.Parameters; + var actionDescriptor = actionContext.ActionDescriptor as ControllerActionDescriptor; + if (actionDescriptor == null) + { + throw new ArgumentException( + Resources.FormatActionDescriptorMustBeBasedOnControllerAction( + typeof(ControllerActionDescriptor)), + nameof(actionContext)); + } + + var actionMethodInfo = actionDescriptor.MethodInfo; + var parameterMetadatas = metadataProvider.GetMetadataForParameters(actionMethodInfo); + + var actionArguments = new Dictionary(StringComparer.Ordinal); + await PopulateActionArgumentsAsync(parameterMetadatas, actionBindingContext, actionArguments); + return actionArguments; + } + + private async Task PopulateActionArgumentsAsync(IEnumerable modelMetadatas, + ActionBindingContext actionBindingContext, + IDictionary invocationInfo) + { + var bodyBoundParameterCount = modelMetadatas.Count( + modelMetadata => modelMetadata.Marker is IBodyBinderMarker); + if (bodyBoundParameterCount > 1) + { + throw new InvalidOperationException(Resources.MultipleBodyParametersAreNotAllowed); + } + + foreach (var modelMetadata in modelMetadatas) + { + var parameterType = modelMetadata.ModelType; + var modelBindingContext = new ModelBindingContext + { + ModelName = modelMetadata.PropertyName, + ModelMetadata = modelMetadata, + ModelState = actionBindingContext.ActionContext.ModelState, + ModelBinder = actionBindingContext.ModelBinder, + ValidatorProvider = actionBindingContext.ValidatorProvider, + MetadataProvider = actionBindingContext.MetadataProvider, + HttpContext = actionBindingContext.ActionContext.HttpContext, + FallbackToEmptyPrefix = true, + ValueProvider = actionBindingContext.ValueProvider, + }; + + if (await actionBindingContext.ModelBinder.BindModelAsync(modelBindingContext)) + { + invocationInfo[modelMetadata.PropertyName] = modelBindingContext.Model; + } + } + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/ControllerActionInvoker.cs b/src/Microsoft.AspNet.Mvc.Core/ControllerActionInvoker.cs index 5d77e53ff1..6211c156cf 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ControllerActionInvoker.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ControllerActionInvoker.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Mvc.Core; @@ -15,19 +16,20 @@ namespace Microsoft.AspNet.Mvc private readonly ControllerActionDescriptor _descriptor; private readonly IControllerFactory _controllerFactory; private readonly IInputFormattersProvider _inputFormattersProvider; + private readonly IControllerActionArgumentBinder _actionInvocationProvider; public ControllerActionInvoker([NotNull] ActionContext actionContext, - [NotNull] IActionBindingContextProvider bindingContextProvider, [NotNull] INestedProviderManager filterProvider, [NotNull] IControllerFactory controllerFactory, [NotNull] ControllerActionDescriptor descriptor, [NotNull] IInputFormattersProvider inputFormattersProvider, - [NotNull] IBodyModelValidator modelValidator) - : base(actionContext, bindingContextProvider, filterProvider, modelValidator) + [NotNull] IControllerActionArgumentBinder controllerActionArgumentBinder) + : base(actionContext, filterProvider) { _descriptor = descriptor; _controllerFactory = controllerFactory; _inputFormattersProvider = inputFormattersProvider; + _actionInvocationProvider = controllerActionArgumentBinder; if (descriptor.MethodInfo == null) { throw new ArgumentException( @@ -67,6 +69,11 @@ namespace Microsoft.AspNet.Mvc return actionResult; } + protected override Task> GetActionArgumentsAsync(ActionContext context) + { + return _actionInvocationProvider.GetActionArgumentsAsync(context); + } + // Marking as internal for Unit Testing purposes. internal static IActionResult CreateActionResult([NotNull] Type declaredReturnType, object actionReturnValue) { diff --git a/src/Microsoft.AspNet.Mvc.Core/ControllerActionInvokerProvider.cs b/src/Microsoft.AspNet.Mvc.Core/ControllerActionInvokerProvider.cs index 6cb0d75487..3706723041 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ControllerActionInvokerProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ControllerActionInvokerProvider.cs @@ -10,22 +10,19 @@ namespace Microsoft.AspNet.Mvc public class ControllerActionInvokerProvider : IActionInvokerProvider { private readonly IControllerFactory _controllerFactory; - private readonly IActionBindingContextProvider _bindingProvider; private readonly IInputFormattersProvider _inputFormattersProvider; private readonly INestedProviderManager _filterProvider; - private readonly IBodyModelValidator _modelValidator; + private readonly IControllerActionArgumentBinder _actionInvocationInfoProvider; public ControllerActionInvokerProvider(IControllerFactory controllerFactory, - IActionBindingContextProvider bindingProvider, IInputFormattersProvider inputFormattersProvider, INestedProviderManager filterProvider, - IBodyModelValidator modelValidator) + IControllerActionArgumentBinder actionInvocationInfoProvider) { _controllerFactory = controllerFactory; - _bindingProvider = bindingProvider; _inputFormattersProvider = inputFormattersProvider; _filterProvider = filterProvider; - _modelValidator = modelValidator; + _actionInvocationInfoProvider = actionInvocationInfoProvider; } public int Order @@ -41,12 +38,11 @@ namespace Microsoft.AspNet.Mvc { context.Result = new ControllerActionInvoker( context.ActionContext, - _bindingProvider, _filterProvider, _controllerFactory, actionDescriptor, _inputFormattersProvider, - _modelValidator); + _actionInvocationInfoProvider); } callNext(); diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerFactory.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerFactory.cs index 8eb6c5f674..898de4bf8e 100644 --- a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerFactory.cs +++ b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerFactory.cs @@ -28,7 +28,7 @@ namespace Microsoft.AspNet.Mvc if (actionDescriptor == null) { throw new ArgumentException( - Resources.FormatDefaultControllerFactory_ActionDescriptorMustBeReflected( + Resources.FormatActionDescriptorMustBeBasedOnControllerAction( typeof(ControllerActionDescriptor)), "actionContext"); } diff --git a/src/Microsoft.AspNet.Mvc.Core/FilterActionInvoker.cs b/src/Microsoft.AspNet.Mvc.Core/FilterActionInvoker.cs index 23b88b19bc..6353c5d622 100644 --- a/src/Microsoft.AspNet.Mvc.Core/FilterActionInvoker.cs +++ b/src/Microsoft.AspNet.Mvc.Core/FilterActionInvoker.cs @@ -15,9 +15,7 @@ namespace Microsoft.AspNet.Mvc { public abstract class FilterActionInvoker : IActionInvoker { - private readonly IActionBindingContextProvider _bindingProvider; private readonly INestedProviderManager _filterProvider; - private readonly IBodyModelValidator _modelValidator; private IFilter[] _filters; private FilterCursor _cursor; @@ -34,20 +32,18 @@ namespace Microsoft.AspNet.Mvc public FilterActionInvoker( [NotNull] ActionContext actionContext, - [NotNull] IActionBindingContextProvider bindingContextProvider, - [NotNull] INestedProviderManager filterProvider, - [NotNull] IBodyModelValidator modelValidator) + [NotNull] INestedProviderManager filterProvider) { ActionContext = actionContext; - _bindingProvider = bindingContextProvider; _filterProvider = filterProvider; - _modelValidator = modelValidator; } protected ActionContext ActionContext { get; private set; } protected abstract Task InvokeActionAsync(ActionExecutingContext actionExecutingContext); + protected abstract Task> GetActionArgumentsAsync([NotNull] ActionContext context); + public virtual async Task InvokeAsync() { _filters = GetFilters(); @@ -220,80 +216,11 @@ namespace Microsoft.AspNet.Mvc private async Task InvokeActionMethodWithFilters() { _cursor.SetStage(FilterStage.ActionFilters); - - var arguments = await GetActionArguments(ActionContext.ModelState); + var arguments = await GetActionArgumentsAsync(ActionContext); _actionExecutingContext = new ActionExecutingContext(ActionContext, _filters, arguments); - await InvokeActionMethodFilter(); } - internal async Task> GetActionArguments(ModelStateDictionary modelState) - { - var actionBindingContext = await _bindingProvider.GetActionBindingContextAsync(ActionContext); - var parameters = ActionContext.ActionDescriptor.Parameters; - var metadataProvider = actionBindingContext.MetadataProvider; - var parameterValues = new Dictionary(parameters.Count, StringComparer.Ordinal); - - for (var i = 0; i < parameters.Count; i++) - { - var parameter = parameters[i]; - var parameterType = parameter.BodyParameterInfo != null ? - parameter.BodyParameterInfo.ParameterType : parameter.ParameterBindingInfo.ParameterType; - - if (parameter.BodyParameterInfo != null) - { - var formatterContext = new InputFormatterContext(actionBindingContext.ActionContext, - parameterType); - var inputFormatter = actionBindingContext.InputFormatterSelector.SelectFormatter( - formatterContext); - if (inputFormatter == null) - { - var request = ActionContext.HttpContext.Request; - var unsupportedContentType = Resources.FormatUnsupportedContentType(request.ContentType); - ActionContext.ModelState.AddModelError(parameter.Name, unsupportedContentType); - } - else - { - parameterValues[parameter.Name] = await inputFormatter.ReadAsync(formatterContext); - var modelMetadata = - metadataProvider.GetMetadataForType(modelAccessor: null, modelType: parameterType); - modelMetadata.Model = parameterValues[parameter.Name]; - - // Validate the generated object - var validationContext = new ModelValidationContext(metadataProvider, - actionBindingContext.ValidatorProvider, - modelState, - modelMetadata, - containerMetadata: null); - _modelValidator.Validate(validationContext, parameter.Name); - } - } - else - { - var modelMetadata = - metadataProvider.GetMetadataForType(modelAccessor: null, modelType: parameterType); - var modelBindingContext = new ModelBindingContext - { - ModelName = parameter.Name, - ModelState = modelState, - ModelMetadata = modelMetadata, - ModelBinder = actionBindingContext.ModelBinder, - ValueProvider = actionBindingContext.ValueProvider, - ValidatorProvider = actionBindingContext.ValidatorProvider, - MetadataProvider = metadataProvider, - HttpContext = actionBindingContext.ActionContext.HttpContext, - FallbackToEmptyPrefix = true - }; - if (await actionBindingContext.ModelBinder.BindModelAsync(modelBindingContext)) - { - parameterValues[parameter.Name] = modelBindingContext.Model; - } - } - } - - return parameterValues; - } - private async Task InvokeActionMethodFilter() { Contract.Assert(_actionExecutingContext != null); diff --git a/src/Microsoft.AspNet.Mvc.Core/IControllerActionArgumentBinder.cs b/src/Microsoft.AspNet.Mvc.Core/IControllerActionArgumentBinder.cs new file mode 100644 index 0000000000..51dc2ed0e8 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/IControllerActionArgumentBinder.cs @@ -0,0 +1,21 @@ +// 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 System.Threading.Tasks; + +namespace Microsoft.AspNet.Mvc +{ + /// + /// Provides a dictionary of action arguments. + /// + public interface IControllerActionArgumentBinder + { + /// + /// Returns a dictionary of representing the parameter-argument name-value pairs, + /// which can be used to invoke the action. + /// + /// The action context assoicated with the current action. + Task> GetActionArgumentsAsync([NotNull] ActionContext context); + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs new file mode 100644 index 0000000000..0cf768ddf7 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs @@ -0,0 +1,56 @@ +// 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.Linq; +using System.Threading.Tasks; +using Microsoft.AspNet.Mvc.Core; +using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.Framework.DependencyInjection; + +namespace Microsoft.AspNet.Mvc +{ + /// + /// Represents a model binder which understands and uses + /// InputFomatters to bind the model to request's body. + /// + public class BodyModelBinder : MarkerAwareBinder + { + private readonly ActionContext _actionContext; + private readonly IInputFormatterSelector _formatterSelector; + private readonly IBodyModelValidator _bodyModelValidator; + + public BodyModelBinder([NotNull] IContextAccessor context, + [NotNull] IInputFormatterSelector selector, + [NotNull] IBodyModelValidator bodyModelValidator) + { + _actionContext = context.Value; + _formatterSelector = selector; + _bodyModelValidator = bodyModelValidator; + } + + protected override async Task BindAsync(ModelBindingContext bindingContext, IBodyBinderMarker marker) + { + var formatterContext = new InputFormatterContext(_actionContext, bindingContext.ModelType); + var formatter = _formatterSelector.SelectFormatter(formatterContext); + + if (formatter == null) + { + var unsupportedContentType = Resources.FormatUnsupportedContentType( + bindingContext.HttpContext.Request.ContentType); + bindingContext.ModelState.AddModelError(bindingContext.ModelName, unsupportedContentType); + + // Should always return true so that the model binding process ends here. + return true; + } + + bindingContext.Model = await formatter.ReadAsync(formatterContext); + + // Validate the deserialized object + var validationContext = new ModelValidationContext(bindingContext, bindingContext.ModelMetadata); + _bodyModelValidator.Validate(validationContext, bindingContext.ModelName); + return true; + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/ParameterDescriptor.cs b/src/Microsoft.AspNet.Mvc.Core/ParameterDescriptor.cs index e29fd00ace..39610e508a 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ParameterDescriptor.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ParameterDescriptor.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNet.Mvc.ModelBinding; namespace Microsoft.AspNet.Mvc { @@ -11,6 +12,8 @@ namespace Microsoft.AspNet.Mvc public bool IsOptional { get; set; } + public Type ParameterType { get; set; } + public ParameterBindingInfo ParameterBindingInfo { get; set; } public BodyParameterInfo BodyParameterInfo { get; set; } diff --git a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs index db667803ed..c5c0401072 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs @@ -10,6 +10,22 @@ namespace Microsoft.AspNet.Mvc.Core private static readonly ResourceManager _resourceManager = new ResourceManager("Microsoft.AspNet.Mvc.Core.Resources", typeof(Resources).GetTypeInfo().Assembly); + /// + /// More than one parameter is bound to the HTTP request's content. + /// + internal static string MultipleBodyParametersAreNotAllowed + { + get { return GetString("MultipleBodyParametersAreNotAllowed"); } + } + + /// + /// More than one parameter is bound to the HTTP request's content. + /// + internal static string FormatMultipleBodyParametersAreNotAllowed() + { + return GetString("MultipleBodyParametersAreNotAllowed"); + } + /// /// The provided anti-forgery token failed a custom data check. /// @@ -413,17 +429,17 @@ namespace Microsoft.AspNet.Mvc.Core /// /// The action descriptor must be of type '{0}'. /// - internal static string DefaultControllerFactory_ActionDescriptorMustBeReflected + internal static string ActionDescriptorMustBeBasedOnControllerAction { - get { return GetString("DefaultControllerFactory_ActionDescriptorMustBeReflected"); } + get { return GetString("ActionDescriptorMustBeBasedOnControllerAction"); } } /// /// The action descriptor must be of type '{0}'. /// - internal static string FormatDefaultControllerFactory_ActionDescriptorMustBeReflected(object p0) + internal static string FormatActionDescriptorMustBeBasedOnControllerAction(object p0) { - return string.Format(CultureInfo.CurrentCulture, GetString("DefaultControllerFactory_ActionDescriptorMustBeReflected"), p0); + return string.Format(CultureInfo.CurrentCulture, GetString("ActionDescriptorMustBeBasedOnControllerAction"), p0); } /// diff --git a/src/Microsoft.AspNet.Mvc.Core/Resources.resx b/src/Microsoft.AspNet.Mvc.Core/Resources.resx index 38e275f72d..14ff825342 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.Core/Resources.resx @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + More than one parameter is bound to the HTTP request's content. + The provided anti-forgery token failed a custom data check. @@ -192,7 +195,7 @@ An action invoker could not be created for action '{0}'. - + The action descriptor must be of type '{0}'. diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/FromBodyAttribute.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/FromBodyAttribute.cs new file mode 100644 index 0000000000..da41941d13 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/FromBodyAttribute.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; +using Microsoft.AspNet.Mvc.ModelBinding; + +namespace Microsoft.AspNet.Mvc +{ + /// + /// This attribute is used on action parameters to indicate + /// they are bound from the body of the incoming request. + /// + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] + public class FromBodyAttribute : Attribute, IBodyBinderMarker + { + } +} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/FromFormAttribute.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/FromFormAttribute.cs new file mode 100644 index 0000000000..ea3673945e --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/FromFormAttribute.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; +using Microsoft.AspNet.Mvc.ModelBinding; + +namespace Microsoft.AspNet.Mvc +{ + /// + /// This attribute is used on action parameters to indicate that + /// they will be bound using form data of the incoming request. + /// + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] + public class FromFormAttribute : Attribute, IFormDataMarker + { + } +} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/FromQueryAttribute.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/FromQueryAttribute.cs new file mode 100644 index 0000000000..5509419dd7 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/FromQueryAttribute.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; +using Microsoft.AspNet.Mvc.ModelBinding; + +namespace Microsoft.AspNet.Mvc +{ + /// + /// This attribute is used on action parameters to indicate that + /// they will be bound using query data of the incoming request. + /// + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] + public class FromQueryAttribute : Attribute, IQueryBinderMarker + { + } +} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/FromRouteAttribute.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/FromRouteAttribute.cs new file mode 100644 index 0000000000..c4d5fc4da2 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/FromRouteAttribute.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; +using Microsoft.AspNet.Mvc.ModelBinding; + +namespace Microsoft.AspNet.Mvc +{ + /// + /// This attribute is used on action parameters to indicate that + /// they will be bound using route data of the incoming request. + /// + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] + public class FromRouteAttribute : Attribute, IRouteDataMarker + { + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/FromBodyAttribute.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/IBinderMarker.cs similarity index 51% rename from src/Microsoft.AspNet.Mvc.Core/ParameterBinding/FromBodyAttribute.cs rename to src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/IBinderMarker.cs index 8aa0e27b18..568e9c7685 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/FromBodyAttribute.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/IBinderMarker.cs @@ -3,10 +3,12 @@ using System; -namespace Microsoft.AspNet.Mvc +namespace Microsoft.AspNet.Mvc.ModelBinding { - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class FromBodyAttribute : Attribute + /// + /// Represents a marker used to identify a particular binder applies to a model. + /// + public interface IBinderMarker { } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/IBodyBinderMarker.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/IBodyBinderMarker.cs new file mode 100644 index 0000000000..5ade36c809 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/IBodyBinderMarker.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. + + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + /// + /// Represents a marker used to identify a binder which can bind request body to a model. + /// + public interface IBodyBinderMarker : IBinderMarker + { + } +} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/IFormDataMarker.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/IFormDataMarker.cs new file mode 100644 index 0000000000..7ee357c3ea --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/IFormDataMarker.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. + + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + /// + /// Represents a marker used to identify a binder which can bind form data to a model. + /// + public interface IFormDataMarker : IValueBinderMarker + { + } +} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/IQueryBinderMarker.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/IQueryBinderMarker.cs new file mode 100644 index 0000000000..712311ffaf --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/IQueryBinderMarker.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. + + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + /// + /// Represents a marker used to identify a binder which can bind query data to a model. + /// + public interface IQueryBinderMarker : IValueBinderMarker + { + } +} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/IRouteDataMarker.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/IRouteDataMarker.cs new file mode 100644 index 0000000000..a0784c2ab7 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/IRouteDataMarker.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. + + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + /// + /// Represents a marker used to identify a binder which can bind route data to a model. + /// + public interface IRouteDataMarker : IValueBinderMarker + { + } +} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/IValueBinderMarker.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/IValueBinderMarker.cs new file mode 100644 index 0000000000..0f65cb1bca --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMarkers/IValueBinderMarker.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. + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + /// + /// Represents a binder marker which identifies a binder which is based on a value provider. + /// + public interface IValueBinderMarker : IBinderMarker + { + } +} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs index 8bd9d52328..15c312eb29 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; +using System.Globalization; +using System.Linq; using System.Threading.Tasks; namespace Microsoft.AspNet.Mvc.ModelBinding @@ -132,7 +134,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding ValidatorProvider = oldBindingContext.ValidatorProvider, MetadataProvider = oldBindingContext.MetadataProvider, ModelBinder = oldBindingContext.ModelBinder, - HttpContext = oldBindingContext.HttpContext + HttpContext = oldBindingContext.HttpContext, }; // validation is expensive to create, so copy it over if we can @@ -141,6 +143,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding newBindingContext.ValidationNode = oldBindingContext.ValidationNode; } + // look at the value providers and see if they need to be restricted. + var marker = oldBindingContext.ModelMetadata.Marker as IValueBinderMarker; + if (marker != null) + { + var valueProvider = oldBindingContext.ValueProvider as IMarkerAwareValueProvider; + if (valueProvider != null) + { + newBindingContext.ValueProvider = valueProvider.Filter(marker); + } + } + return newBindingContext; } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/IMarkerAwareBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/IMarkerAwareBinder.cs new file mode 100644 index 0000000000..b0b67a86bc --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/IMarkerAwareBinder.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. + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + /// + /// An which is aware of . + /// + public interface IMarkerAwareBinder : IModelBinder + { + } +} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MarkerAwareBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MarkerAwareBinder.cs new file mode 100644 index 0000000000..dd37f83238 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MarkerAwareBinder.cs @@ -0,0 +1,35 @@ +// 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.ModelBinding +{ + /// + /// Represents an which can select itself based on the + /// . + /// + /// Represents a type implementing + public abstract class MarkerAwareBinder : IMarkerAwareBinder + where TBinderMarker : IBinderMarker + { + /// + /// Async function which does the actual binding to bind to a particular model. + /// + /// The binding context which has the object to be bound. + /// The associated with the current binder. + /// A Task with a bool implying the success or failure of the operation. + protected abstract Task BindAsync(ModelBindingContext bindingContext, TBinderMarker marker); + + public Task BindModelAsync(ModelBindingContext context) + { + if (context.ModelMetadata.Marker is TBinderMarker) + { + var marker = (TBinderMarker)context.ModelMetadata.Marker; + return BindAsync(context, marker); + } + + return Task.FromResult(false); + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/AssociatedMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/AssociatedMetadataProvider.cs index 2e23e34724..1e1508e767 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/AssociatedMetadataProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/AssociatedMetadataProvider.cs @@ -46,6 +46,36 @@ namespace Microsoft.AspNet.Mvc.ModelBinding return CreateMetadataFromPrototype(prototype, modelAccessor); } + public IEnumerable GetMetadataForParameters([NotNull] MethodInfo methodInfo) + { + var parameters = methodInfo.GetParameters(); + foreach (var parameter in parameters) + { + // Name can be null if the methodinfo represents an open delegate. + if (!string.IsNullOrEmpty(parameter.Name)) + { + yield return GetMetadataForParameterCore( + modelAccessor: null, parameterName: parameter.Name, parameter: parameter); + } + } + } + + public ModelMetadata GetMetadataForParameter( + Func modelAccessor, + [NotNull] MethodInfo methodInfo, + [NotNull] string parameterName) + { + var parameter = methodInfo.GetParameters().FirstOrDefault( + param => StringComparer.Ordinal.Equals(param.Name, parameterName)); + if (parameter == null) + { + var message = Resources.FormatCommon_ParameterNotFound(parameterName); + throw new ArgumentException(message, nameof(parameterName)); + } + + return GetMetadataForParameterCore(modelAccessor, parameterName, parameter); + } + // Override for creating the prototype metadata (without the accessor) protected abstract TModelMetadata CreateMetadataPrototype(IEnumerable attributes, Type containerType, @@ -56,6 +86,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding protected abstract TModelMetadata CreateMetadataFromPrototype(TModelMetadata prototype, Func modelAccessor); + private ModelMetadata GetMetadataForParameterCore(Func modelAccessor, + string parameterName, + ParameterInfo parameter) + { + var parameterInfo = + CreateParameterInfo(parameter.ParameterType, + parameter.GetCustomAttributes(), + parameterName); + var typePrototype = GetTypeInformation(parameter.ParameterType).Prototype; + return CreateMetadataFromPrototype(parameterInfo.Prototype, modelAccessor); + } + private IEnumerable GetMetadataForPropertiesCore(object container, Type containerType) { var typeInfo = GetTypeInformation(containerType); @@ -78,6 +120,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { metadata.IsReadOnly = true; } + return metadata; } @@ -138,6 +181,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding }; } + private ParameterInformation CreateParameterInfo(Type parameterType, IEnumerable attributes, string parameterName) + { + var metadataProtoType = CreateMetadataPrototype(attributes: attributes, + containerType: null, + modelType: parameterType, + propertyName: parameterName); + + return new ParameterInformation + { + Prototype = metadataProtoType + }; + } + + private sealed class ParameterInformation + { + public TModelMetadata Prototype { get; set; } + } + private sealed class TypeInformation { public TModelMetadata Prototype { get; set; } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsModelMetadata.cs index c9622d8608..64171d9f57 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsModelMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsModelMetadata.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Linq; using System.Reflection; namespace Microsoft.AspNet.Mvc.ModelBinding @@ -32,6 +33,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding propertyName, new CachedDataAnnotationsMetadataAttributes(attributes)) { + Marker = attributes.OfType().FirstOrDefault(); } protected override bool ComputeConvertEmptyStringToNull() diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedModelMetadata.cs index 171b137bd9..e98a6c2b74 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedModelMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedModelMetadata.cs @@ -55,7 +55,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { CacheKey = prototype.CacheKey; PrototypeCache = prototype.PrototypeCache; - + Marker = prototype.Marker; _isComplexType = prototype.IsComplexType; _isComplexTypeComputed = true; } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IModelMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IModelMetadataProvider.cs index 6f9d2b4c05..3eb382883b 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IModelMetadataProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IModelMetadataProvider.cs @@ -3,15 +3,20 @@ using System; using System.Collections.Generic; +using System.Reflection; namespace Microsoft.AspNet.Mvc.ModelBinding { public interface IModelMetadataProvider { - IEnumerable GetMetadataForProperties(object container, Type containerType); + IEnumerable GetMetadataForProperties(object container, [NotNull] Type containerType); - ModelMetadata GetMetadataForProperty(Func modelAccessor, Type containerType, string propertyName); + ModelMetadata GetMetadataForProperty(Func modelAccessor, [NotNull] Type containerType, [NotNull] string propertyName); - ModelMetadata GetMetadataForType(Func modelAccessor, Type modelType); + ModelMetadata GetMetadataForType(Func modelAccessor, [NotNull] Type modelType); + + IEnumerable GetMetadataForParameters([NotNull] MethodInfo methodInfo); + + ModelMetadata GetMetadataForParameter(Func modelAccessor, [NotNull] MethodInfo methodInfo, [NotNull] string parameterName); } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs index ee8faf91c2..8fd70d4950 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs @@ -48,6 +48,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding _isRequired = !modelType.AllowsNullValue(); } + /// + /// Gets or sets a binder marker for this model. + /// + public IBinderMarker Marker { get; set; } + public Type ContainerType { get { return _containerType; } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Properties/Resources.Designer.cs index feb331ddc9..12280b6b65 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Properties/Resources.Designer.cs @@ -42,6 +42,22 @@ namespace Microsoft.AspNet.Mvc.ModelBinding return string.Format(CultureInfo.CurrentCulture, GetString("Common_PropertyNotFound"), p0, p1); } + /// + /// The parameter '{0}' could not be found. + /// + internal static string Common_ParameterNotFound + { + get { return GetString("Common_ParameterNotFound"); } + } + + /// + /// The parameter '{0}' could not be found. + /// + internal static string FormatCommon_ParameterNotFound(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("Common_ParameterNotFound"), p0); + } + /// /// The type '{0}' must have a public constructor which accepts a single parameter of type '{1}'. /// diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Resources.resx b/src/Microsoft.AspNet.Mvc.ModelBinding/Resources.resx index 7d53d5b543..a34683263e 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Resources.resx @@ -123,6 +123,9 @@ The property {0}.{1} could not be found. + + The parameter '{0}' could not be found. + The type '{0}' must have a public constructor which accepts a single parameter of type '{1}'. diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/CompositeValueProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/CompositeValueProvider.cs index 38f6c87bff..a7f925c75f 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/CompositeValueProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/CompositeValueProvider.cs @@ -9,7 +9,7 @@ using System.Threading.Tasks; namespace Microsoft.AspNet.Mvc.ModelBinding { - public class CompositeValueProvider : Collection, IEnumerableValueProvider + public class CompositeValueProvider : Collection, IEnumerableValueProvider, IMarkerAwareValueProvider { public CompositeValueProvider() : base() @@ -79,5 +79,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { base.SetItem(index, item); } + + public IValueProvider Filter(IValueBinderMarker valueBinderMarker) + { + var filteredValueProviders = new List(); + foreach (var valueProvider in this.OfType()) + { + var result = valueProvider.Filter(valueBinderMarker); + if (result != null) + { + filteredValueProviders.Add(result); + } + } + + return new CompositeValueProvider(filteredValueProviders); + } } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/DictionaryBasedValueProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/DictionaryBasedValueProvider.cs index 270d4aa35a..361d5ff64d 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/DictionaryBasedValueProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/DictionaryBasedValueProvider.cs @@ -7,7 +7,8 @@ using System.Threading.Tasks; namespace Microsoft.AspNet.Mvc.ModelBinding { - public class DictionaryBasedValueProvider : IValueProvider + public class DictionaryBasedValueProvider : MakerAwareValueProvider + where TBinderMarker : IValueBinderMarker { private readonly IDictionary _values; @@ -16,12 +17,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding _values = values; } - public Task ContainsPrefixAsync(string key) + public override Task ContainsPrefixAsync(string key) { return Task.FromResult(_values.ContainsKey(key)); } - public Task GetValueAsync([NotNull] string key) + public override Task GetValueAsync([NotNull] string key) { object value; ValueProviderResult result; diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/FormValueProviderFactory.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/FormValueProviderFactory.cs index e18bed1260..b3237f6469 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/FormValueProviderFactory.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/FormValueProviderFactory.cs @@ -20,7 +20,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding if (IsSupportedContentType(request)) { var culture = GetCultureInfo(request); - return new ReadableStringCollectionValueProvider(() => request.GetFormAsync(), culture); + return new ReadableStringCollectionValueProvider(() => request.GetFormAsync(), culture); } return null; diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/IMarkerAwareValueProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/IMarkerAwareValueProvider.cs new file mode 100644 index 0000000000..ec1850dd3c --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/IMarkerAwareValueProvider.cs @@ -0,0 +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. + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + /// + /// A value provider which is aware of . + /// + public interface IMarkerAwareValueProvider : IValueProvider + { + /// + /// Filters the value provider based on . + /// + /// The associated with a model. + /// The filtered value provider. + IValueProvider Filter([NotNull] IValueBinderMarker valueBinderMarker); + } +} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/MarkerAwareValueProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/MarkerAwareValueProvider.cs new file mode 100644 index 0000000000..47b37f50a5 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/MarkerAwareValueProvider.cs @@ -0,0 +1,31 @@ +// 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; + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + /// + /// A value provider which can filter + /// based on . + /// + /// Represents a type implementing + public abstract class MakerAwareValueProvider : IMarkerAwareValueProvider + where TBinderMarker : IValueBinderMarker + { + public abstract Task ContainsPrefixAsync(string prefix); + + public abstract Task GetValueAsync(string key); + + public virtual IValueProvider Filter(IValueBinderMarker valueBinderMarker) + { + if (valueBinderMarker is TBinderMarker) + { + return this; + } + + return null; + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/QueryStringValueProviderFactory.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/QueryStringValueProviderFactory.cs index 0796be18f0..b9f75f297b 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/QueryStringValueProviderFactory.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/QueryStringValueProviderFactory.cs @@ -18,12 +18,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding if (!storage.TryGetValue(_cacheKey, out value)) { var queryCollection = context.HttpContext.Request.Query; - provider = new ReadableStringCollectionValueProvider(queryCollection, CultureInfo.InvariantCulture); + provider = new ReadableStringCollectionValueProvider(queryCollection, + CultureInfo.InvariantCulture); storage[_cacheKey] = provider; } else { - provider = (ReadableStringCollectionValueProvider)value; + provider = (ReadableStringCollectionValueProvider)value; } return provider; } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/ReadableStringCollectionValueProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/ReadableStringCollectionValueProvider.cs index 6dcf116ac6..4e322574f7 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/ReadableStringCollectionValueProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/ReadableStringCollectionValueProvider.cs @@ -5,14 +5,14 @@ using System; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Globalization; -using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc.ModelBinding.Internal; namespace Microsoft.AspNet.Mvc.ModelBinding { - public class ReadableStringCollectionValueProvider : IEnumerableValueProvider + public class ReadableStringCollectionValueProvider : MakerAwareValueProvider, IEnumerableValueProvider + where TBinderMarker : IValueBinderMarker { private readonly CultureInfo _culture; private PrefixContainer _prefixContainer; @@ -45,7 +45,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } } - public virtual async Task ContainsPrefixAsync(string prefix) + public override async Task ContainsPrefixAsync(string prefix) { var prefixContainer = await GetPrefixContainerAsync(); return prefixContainer.ContainsPrefix(prefix); @@ -57,7 +57,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding return prefixContainer.GetKeysFromPrefix(prefix); } - public virtual async Task GetValueAsync([NotNull] string key) + public override async Task GetValueAsync([NotNull] string key) { var collection = await GetValueCollectionAsync(); var values = collection.GetValues(key); diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/RouteValueValueProviderFactory.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/RouteValueValueProviderFactory.cs index 5cc9484e3f..adca3248fa 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/RouteValueValueProviderFactory.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/RouteValueValueProviderFactory.cs @@ -7,7 +7,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { public IValueProvider GetValueProvider([NotNull] ValueProviderFactoryContext context) { - return new DictionaryBasedValueProvider(context.RouteValues); + return new DictionaryBasedValueProvider(context.RouteValues); } } } diff --git a/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs b/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs index dde9bdafa1..7b5c72cb82 100644 --- a/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs +++ b/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs @@ -24,6 +24,7 @@ namespace Microsoft.AspNet.Mvc options.ViewEngines.Add(typeof(RazorViewEngine)); // Set up ModelBinding + options.ModelBinders.Add(typeof(BodyModelBinder)); options.ModelBinders.Add(new TypeConverterModelBinder()); options.ModelBinders.Add(new TypeMatchModelBinder()); options.ModelBinders.Add(new CancellationTokenModelBinder()); diff --git a/src/Microsoft.AspNet.Mvc/MvcServices.cs b/src/Microsoft.AspNet.Mvc/MvcServices.cs index 77573d17ea..58604d37aa 100644 --- a/src/Microsoft.AspNet.Mvc/MvcServices.cs +++ b/src/Microsoft.AspNet.Mvc/MvcServices.cs @@ -56,6 +56,7 @@ namespace Microsoft.AspNet.Mvc yield return describe.Singleton(); yield return describe.Scoped(); + yield return describe.Transient(); yield return describe.Transient, ControllerActionDescriptorProvider>(); @@ -83,8 +84,8 @@ namespace Microsoft.AspNet.Mvc yield return describe.Scoped(); yield return describe.Transient(); yield return describe.Scoped(); + yield return describe.Transient(); yield return describe.Transient(); - yield return describe.Instance(new JsonOutputFormatter()); yield return describe.Transient(); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/BodyModelBinderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/BodyModelBinderTests.cs new file mode 100644 index 0000000000..c6cd9ee3f6 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/BodyModelBinderTests.cs @@ -0,0 +1,140 @@ +// 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.Threading.Tasks; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.PipelineCore; +using Microsoft.AspNet.Routing; +using Microsoft.Framework.DependencyInjection; +using Moq; +using Xunit; + +namespace Microsoft.AspNet.Mvc +{ + public class BodyModelBinderTests + { + [Fact] + public async Task BindModel_CallsValidationAndSelectedInputFormatterOnce() + { + // Arrange + var mockValidator = new Mock(); + mockValidator.Setup(o => o.Validate(It.IsAny(), It.IsAny())) + .Returns(true) + .Verifiable(); + var mockInputFormatter = new Mock(); + mockInputFormatter.Setup(o => o.ReadAsync(It.IsAny())) + .Returns(Task.FromResult(new Person())) + .Verifiable(); + + var bindingContext = GetBindingContext(typeof(Person), inputFormatter: mockInputFormatter.Object); + bindingContext.ModelMetadata.Marker = Mock.Of(); + + var binder = GetBodyBinder(mockInputFormatter.Object, mockValidator.Object); + + // Act + var binderResult = await binder.BindModelAsync(bindingContext); + + // Assert + mockValidator.Verify(v => v.Validate(It.IsAny(), It.IsAny()), Times.Once); + mockInputFormatter.Verify(v => v.ReadAsync(It.IsAny()), Times.Once); + } + + [Fact] + public async Task BindModel_NoInputFormatterFound_SetsModelStateError() + { + // Arrange + var bindingContext = GetBindingContext(typeof(Person), inputFormatter: null); + bindingContext.ModelMetadata.Marker = Mock.Of(); + var binder = bindingContext.ModelBinder; + + // Act + var binderResult = await binder.BindModelAsync(bindingContext); + + // Assert + + // Returns true because it understands the marker. + Assert.True(binderResult); + Assert.Null(bindingContext.Model); + Assert.True(bindingContext.ModelState.ContainsKey("someName")); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task BindModel_IsMarkerAware(bool useBodyMarker) + { + // Arrange + var bindingContext = GetBindingContext(typeof(Person), inputFormatter: null); + bindingContext.ModelMetadata.Marker = useBodyMarker ? Mock.Of() : + Mock.Of(); + var binder = bindingContext.ModelBinder; + + // Act + var binderResult = await binder.BindModelAsync(bindingContext); + + // Assert + Assert.Equal(useBodyMarker, binderResult); + } + + private static ModelBindingContext GetBindingContext(Type modelType, IInputFormatter inputFormatter) + { + var metadataProvider = new EmptyModelMetadataProvider(); + ModelBindingContext bindingContext = new ModelBindingContext + { + ModelMetadata = metadataProvider.GetMetadataForType(null, modelType), + ModelName = "someName", + ValueProvider = Mock.Of(), + ModelBinder = GetBodyBinder(inputFormatter, null), + MetadataProvider = metadataProvider, + HttpContext = new DefaultHttpContext(), + ModelState = new ModelStateDictionary() + }; + + return bindingContext; + } + + private static BodyModelBinder GetBodyBinder(IInputFormatter inputFormatter, IBodyModelValidator validator) + { + var actionContext = CreateActionContext(new DefaultHttpContext()); + var inputFormatterSelector = new Mock(); + inputFormatterSelector.Setup(o => o.SelectFormatter(It.IsAny())).Returns(inputFormatter); + + if (validator == null) + { + var mockValidator = new Mock(); + mockValidator.Setup(o => o.Validate(It.IsAny(), It.IsAny())) + .Returns(true) + .Verifiable(); + validator = mockValidator.Object; + } + + var binder = new BodyModelBinder(actionContext, + inputFormatterSelector.Object, + validator); + return binder; + } + + private static IContextAccessor CreateActionContext(HttpContext context) + { + return CreateActionContext(context, (new Mock()).Object); + } + + private static IContextAccessor CreateActionContext(HttpContext context, IRouter router) + { + var routeData = new RouteData(); + routeData.Values = new Dictionary(StringComparer.OrdinalIgnoreCase); + routeData.Routers.Add(router); + + var actionContext = new ActionContext(context, + routeData, + new ActionDescriptor()); + var contextAccessor = new Mock>(); + contextAccessor.SetupGet(c => c.Value) + .Returns(actionContext); + return contextAccessor.Object; + } + } +} \ 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 50bc3cecb3..008c1240ab 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionInvokerTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionInvokerTest.cs @@ -1339,187 +1339,16 @@ namespace Microsoft.AspNet.Mvc var invoker = new TestControllerActionInvoker( actionContext, - actionBindingContextProvider.Object, filterProvider.Object, controllerFactory, actionDescriptor, inputFormattersProvider.Object, - new DefaultBodyModelValidator()); + Mock.Of()); return invoker; } - [Fact] - public async Task GetActionArguments_DoesNotAddActionArgumentsToModelStateDictionary_IfBinderReturnsFalse() - { - // Arrange - Func method = x => 1; - var actionDescriptor = new ControllerActionDescriptor - { - MethodInfo = method.Method, - Parameters = new List - { - new ParameterDescriptor - { - Name = "foo", - ParameterBindingInfo = new ParameterBindingInfo("foo", typeof(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); - 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 inputFormattersProvider = new Mock(); - inputFormattersProvider.SetupGet(o => o.InputFormatters) - .Returns(new List()); - var invoker = new ControllerActionInvoker(actionContext, - actionBindingContextProvider.Object, - Mock.Of>(), - Mock.Of(), - actionDescriptor, - inputFormattersProvider.Object, - new DefaultBodyModelValidator()); - - var modelStateDictionary = new ModelStateDictionary(); - - // Act - var result = await invoker.GetActionArguments(modelStateDictionary); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task GetActionArguments_AddsActionArgumentsToModelStateDictionary_IfBinderReturnsTrue() - { - // Arrange - Func method = x => 1; - var actionDescriptor = new ControllerActionDescriptor - { - MethodInfo = method.Method, - Parameters = new List - { - new ParameterDescriptor - { - Name = "foo", - ParameterBindingInfo = new ParameterBindingInfo("foo", typeof(object)) - } - } - }; - 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); - 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 inputFormattersProvider = new Mock(); - inputFormattersProvider.SetupGet(o => o.InputFormatters) - .Returns(new List()); - var invoker = new ControllerActionInvoker(actionContext, - actionBindingContextProvider.Object, - Mock.Of>(), - Mock.Of(), - actionDescriptor, - inputFormattersProvider.Object, - new DefaultBodyModelValidator()); - - var modelStateDictionary = new ModelStateDictionary(); - - // Act - var result = await invoker.GetActionArguments(modelStateDictionary); - - // Assert - Assert.Equal(1, result.Count); - Assert.Equal(value, result["foo"]); - } - - [Fact] - public async Task GetActionArguments_NoInputFormatterFound_SetsModelStateError() - { - var actionDescriptor = new ControllerActionDescriptor - { - MethodInfo = typeof(TestController).GetTypeInfo().GetMethod("ActionMethodWithDefaultValues"), - Parameters = new List - { - new ParameterDescriptor - { - Name = "bodyParam", - BodyParameterInfo = new BodyParameterInfo(typeof(Person)) - } - }, - FilterDescriptors = new List() - }; - - var context = new DefaultHttpContext(); - var routeContext = new RouteContext(context); - var actionContext = new ActionContext(routeContext, - actionDescriptor); - var bindingContext = new ActionBindingContext(actionContext, - Mock.Of(), - Mock.Of(), - Mock.Of(), - Mock.Of(), - Mock.Of()); - - var actionBindingContextProvider = new Mock(); - actionBindingContextProvider.Setup(p => p.GetActionBindingContextAsync(It.IsAny())) - .Returns(Task.FromResult(bindingContext)); - 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, - actionBindingContextProvider.Object, - Mock.Of>(), - controllerFactory.Object, - actionDescriptor, - inputFormattersProvider.Object, - new DefaultBodyModelValidator()); - - - var modelStateDictionary = new ModelStateDictionary(); - - // Act - var result = await invoker.GetActionArguments(modelStateDictionary); - - // Assert - Assert.Empty(result); - Assert.DoesNotContain("bodyParam", result.Keys); - Assert.False(actionContext.ModelState.IsValid); - Assert.Equal("Unsupported content type '" + context.Request.ContentType + "'.", - actionContext.ModelState["bodyParam"].Errors[0].ErrorMessage); - } + [Fact] public async Task Invoke_UsesDefaultValuesIfNotBound() @@ -1568,12 +1397,13 @@ namespace Microsoft.AspNet.Mvc inputFormattersProvider.SetupGet(o => o.InputFormatters) .Returns(new List()); var invoker = new ControllerActionInvoker(actionContext, - actionBindingContextProvider.Object, Mock.Of>(), controllerFactory.Object, actionDescriptor, inputFormattersProvider.Object, - new DefaultBodyModelValidator()); + new DefaultControllerActionArgumentBinder( + actionBindingContextProvider.Object, + new DefaultBodyModelValidator())); // Act await invoker.InvokeAsync(); @@ -1629,19 +1459,17 @@ namespace Microsoft.AspNet.Mvc public TestControllerActionInvoker( ActionContext actionContext, - IActionBindingContextProvider bindingContextProvider, INestedProviderManager filterProvider, Mock controllerFactoryMock, ControllerActionDescriptor descriptor, IInputFormattersProvider inputFormattersProvider, - IBodyModelValidator bodyModelValidator) : + IControllerActionArgumentBinder controllerActionArgumentBinder) : base(actionContext, - bindingContextProvider, filterProvider, controllerFactoryMock.Object, descriptor, inputFormattersProvider, - bodyModelValidator) + controllerActionArgumentBinder) { _factoryMock = controllerFactoryMock; } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/InputObjectBindingTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/InputObjectBindingTests.cs index af28b7b8bf..62d1f6cad8 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/InputObjectBindingTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/InputObjectBindingTests.cs @@ -19,112 +19,6 @@ namespace Microsoft.AspNet.Mvc { public class InputObjectBindingTests { - [Fact] - public async Task GetArguments_UsingInputFormatter_DeserializesWithoutErrors_WhenValidationAttributesAreAbsent() - { - // Arrange - var sampleName = "SampleName"; - var input = "" + - "" + sampleName + ""; - var modelStateDictionary = new ModelStateDictionary(); - var invoker = GetControllerActionInvoker( - input, typeof(Person), new XmlSerializerInputFormatter(), "application/xml"); - - // Act - var result = await invoker.GetActionArguments(modelStateDictionary); - - // Assert - Assert.True(modelStateDictionary.IsValid); - Assert.Equal(0, modelStateDictionary.ErrorCount); - var model = result["foo"] as Person; - Assert.Equal(sampleName, model.Name); - } - - [Fact] - public async Task GetArguments_UsingInputFormatter_DeserializesWithValidationError() - { - // Arrange - var sampleName = "SampleName"; - var sampleUserName = "No5"; - var input = "" + - "" + sampleName + "" + sampleUserName + ""; - var modelStateDictionary = new ModelStateDictionary(); - var invoker = GetControllerActionInvoker(input, typeof(User), new XmlSerializerInputFormatter(), "application/xml"); - - // Act - var result = await invoker.GetActionArguments(modelStateDictionary); - - // Assert - Assert.False(modelStateDictionary.IsValid); - Assert.Equal(1, modelStateDictionary.ErrorCount); - Assert.Equal( - ValidationAttributeUtil.GetMinLengthErrorMessage(5, "UserName"), - Assert.Single(Assert.Single(modelStateDictionary.Values).Errors).ErrorMessage); - var model = result["foo"] as User; - Assert.Equal(sampleName, model.Name); - Assert.Equal(sampleUserName, model.UserName); - } - - [Fact] - public async Task GetArguments_UsingInputFormatter_DeserializesArrays() - { - // Arrange - var sampleFirstUser = "FirstUser"; - var sampleFirstUserName = "fuser"; - var sampleSecondUser = "SecondUser"; - var sampleSecondUserName = "suser"; - var input = "{'Users': [{Name : '" + sampleFirstUser + "', UserName: '" + sampleFirstUserName + - "'}, {Name: '" + sampleSecondUser + "', UserName: '" + sampleSecondUserName + "'}]}"; - var modelStateDictionary = new ModelStateDictionary(); - var invoker = GetControllerActionInvoker(input, typeof(Customers), new JsonInputFormatter(), "application/xml"); - - // Act - var result = await invoker.GetActionArguments(modelStateDictionary); - - // Assert - Assert.True(modelStateDictionary.IsValid); - Assert.Equal(0, modelStateDictionary.ErrorCount); - var model = result["foo"] as Customers; - Assert.Equal(2, model.Users.Count); - Assert.Equal(sampleFirstUser, model.Users[0].Name); - Assert.Equal(sampleFirstUserName, model.Users[0].UserName); - Assert.Equal(sampleSecondUser, model.Users[1].Name); - Assert.Equal(sampleSecondUserName, model.Users[1].UserName); - } - - [Fact] - public async Task GetArguments_UsingInputFormatter_DeserializesArrays_WithErrors() - { - // Arrange - var sampleFirstUser = "FirstUser"; - var sampleFirstUserName = "fusr"; - var sampleSecondUser = "SecondUser"; - var sampleSecondUserName = "susr"; - var input = "{'Users': [{Name : '" + sampleFirstUser + "', UserName: '" + sampleFirstUserName + - "'}, {Name: '" + sampleSecondUser + "', UserName: '" + sampleSecondUserName + "'}]}"; - var modelStateDictionary = new ModelStateDictionary(); - var invoker = GetControllerActionInvoker(input, typeof(Customers), new JsonInputFormatter(), "application/xml"); - - // Act - var result = await invoker.GetActionArguments(modelStateDictionary); - - // Assert - Assert.False(modelStateDictionary.IsValid); - Assert.Equal(2, modelStateDictionary.ErrorCount); - var model = result["foo"] as Customers; - Assert.Equal( - ValidationAttributeUtil.GetMinLengthErrorMessage(5, "UserName"), - modelStateDictionary["foo.Users[0].UserName"].Errors[0].ErrorMessage); - Assert.Equal( - ValidationAttributeUtil.GetMinLengthErrorMessage(5, "UserName"), - modelStateDictionary["foo.Users[1].UserName"].Errors[0].ErrorMessage); - Assert.Equal(2, model.Users.Count); - Assert.Equal(sampleFirstUser, model.Users[0].Name); - Assert.Equal(sampleFirstUserName, model.Users[0].UserName); - Assert.Equal(sampleSecondUser, model.Users[1].Name); - Assert.Equal(sampleSecondUserName, model.Users[1].UserName); - } - private static ControllerActionInvoker GetControllerActionInvoker( string input, Type parameterType, IInputFormatter selectedFormatter, string contentType) { @@ -174,12 +68,11 @@ namespace Microsoft.AspNet.Mvc inputFormattersProvider.SetupGet(o => o.InputFormatters) .Returns(new List()); return new ControllerActionInvoker(actionContext, - actionBindingContextProvider.Object, Mock.Of>(), Mock.Of(), actionDescriptor, inputFormattersProvider.Object, - new DefaultBodyModelValidator()); + Mock.Of()); } private static ActionContext GetActionContext(byte[] contentBytes, diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs new file mode 100644 index 0000000000..45ecefafcf --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs @@ -0,0 +1,196 @@ +// 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.Threading.Tasks; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.Routing; +using Moq; +using Xunit; + +namespace Microsoft.AspNet.Mvc.Core.Test +{ + public class ControllerActionArgumentBinderTests + { + [Fact] + public async Task Parameters_WithMultipleFromBody_Throw() + { + // Arrange + var actionDescriptor = new ControllerActionDescriptor + { + MethodInfo = typeof(TestController).GetMethod("ActionWithTwoBodyParam"), + Parameters = new List + { + new ParameterDescriptor + { + Name = "bodyParam", + ParameterType = typeof(Person), + }, + new ParameterDescriptor + { + Name = "bodyParam1", + ParameterType = typeof(Person), + } + } + }; + + var binder = new Mock(); + var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var actionContext = new ActionContext(new RouteContext(Mock.Of()), + actionDescriptor); + actionContext.Controller = Mock.Of(); + var bindingContext = new ActionBindingContext(actionContext, + metadataProvider, + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of()); + + var actionBindingContextProvider = new Mock(); + actionBindingContextProvider.Setup(p => p.GetActionBindingContextAsync(It.IsAny())) + .Returns(Task.FromResult(bindingContext)); + + var invoker = new DefaultControllerActionArgumentBinder( + actionBindingContextProvider.Object, Mock.Of()); + + // Act + var ex = await Assert.ThrowsAsync( + () => invoker.GetActionArgumentsAsync(actionContext)); + + // Assert + Assert.Equal("More than one parameter is bound to the HTTP request's content.", + ex.Message); + } + + [Fact] + public async Task GetActionArgumentsAsync_DoesNotAddActionArguments_IfBinderReturnsFalse() + { + // Arrange + Func method = foo => 1; + var actionDescriptor = new ControllerActionDescriptor + { + MethodInfo = method.Method, + Parameters = new List + { + new ParameterDescriptor + { + Name = "foo", + ParameterBindingInfo = new ParameterBindingInfo("foo", typeof(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); + 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, Mock.Of()); + + // Act + var result = await invoker.GetActionArgumentsAsync(actionContext); + + // Assert + Assert.Empty(result); + } + + [Fact] + public async Task GetActionArgumentsAsync_AddsActionArguments_IfBinderReturnsTrue() + { + // Arrange + Func method = foo => 1; + var actionDescriptor = new ControllerActionDescriptor + { + MethodInfo = method.Method, + Parameters = new List + { + 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 invoker = new DefaultControllerActionArgumentBinder( + actionBindingContextProvider.Object, Mock.Of()); + + // Act + var result = await invoker.GetActionArgumentsAsync(actionContext); + + // Assert + Assert.Equal(1, result.Count); + Assert.Equal(value, result["foo"]); + } + + private class TestController + { + public string UnmarkedProperty { get; set; } + + [NonValueBinderMarker] + public string NonValueBinderMarkedProperty { get; set; } + + [ValueBinderMarker] + public string ValueBinderMarkedProperty { get; set; } + + public Person ActionWithBodyParam([FromBody] Person bodyParam) + { + return bodyParam; + } + + public Person ActionWithTwoBodyParam([FromBody] Person bodyParam, [FromBody] Person bodyParam1) + { + return bodyParam; + } + } + + + private class NonValueBinderMarkerAttribute : Attribute, IBinderMarker + { + } + + private class ValueBinderMarkerAttribute : Attribute, IValueBinderMarker + { + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ModelBindingHelperTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ModelBindingHelperTest.cs index 1f1beccf00..33c5a68a55 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ModelBindingHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ModelBindingHelperTest.cs @@ -67,7 +67,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test { { "", null } }; - var valueProvider = new DictionaryBasedValueProvider(values); + var valueProvider = new DictionaryBasedValueProvider(values); // Act var result = await ModelBindingHelper.TryUpdateModelAsync( @@ -105,7 +105,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test { "", null }, { "MyProperty", "MyPropertyValue" } }; - var valueProvider = new DictionaryBasedValueProvider(values); + var valueProvider = new DictionaryBasedValueProvider(values); // Act var result = await ModelBindingHelper.TryUpdateModelAsync( @@ -136,6 +136,10 @@ namespace Microsoft.AspNet.Mvc.Core.Test [Required] public string MyProperty { get; set; } } + + private class TestValueBinderMarker : IValueBinderMarker + { + } } } #endif diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs index f120d7e3d5..01ed7af2b9 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs @@ -5,9 +5,11 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNet.Builder; using Microsoft.AspNet.TestHost; +using ModelBindingWebSite; using Newtonsoft.Json; using Xunit; @@ -18,6 +20,93 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests private readonly IServiceProvider _services = TestHelper.CreateServices("ModelBindingWebSite"); private readonly Action _app = new ModelBindingWebSite.Startup().Configure; + [Theory] + [InlineData("RestrictValueProvidersUsingFromRoute", "valueFromRoute")] + [InlineData("RestrictValueProvidersUsingFromQuery", "valueFromQuery")] + [InlineData("RestrictValueProvidersUsingFromForm", "valueFromForm")] + public async Task CompositeModelBinder_Restricts_ValueProviders(string actionName, string expectedValue) + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + + // Provide all three values, it should bind based on the attribute on the action method. + var request = new HttpRequestMessage(HttpMethod.Post, + string.Format("http://localhost/CompositeTest/{0}/valueFromRoute?param=valueFromQuery", actionName)); + var nameValueCollection = new List> + { + new KeyValuePair("param", "valueFromForm"), + }; + + request.Content = new FormUrlEncodedContent(nameValueCollection); + + // Act + var response = await client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(expectedValue, await response.Content.ReadAsStringAsync()); + } + + [Fact] + public async Task MultipleParametersMarkedWithFromBody_Throws() + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + + // Act & Assert + var ex = await Assert.ThrowsAsync(() => + client.GetAsync("http://localhost/MultipleParametersFromBody/MultipleParametersFromBodyThrows")); + + Assert.Equal("More than one parameter is bound to the HTTP request's content.", + ex.Message); + } + + [Fact] + public async Task ParametersWithNoMarkersUseTheAvailableValueProviders() + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + + // Act + var response = await + client.GetAsync("http://localhost/WithMarker" + + "/ParametersWithNoMarkersUseTheAvailableValueProviders" + + "?Name=somename&Age=12"); + + //Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var emp = JsonConvert.DeserializeObject( + await response.Content.ReadAsStringAsync()); + Assert.Null(emp.Department); + Assert.Equal("somename", emp.Name); + Assert.Equal(12, emp.Age); + } + + [Fact] + public async Task ParametersAreAlwaysCreated() + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + + // Act + var response = await + client.GetAsync("http://localhost/WithoutMarker" + + "/GetPersonParameter" + + "?Name=somename&Age=12"); + + //Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var person = JsonConvert.DeserializeObject( + await response.Content.ReadAsStringAsync()); + Assert.NotNull(person); + Assert.Equal("somename", person.Name); + Assert.Equal(12, person.Age); + } + [Fact] public async Task ModelBindCancellationTokenParameteres() { diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Utils/SimpleHttpValueProvider.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Utils/SimpleHttpValueProvider.cs index 30f5f96114..94066d8a17 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Utils/SimpleHttpValueProvider.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Utils/SimpleHttpValueProvider.cs @@ -68,5 +68,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test return Task.FromResult(result); } + + public bool IsValidFor(Type valueProviderMarkerType) + { + // since this is not aware for any marker type, it should return false. + return false; + } } } diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/CompositeValueProviderTests.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/CompositeValueProviderTests.cs new file mode 100644 index 0000000000..c19f7c27b8 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/CompositeValueProviderTests.cs @@ -0,0 +1,66 @@ +// 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. + +#if ASPNET50 + +using System; +using System.Collections.Generic; +using Moq; +using Xunit; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Test +{ + public class CompositeValueProviderTests + { + public static IEnumerable RegisteredAsMarkerClasses + { + get + { + yield return new object[] { new TestValueBinderMarker() }; + yield return new object[] { new DerivedValueBinder() }; + } + } + + [Theory] + [MemberData(nameof(RegisteredAsMarkerClasses))] + public void FilterReturnsItself_ForAnyClassRegisteredAsGenericParam(IValueBinderMarker binderMarker) + { + // Arrange + var values = new Dictionary(StringComparer.OrdinalIgnoreCase); + var unrelatedMarker = new UnrelatedValueBinderMarker(); + var valueProvider1 = GetMockValueProvider(binderMarker); + var valueProvider2 = GetMockValueProvider(unrelatedMarker); + var provider = new CompositeValueProvider(new List() { valueProvider1.Object, valueProvider2.Object }); + + // Act + var result = provider.Filter(binderMarker); + + // Assert + var valueProvider = Assert.IsType(result); + var filteredProvider = Assert.Single(valueProvider); + + // should not be unrelated marker. + Assert.Same(valueProvider1.Object, filteredProvider); + } + + private Mock GetMockValueProvider(IValueBinderMarker marker) + { + var valueProvider = new Mock(); + valueProvider.Setup(o => o.Filter(marker)) + .Returns(valueProvider.Object); + return valueProvider; + } + private class TestValueBinderMarker : IValueBinderMarker + { + } + + private class DerivedValueBinder : TestValueBinderMarker + { + } + + private class UnrelatedValueBinderMarker : IValueBinderMarker + { + } + } +} +#endif \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/DictionaryBasedValueProviderTests.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/DictionaryBasedValueProviderTests.cs index 94b563ce38..386cc9a8e6 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/DictionaryBasedValueProviderTests.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/DictionaryBasedValueProviderTests.cs @@ -18,7 +18,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { { "test-key", "value" } }; - var provider = new DictionaryBasedValueProvider(values); + var provider = new DictionaryBasedValueProvider(values); // Act var result = await provider.GetValueAsync("not-test-key"); @@ -35,7 +35,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { { "test-key", "test-value" } }; - var provider = new DictionaryBasedValueProvider(values); + var provider = new DictionaryBasedValueProvider(values); // Act var result = await provider.GetValueAsync("test-key"); @@ -52,7 +52,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { { "test-key", null } }; - var provider = new DictionaryBasedValueProvider(values); + var provider = new DictionaryBasedValueProvider(values); // Act var result = await provider.GetValueAsync("test-key"); @@ -71,7 +71,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { { "test-key", "test-value" } }; - var provider = new DictionaryBasedValueProvider(values); + var provider = new DictionaryBasedValueProvider(values); // Act var result = await provider.ContainsPrefixAsync("not-test-key"); @@ -88,7 +88,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { { "test-key", "test-value" } }; - var provider = new DictionaryBasedValueProvider(values); + var provider = new DictionaryBasedValueProvider(values); // Act var result = await provider.ContainsPrefixAsync("test-key"); @@ -96,5 +96,38 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // Assert Assert.True(result); } + + public static IEnumerable RegisteredAsMarkerClasses + { + get + { + yield return new object[] { new TestValueBinderMarker() }; + yield return new object[] { new DerivedValueBinder() }; + } + } + + [Theory] + [MemberData(nameof(RegisteredAsMarkerClasses))] + public void FilterReturnsItself_ForAnyClassRegisteredAsGenericParam(IValueBinderMarker binderMarker) + { + // Arrange + var values = new Dictionary(StringComparer.OrdinalIgnoreCase); + var provider = new DictionaryBasedValueProvider(values); + + // Act + var result = provider.Filter(binderMarker); + + // Assert + Assert.NotNull(result); + Assert.IsType>(result); + } + + private class TestValueBinderMarker : IValueBinderMarker + { + } + + private class DerivedValueBinder :TestValueBinderMarker + { + } } } diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/FormValueProviderFactoryTests.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/FormValueProviderFactoryTests.cs index 3bea4e4df1..98dff1439d 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/FormValueProviderFactoryTests.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/FormValueProviderFactoryTests.cs @@ -43,7 +43,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test var result = factory.GetValueProvider(context); // Assert - var valueProvider = Assert.IsType(result); + var valueProvider = Assert.IsType>(result); Assert.Equal(CultureInfo.CurrentCulture, valueProvider.Culture); } diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/QueryStringValueProviderFactoryTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/QueryStringValueProviderFactoryTest.cs index c842211752..7f9df34968 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/QueryStringValueProviderFactoryTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/QueryStringValueProviderFactoryTest.cs @@ -33,7 +33,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test var result = _factory.GetValueProvider(factoryContext); // Assert - var valueProvider = Assert.IsType(result); + var valueProvider = Assert.IsType>(result); Assert.Equal(CultureInfo.InvariantCulture, valueProvider.Culture); } #endif diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/ReadableStringCollectionValueProviderTests.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/ReadableStringCollectionValueProviderTests.cs index 7ccc996fc8..8326e9418d 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/ReadableStringCollectionValueProviderTests.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/ReadableStringCollectionValueProviderTests.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; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -27,7 +28,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test { // Arrange var backingStore = new ReadableStringCollection(new Dictionary()); - var valueProvider = new ReadableStringCollectionValueProvider(backingStore, null); + var valueProvider = new ReadableStringCollectionValueProvider(backingStore, null); // Act var result = await valueProvider.ContainsPrefixAsync(""); @@ -40,7 +41,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test public async Task ContainsPrefixAsync_WithNonEmptyCollection_ReturnsTrueForEmptyPrefix() { // Arrange - var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); + var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); // Act var result = await valueProvider.ContainsPrefixAsync(""); @@ -53,7 +54,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test public async Task ContainsPrefixAsync_WithNonEmptyCollection_ReturnsTrueForKnownPrefixes() { // Arrange - var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); + var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); // Act & Assert Assert.True(await valueProvider.ContainsPrefixAsync("foo")); @@ -65,7 +66,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test public async Task ContainsPrefixAsync_WithNonEmptyCollection_ReturnsFalseForUnknownPrefix() { // Arrange - var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); + var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); // Act var result = await valueProvider.ContainsPrefixAsync("biff"); @@ -85,7 +86,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test { "null_value", "null_value" }, { "prefix", "prefix" } }; - var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, culture: null); + var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, culture: null); // Act var result = await valueProvider.GetKeysFromPrefixAsync(""); @@ -98,7 +99,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test public async Task GetKeysFromPrefixAsync_UnknownPrefix_ReturnsEmptyDictionary() { // Arrange - var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); + var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); // Act var result = await valueProvider.GetKeysFromPrefixAsync("abc"); @@ -111,7 +112,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test public async Task GetKeysFromPrefixAsync_KnownPrefix_ReturnsMatchingItems() { // Arrange - var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); + var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); // Act var result = await valueProvider.GetKeysFromPrefixAsync("bar"); @@ -127,7 +128,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test { // Arrange var culture = new CultureInfo("fr-FR"); - var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, culture); + var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, culture); // Act var vpResult = await valueProvider.GetValueAsync("bar.baz"); @@ -144,7 +145,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test { // Arrange var culture = new CultureInfo("fr-FR"); - var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, culture); + var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, culture); // Act var vpResult = await valueProvider.GetValueAsync("foo"); @@ -187,7 +188,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test { "key", new string[] { null, null, "value" } } }); var culture = new CultureInfo("fr-FR"); - var valueProvider = new ReadableStringCollectionValueProvider(backingStore, culture); + var valueProvider = new ReadableStringCollectionValueProvider(backingStore, culture); // Act var vpResult = await valueProvider.GetValueAsync("key"); @@ -201,7 +202,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test public async Task GetValueAsync_ReturnsNullIfKeyNotFound() { // Arrange - var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); + var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); // Act var vpResult = await valueProvider.GetValueAsync("bar"); @@ -209,5 +210,37 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test // Assert Assert.Null(vpResult); } + + public static IEnumerable RegisteredAsMarkerClasses + { + get + { + yield return new object[] { new TestValueBinderMarker() }; + yield return new object[] { new DerivedValueBinder() }; + } + } + + [Theory] + [MemberData(nameof(RegisteredAsMarkerClasses))] + public void FilterReturnsItself_ForAnyClassRegisteredAsGenericParam(IValueBinderMarker binderMarker) + { + // Arrange + var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); + + // Act + var result = valueProvider.Filter(binderMarker); + + // Assert + Assert.NotNull(result); + Assert.IsType>(result); + } + + private class TestValueBinderMarker : IValueBinderMarker + { + } + + private class DerivedValueBinder : TestValueBinderMarker + { + } } } diff --git a/test/Microsoft.AspNet.Mvc.Test/MvcOptionSetupTest.cs b/test/Microsoft.AspNet.Mvc.Test/MvcOptionSetupTest.cs index fdc6d4d688..db8211309b 100644 --- a/test/Microsoft.AspNet.Mvc.Test/MvcOptionSetupTest.cs +++ b/test/Microsoft.AspNet.Mvc.Test/MvcOptionSetupTest.cs @@ -35,14 +35,15 @@ namespace Microsoft.AspNet.Mvc setup.Configure(mvcOptions); // Assert - Assert.Equal(7, mvcOptions.ModelBinders.Count); - Assert.Equal(typeof(TypeConverterModelBinder), mvcOptions.ModelBinders[0].OptionType); - Assert.Equal(typeof(TypeMatchModelBinder), mvcOptions.ModelBinders[1].OptionType); - Assert.Equal(typeof(CancellationTokenModelBinder), mvcOptions.ModelBinders[2].OptionType); - Assert.Equal(typeof(ByteArrayModelBinder), mvcOptions.ModelBinders[3].OptionType); - Assert.Equal(typeof(GenericModelBinder), mvcOptions.ModelBinders[4].OptionType); - Assert.Equal(typeof(MutableObjectModelBinder), mvcOptions.ModelBinders[5].OptionType); - Assert.Equal(typeof(ComplexModelDtoModelBinder), mvcOptions.ModelBinders[6].OptionType); + Assert.Equal(8, mvcOptions.ModelBinders.Count); + Assert.Equal(typeof(BodyModelBinder), mvcOptions.ModelBinders[0].OptionType); + Assert.Equal(typeof(TypeConverterModelBinder), mvcOptions.ModelBinders[1].OptionType); + Assert.Equal(typeof(TypeMatchModelBinder), mvcOptions.ModelBinders[2].OptionType); + Assert.Equal(typeof(CancellationTokenModelBinder), mvcOptions.ModelBinders[3].OptionType); + Assert.Equal(typeof(ByteArrayModelBinder), mvcOptions.ModelBinders[4].OptionType); + Assert.Equal(typeof(GenericModelBinder), mvcOptions.ModelBinders[5].OptionType); + Assert.Equal(typeof(MutableObjectModelBinder), mvcOptions.ModelBinders[6].OptionType); + Assert.Equal(typeof(ComplexModelDtoModelBinder), mvcOptions.ModelBinders[7].OptionType); } [Fact] diff --git a/test/WebSites/ModelBindingWebSite/Controllers/CompositeTestController.cs b/test/WebSites/ModelBindingWebSite/Controllers/CompositeTestController.cs new file mode 100644 index 0000000000..808871cf85 --- /dev/null +++ b/test/WebSites/ModelBindingWebSite/Controllers/CompositeTestController.cs @@ -0,0 +1,26 @@ +// 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.AspNet.Mvc; + +namespace ModelBindingWebSite.Controllers +{ + [Route("/CompositeTest/[action]/{param}")] + public class CompositeTestController : Controller + { + public string RestrictValueProvidersUsingFromQuery([FromQuery] string param) + { + return param; + } + + public string RestrictValueProvidersUsingFromRoute([FromRoute] string param) + { + return param; + } + + public string RestrictValueProvidersUsingFromForm([FromForm] string param) + { + return param; + } + } +} \ No newline at end of file diff --git a/test/WebSites/ModelBindingWebSite/Controllers/MultipleParametersFromBodyController.cs b/test/WebSites/ModelBindingWebSite/Controllers/MultipleParametersFromBodyController.cs new file mode 100644 index 0000000000..828ac950f9 --- /dev/null +++ b/test/WebSites/ModelBindingWebSite/Controllers/MultipleParametersFromBodyController.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 Microsoft.AspNet.Mvc; + +namespace ModelBindingWebSite.Controllers +{ + public class MultipleParametersFromBodyController : Controller + { + public void MultipleParametersFromBodyThrows([FromBody] int i, [FromBody] string emp) + { + } + } +} \ No newline at end of file diff --git a/test/WebSites/ModelBindingWebSite/Controllers/WithMarkerController.cs b/test/WebSites/ModelBindingWebSite/Controllers/WithMarkerController.cs new file mode 100644 index 0000000000..b602e44da0 --- /dev/null +++ b/test/WebSites/ModelBindingWebSite/Controllers/WithMarkerController.cs @@ -0,0 +1,31 @@ +// 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; +using Microsoft.AspNet.Mvc.ModelBinding; + +namespace ModelBindingWebSite.Controllers +{ + public class WithMarkerController : Controller + { + public EmployeeWithMarker BindWithTypeMarker(EmployeeWithMarker emp) + { + return emp; + } + + public DerivedEmployee TypeMarkerAtDerivedTypeWinsOverTheBaseType(DerivedEmployee emp) + { + return emp; + } + + public void ParameterMarkerOverridesTypeMarker([FromBody] Employee emp) + { + } + + public Employee ParametersWithNoMarkersUseTheAvailableValueProviders([FromQuery] Employee emp) + { + return emp; + } + } +} \ No newline at end of file diff --git a/test/WebSites/ModelBindingWebSite/Controllers/WithoutMarkerController.cs b/test/WebSites/ModelBindingWebSite/Controllers/WithoutMarkerController.cs new file mode 100644 index 0000000000..3476b66eda --- /dev/null +++ b/test/WebSites/ModelBindingWebSite/Controllers/WithoutMarkerController.cs @@ -0,0 +1,27 @@ +// 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 ModelBindingWebSite.Controllers +{ + public class WithoutMarkerController : Controller + { + public Person Person { get; set; } + + public Person GetPersonProperty() + { + return Person; + } + + public Person GetPersonParameter(Person p) + { + return p; + } + + public void SimpleTypes(int id, string name, bool isValid, DateTime lastUpdateTime) + { + } + } +} \ No newline at end of file diff --git a/test/WebSites/ModelBindingWebSite/Model/Employee.cs b/test/WebSites/ModelBindingWebSite/Model/Employee.cs new file mode 100644 index 0000000000..7a07710a64 --- /dev/null +++ b/test/WebSites/ModelBindingWebSite/Model/Employee.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 Microsoft.AspNet.Mvc; + +namespace ModelBindingWebSite +{ + public class Employee : Person + { + public string Department { get; set; } + + public string Location { get; set; } + } +} \ No newline at end of file diff --git a/test/WebSites/ModelBindingWebSite/Model/EmployeeWithMarker.cs b/test/WebSites/ModelBindingWebSite/Model/EmployeeWithMarker.cs new file mode 100644 index 0000000000..b220c6f6d4 --- /dev/null +++ b/test/WebSites/ModelBindingWebSite/Model/EmployeeWithMarker.cs @@ -0,0 +1,16 @@ +// 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.AspNet.Mvc; + +namespace ModelBindingWebSite +{ + public class EmployeeWithMarker : Employee + { + } + + public class DerivedEmployee : EmployeeWithMarker + { + + } +} \ No newline at end of file diff --git a/test/WebSites/ModelBindingWebSite/Model/ExternalType.cs b/test/WebSites/ModelBindingWebSite/Model/ExternalType.cs new file mode 100644 index 0000000000..adc3434bc8 --- /dev/null +++ b/test/WebSites/ModelBindingWebSite/Model/ExternalType.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.AspNet.Mvc; + +namespace ModelBindingWebSite +{ + public class ExternalType + { + public string Department { get; set; } + } +} \ No newline at end of file diff --git a/test/WebSites/ModelBindingWebSite/Model/Person.cs b/test/WebSites/ModelBindingWebSite/Model/Person.cs new file mode 100644 index 0000000000..7f9f4de3b8 --- /dev/null +++ b/test/WebSites/ModelBindingWebSite/Model/Person.cs @@ -0,0 +1,16 @@ +// 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.AspNet.Mvc; + +namespace ModelBindingWebSite +{ + public class Person + { + public string Name { get; set; } + + public int Age { get; set; } + + public Person Parent { get; set; } + } +} \ No newline at end of file diff --git a/test/WebSites/ValueProvidersSite/CustomValueProviderFactory.cs b/test/WebSites/ValueProvidersSite/CustomValueProviderFactory.cs index 2c482ace11..e88b998067 100644 --- a/test/WebSites/ValueProvidersSite/CustomValueProviderFactory.cs +++ b/test/WebSites/ValueProvidersSite/CustomValueProviderFactory.cs @@ -34,6 +34,11 @@ namespace ValueProvidersSite var result = new ValueProviderResult(value, value, CultureInfo.CurrentCulture); return Task.FromResult(result); } + + public bool IsValidFor(Type valueProviderMarkerType) + { + return false; + } } } } \ No newline at end of file