[Fixefor #860]- Adding Support for [FromFormData] [FromQuery] and [FromRoute],

also updates FromBody to use the new pattern for model binding.
This commit is contained in:
Harsh Gupta 2014-10-14 15:45:37 -07:00
parent 8cf74afc75
commit 8f933b2ac3
62 changed files with 1348 additions and 430 deletions

View File

@ -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
{
/// <summary>
/// Provides a default implementation of <see cref="IControllerActionArgumentBinder"/>.
/// Uses ModelBinding to populate action parameters.
/// </summary>
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<IDictionary<string, object>> 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<string, object>(StringComparer.Ordinal);
await PopulateActionArgumentsAsync(parameterMetadatas, actionBindingContext, actionArguments);
return actionArguments;
}
private async Task PopulateActionArgumentsAsync(IEnumerable<ModelMetadata> modelMetadatas,
ActionBindingContext actionBindingContext,
IDictionary<string, object> 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;
}
}
}
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.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<FilterProviderContext> 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<IDictionary<string, object>> GetActionArgumentsAsync(ActionContext context)
{
return _actionInvocationProvider.GetActionArgumentsAsync(context);
}
// Marking as internal for Unit Testing purposes.
internal static IActionResult CreateActionResult([NotNull] Type declaredReturnType, object actionReturnValue)
{

View File

@ -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<FilterProviderContext> _filterProvider;
private readonly IBodyModelValidator _modelValidator;
private readonly IControllerActionArgumentBinder _actionInvocationInfoProvider;
public ControllerActionInvokerProvider(IControllerFactory controllerFactory,
IActionBindingContextProvider bindingProvider,
IInputFormattersProvider inputFormattersProvider,
INestedProviderManager<FilterProviderContext> 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();

View File

@ -28,7 +28,7 @@ namespace Microsoft.AspNet.Mvc
if (actionDescriptor == null)
{
throw new ArgumentException(
Resources.FormatDefaultControllerFactory_ActionDescriptorMustBeReflected(
Resources.FormatActionDescriptorMustBeBasedOnControllerAction(
typeof(ControllerActionDescriptor)),
"actionContext");
}

View File

@ -15,9 +15,7 @@ namespace Microsoft.AspNet.Mvc
{
public abstract class FilterActionInvoker : IActionInvoker
{
private readonly IActionBindingContextProvider _bindingProvider;
private readonly INestedProviderManager<FilterProviderContext> _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<FilterProviderContext> filterProvider,
[NotNull] IBodyModelValidator modelValidator)
[NotNull] INestedProviderManager<FilterProviderContext> filterProvider)
{
ActionContext = actionContext;
_bindingProvider = bindingContextProvider;
_filterProvider = filterProvider;
_modelValidator = modelValidator;
}
protected ActionContext ActionContext { get; private set; }
protected abstract Task<IActionResult> InvokeActionAsync(ActionExecutingContext actionExecutingContext);
protected abstract Task<IDictionary<string, object>> 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<IDictionary<string, object>> GetActionArguments(ModelStateDictionary modelState)
{
var actionBindingContext = await _bindingProvider.GetActionBindingContextAsync(ActionContext);
var parameters = ActionContext.ActionDescriptor.Parameters;
var metadataProvider = actionBindingContext.MetadataProvider;
var parameterValues = new Dictionary<string, object>(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<ActionExecutedContext> InvokeActionMethodFilter()
{
Contract.Assert(_actionExecutingContext != null);

View File

@ -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
{
/// <summary>
/// Provides a dictionary of action arguments.
/// </summary>
public interface IControllerActionArgumentBinder
{
/// <summary>
/// Returns a dictionary of representing the parameter-argument name-value pairs,
/// which can be used to invoke the action.
/// </summary>
/// <param name="context">The action context assoicated with the current action.</param>
Task<IDictionary<string, object>> GetActionArgumentsAsync([NotNull] ActionContext context);
}
}

View File

@ -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
{
/// <summary>
/// Represents a model binder which understands <see cref="IBodyBinderMarker"/> and uses
/// InputFomatters to bind the model to request's body.
/// </summary>
public class BodyModelBinder : MarkerAwareBinder<IBodyBinderMarker>
{
private readonly ActionContext _actionContext;
private readonly IInputFormatterSelector _formatterSelector;
private readonly IBodyModelValidator _bodyModelValidator;
public BodyModelBinder([NotNull] IContextAccessor<ActionContext> context,
[NotNull] IInputFormatterSelector selector,
[NotNull] IBodyModelValidator bodyModelValidator)
{
_actionContext = context.Value;
_formatterSelector = selector;
_bodyModelValidator = bodyModelValidator;
}
protected override async Task<bool> 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;
}
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using 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; }

View File

@ -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);
/// <summary>
/// More than one parameter is bound to the HTTP request's content.
/// </summary>
internal static string MultipleBodyParametersAreNotAllowed
{
get { return GetString("MultipleBodyParametersAreNotAllowed"); }
}
/// <summary>
/// More than one parameter is bound to the HTTP request's content.
/// </summary>
internal static string FormatMultipleBodyParametersAreNotAllowed()
{
return GetString("MultipleBodyParametersAreNotAllowed");
}
/// <summary>
/// The provided anti-forgery token failed a custom data check.
/// </summary>
@ -413,17 +429,17 @@ namespace Microsoft.AspNet.Mvc.Core
/// <summary>
/// The action descriptor must be of type '{0}'.
/// </summary>
internal static string DefaultControllerFactory_ActionDescriptorMustBeReflected
internal static string ActionDescriptorMustBeBasedOnControllerAction
{
get { return GetString("DefaultControllerFactory_ActionDescriptorMustBeReflected"); }
get { return GetString("ActionDescriptorMustBeBasedOnControllerAction"); }
}
/// <summary>
/// The action descriptor must be of type '{0}'.
/// </summary>
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);
}
/// <summary>

View File

@ -117,6 +117,9 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="MultipleBodyParametersAreNotAllowed" xml:space="preserve">
<value>More than one parameter is bound to the HTTP request's content.</value>
</data>
<data name="AntiForgeryToken_AdditionalDataCheckFailed" xml:space="preserve">
<value>The provided anti-forgery token failed a custom data check.</value>
</data>
@ -192,7 +195,7 @@
<data name="ActionInvokerFactory_CouldNotCreateInvoker" xml:space="preserve">
<value>An action invoker could not be created for action '{0}'.</value>
</data>
<data name="DefaultControllerFactory_ActionDescriptorMustBeReflected" xml:space="preserve">
<data name="ActionDescriptorMustBeBasedOnControllerAction" xml:space="preserve">
<value>The action descriptor must be of type '{0}'.</value>
</data>
<data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve">

View File

@ -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
{
/// <summary>
/// This attribute is used on action parameters to indicate
/// they are bound from the body of the incoming request.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
public class FromBodyAttribute : Attribute, IBodyBinderMarker
{
}
}

View File

@ -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
{
/// <summary>
/// This attribute is used on action parameters to indicate that
/// they will be bound using form data of the incoming request.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
public class FromFormAttribute : Attribute, IFormDataMarker
{
}
}

View File

@ -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
{
/// <summary>
/// This attribute is used on action parameters to indicate that
/// they will be bound using query data of the incoming request.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
public class FromQueryAttribute : Attribute, IQueryBinderMarker
{
}
}

View File

@ -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
{
/// <summary>
/// This attribute is used on action parameters to indicate that
/// they will be bound using route data of the incoming request.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
public class FromRouteAttribute : Attribute, IRouteDataMarker
{
}
}

View File

@ -3,10 +3,12 @@
using System;
namespace Microsoft.AspNet.Mvc
namespace Microsoft.AspNet.Mvc.ModelBinding
{
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class FromBodyAttribute : Attribute
/// <summary>
/// Represents a marker used to identify a particular binder applies to a model.
/// </summary>
public interface IBinderMarker
{
}
}

View File

@ -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
{
/// <summary>
/// Represents a marker used to identify a binder which can bind request body to a model.
/// </summary>
public interface IBodyBinderMarker : IBinderMarker
{
}
}

View File

@ -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
{
/// <summary>
/// Represents a marker used to identify a binder which can bind form data to a model.
/// </summary>
public interface IFormDataMarker : IValueBinderMarker
{
}
}

View File

@ -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
{
/// <summary>
/// Represents a marker used to identify a binder which can bind query data to a model.
/// </summary>
public interface IQueryBinderMarker : IValueBinderMarker
{
}
}

View File

@ -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
{
/// <summary>
/// Represents a marker used to identify a binder which can bind route data to a model.
/// </summary>
public interface IRouteDataMarker : IValueBinderMarker
{
}
}

View File

@ -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
{
/// <summary>
/// Represents a binder marker which identifies a binder which is based on a value provider.
/// </summary>
public interface IValueBinderMarker : IBinderMarker
{
}
}

View File

@ -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;
}
}

View File

@ -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
{
/// <summary>
/// An <see cref="IModelBinder"/> which is aware of <see cref="IBinderMarker"/>.
/// </summary>
public interface IMarkerAwareBinder : IModelBinder
{
}
}

View File

@ -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
{
/// <summary>
/// Represents an <see cref="IMarkerAwareBinder"/> which can select itself based on the
/// <typeparamref name="TBinderMarker"/>.
/// </summary>
/// <typeparam name="TBinderMarker">Represents a type implementing <see cref="IBinderMarker"/></typeparam>
public abstract class MarkerAwareBinder<TBinderMarker> : IMarkerAwareBinder
where TBinderMarker : IBinderMarker
{
/// <summary>
/// Async function which does the actual binding to bind to a particular model.
/// </summary>
/// <param name="bindingContext">The binding context which has the object to be bound.</param>
/// <param name="marker">The <see cref="IBinderMarker"/> associated with the current binder.</param>
/// <returns>A Task with a bool implying the success or failure of the operation.</returns>
protected abstract Task<bool> BindAsync(ModelBindingContext bindingContext, TBinderMarker marker);
public Task<bool> BindModelAsync(ModelBindingContext context)
{
if (context.ModelMetadata.Marker is TBinderMarker)
{
var marker = (TBinderMarker)context.ModelMetadata.Marker;
return BindAsync(context, marker);
}
return Task.FromResult(false);
}
}
}

View File

@ -46,6 +46,36 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return CreateMetadataFromPrototype(prototype, modelAccessor);
}
public IEnumerable<ModelMetadata> 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<object> 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<Attribute> attributes,
Type containerType,
@ -56,6 +86,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
protected abstract TModelMetadata CreateMetadataFromPrototype(TModelMetadata prototype,
Func<object> modelAccessor);
private ModelMetadata GetMetadataForParameterCore(Func<object> 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<ModelMetadata> 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<Attribute> 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; }

View File

@ -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<IBinderMarker>().FirstOrDefault();
}
protected override bool ComputeConvertEmptyStringToNull()

View File

@ -55,7 +55,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
CacheKey = prototype.CacheKey;
PrototypeCache = prototype.PrototypeCache;
Marker = prototype.Marker;
_isComplexType = prototype.IsComplexType;
_isComplexTypeComputed = true;
}

View File

@ -3,15 +3,20 @@
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public interface IModelMetadataProvider
{
IEnumerable<ModelMetadata> GetMetadataForProperties(object container, Type containerType);
IEnumerable<ModelMetadata> GetMetadataForProperties(object container, [NotNull] Type containerType);
ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName);
ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, [NotNull] Type containerType, [NotNull] string propertyName);
ModelMetadata GetMetadataForType(Func<object> modelAccessor, Type modelType);
ModelMetadata GetMetadataForType(Func<object> modelAccessor, [NotNull] Type modelType);
IEnumerable<ModelMetadata> GetMetadataForParameters([NotNull] MethodInfo methodInfo);
ModelMetadata GetMetadataForParameter(Func<object> modelAccessor, [NotNull] MethodInfo methodInfo, [NotNull] string parameterName);
}
}

View File

@ -48,6 +48,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
_isRequired = !modelType.AllowsNullValue();
}
/// <summary>
/// Gets or sets a binder marker for this model.
/// </summary>
public IBinderMarker Marker { get; set; }
public Type ContainerType
{
get { return _containerType; }

View File

@ -42,6 +42,22 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return string.Format(CultureInfo.CurrentCulture, GetString("Common_PropertyNotFound"), p0, p1);
}
/// <summary>
/// The parameter '{0}' could not be found.
/// </summary>
internal static string Common_ParameterNotFound
{
get { return GetString("Common_ParameterNotFound"); }
}
/// <summary>
/// The parameter '{0}' could not be found.
/// </summary>
internal static string FormatCommon_ParameterNotFound(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Common_ParameterNotFound"), p0);
}
/// <summary>
/// The type '{0}' must have a public constructor which accepts a single parameter of type '{1}'.
/// </summary>

View File

@ -123,6 +123,9 @@
<data name="Common_PropertyNotFound" xml:space="preserve">
<value>The property {0}.{1} could not be found.</value>
</data>
<data name="Common_ParameterNotFound" xml:space="preserve">
<value>The parameter '{0}' could not be found.</value>
</data>
<data name="DataAnnotationsModelValidatorProvider_ConstructorRequirements" xml:space="preserve">
<value>The type '{0}' must have a public constructor which accepts a single parameter of type '{1}'.</value>
</data>

View File

@ -9,7 +9,7 @@ using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class CompositeValueProvider : Collection<IValueProvider>, IEnumerableValueProvider
public class CompositeValueProvider : Collection<IValueProvider>, 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<IValueProvider>();
foreach (var valueProvider in this.OfType<IMarkerAwareValueProvider>())
{
var result = valueProvider.Filter(valueBinderMarker);
if (result != null)
{
filteredValueProviders.Add(result);
}
}
return new CompositeValueProvider(filteredValueProviders);
}
}
}

View File

@ -7,7 +7,8 @@ using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class DictionaryBasedValueProvider : IValueProvider
public class DictionaryBasedValueProvider<TBinderMarker> : MakerAwareValueProvider<TBinderMarker>
where TBinderMarker : IValueBinderMarker
{
private readonly IDictionary<string, object> _values;
@ -16,12 +17,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
_values = values;
}
public Task<bool> ContainsPrefixAsync(string key)
public override Task<bool> ContainsPrefixAsync(string key)
{
return Task.FromResult(_values.ContainsKey(key));
}
public Task<ValueProviderResult> GetValueAsync([NotNull] string key)
public override Task<ValueProviderResult> GetValueAsync([NotNull] string key)
{
object value;
ValueProviderResult result;

View File

@ -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<IFormDataMarker>(() => request.GetFormAsync(), culture);
}
return null;

View File

@ -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
{
/// <summary>
/// A value provider which is aware of <see cref="IValueBinderMarker"/>.
/// </summary>
public interface IMarkerAwareValueProvider : IValueProvider
{
/// <summary>
/// Filters the value provider based on <paramref name="valueBinderMarker"/>.
/// </summary>
/// <param name="valueBinderMarker">The <see cref="IValueBinderMarker"/> associated with a model.</param>
/// <returns>The filtered value provider.</returns>
IValueProvider Filter([NotNull] IValueBinderMarker valueBinderMarker);
}
}

View File

@ -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
{
/// <summary>
/// A <see cref="IMarkerAwareValueProvider"/> value provider which can filter
/// based on <see cref="IValueBinderMarker"/>.
/// </summary>
/// <typeparam name="TBinderMarker">Represents a type implementing <see cref="IValueBinderMarker"/></typeparam>
public abstract class MakerAwareValueProvider<TBinderMarker> : IMarkerAwareValueProvider
where TBinderMarker : IValueBinderMarker
{
public abstract Task<bool> ContainsPrefixAsync(string prefix);
public abstract Task<ValueProviderResult> GetValueAsync(string key);
public virtual IValueProvider Filter(IValueBinderMarker valueBinderMarker)
{
if (valueBinderMarker is TBinderMarker)
{
return this;
}
return null;
}
}
}

View File

@ -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<IQueryBinderMarker>(queryCollection,
CultureInfo.InvariantCulture);
storage[_cacheKey] = provider;
}
else
{
provider = (ReadableStringCollectionValueProvider)value;
provider = (ReadableStringCollectionValueProvider<IQueryBinderMarker>)value;
}
return provider;
}

View File

@ -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<TBinderMarker> : MakerAwareValueProvider<TBinderMarker>, IEnumerableValueProvider
where TBinderMarker : IValueBinderMarker
{
private readonly CultureInfo _culture;
private PrefixContainer _prefixContainer;
@ -45,7 +45,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
}
public virtual async Task<bool> ContainsPrefixAsync(string prefix)
public override async Task<bool> 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<ValueProviderResult> GetValueAsync([NotNull] string key)
public override async Task<ValueProviderResult> GetValueAsync([NotNull] string key)
{
var collection = await GetValueCollectionAsync();
var values = collection.GetValues(key);

View File

@ -7,7 +7,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
public IValueProvider GetValueProvider([NotNull] ValueProviderFactoryContext context)
{
return new DictionaryBasedValueProvider(context.RouteValues);
return new DictionaryBasedValueProvider<IRouteDataMarker>(context.RouteValues);
}
}
}

View File

@ -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());

View File

@ -56,6 +56,7 @@ namespace Microsoft.AspNet.Mvc
yield return describe.Singleton<IActionSelectorDecisionTreeProvider, ActionSelectorDecisionTreeProvider>();
yield return describe.Scoped<IActionSelector, DefaultActionSelector>();
yield return describe.Transient<IControllerActionArgumentBinder, DefaultControllerActionArgumentBinder>();
yield return describe.Transient<INestedProvider<ActionDescriptorProviderContext>,
ControllerActionDescriptorProvider>();
@ -83,8 +84,8 @@ namespace Microsoft.AspNet.Mvc
yield return describe.Scoped<ICompositeModelBinder, CompositeModelBinder>();
yield return describe.Transient<IValueProviderFactoryProvider, DefaultValueProviderFactoryProvider>();
yield return describe.Scoped<ICompositeValueProviderFactory, CompositeValueProviderFactory>();
yield return describe.Transient<IValueProviderFactoryProvider, DefaultValueProviderFactoryProvider>();
yield return describe.Transient<IOutputFormattersProvider, DefaultOutputFormattersProvider>();
yield return describe.Instance<JsonOutputFormatter>(new JsonOutputFormatter());
yield return describe.Transient<IModelValidatorProviderProvider, DefaultModelValidatorProviderProvider>();

View File

@ -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<IBodyModelValidator>();
mockValidator.Setup(o => o.Validate(It.IsAny<ModelValidationContext>(), It.IsAny<string>()))
.Returns(true)
.Verifiable();
var mockInputFormatter = new Mock<IInputFormatter>();
mockInputFormatter.Setup(o => o.ReadAsync(It.IsAny<InputFormatterContext>()))
.Returns(Task.FromResult<object>(new Person()))
.Verifiable();
var bindingContext = GetBindingContext(typeof(Person), inputFormatter: mockInputFormatter.Object);
bindingContext.ModelMetadata.Marker = Mock.Of<IBodyBinderMarker>();
var binder = GetBodyBinder(mockInputFormatter.Object, mockValidator.Object);
// Act
var binderResult = await binder.BindModelAsync(bindingContext);
// Assert
mockValidator.Verify(v => v.Validate(It.IsAny<ModelValidationContext>(), It.IsAny<string>()), Times.Once);
mockInputFormatter.Verify(v => v.ReadAsync(It.IsAny<InputFormatterContext>()), Times.Once);
}
[Fact]
public async Task BindModel_NoInputFormatterFound_SetsModelStateError()
{
// Arrange
var bindingContext = GetBindingContext(typeof(Person), inputFormatter: null);
bindingContext.ModelMetadata.Marker = Mock.Of<IBodyBinderMarker>();
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<IBodyBinderMarker>() :
Mock.Of<IBinderMarker>();
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<IValueProvider>(),
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<IInputFormatterSelector>();
inputFormatterSelector.Setup(o => o.SelectFormatter(It.IsAny<InputFormatterContext>())).Returns(inputFormatter);
if (validator == null)
{
var mockValidator = new Mock<IBodyModelValidator>();
mockValidator.Setup(o => o.Validate(It.IsAny<ModelValidationContext>(), It.IsAny<string>()))
.Returns(true)
.Verifiable();
validator = mockValidator.Object;
}
var binder = new BodyModelBinder(actionContext,
inputFormatterSelector.Object,
validator);
return binder;
}
private static IContextAccessor<ActionContext> CreateActionContext(HttpContext context)
{
return CreateActionContext(context, (new Mock<IRouter>()).Object);
}
private static IContextAccessor<ActionContext> CreateActionContext(HttpContext context, IRouter router)
{
var routeData = new RouteData();
routeData.Values = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
routeData.Routers.Add(router);
var actionContext = new ActionContext(context,
routeData,
new ActionDescriptor());
var contextAccessor = new Mock<IContextAccessor<ActionContext>>();
contextAccessor.SetupGet(c => c.Value)
.Returns(actionContext);
return contextAccessor.Object;
}
}
}

View File

@ -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<IControllerActionArgumentBinder>());
return invoker;
}
[Fact]
public async Task GetActionArguments_DoesNotAddActionArgumentsToModelStateDictionary_IfBinderReturnsFalse()
{
// Arrange
Func<object, int> method = x => 1;
var actionDescriptor = new ControllerActionDescriptor
{
MethodInfo = method.Method,
Parameters = new List<ParameterDescriptor>
{
new ParameterDescriptor
{
Name = "foo",
ParameterBindingInfo = new ParameterBindingInfo("foo", typeof(object))
}
}
};
var binder = new Mock<IModelBinder>();
binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
.Returns(Task.FromResult(result: false));
var actionContext = new ActionContext(new RouteContext(Mock.Of<HttpContext>()),
actionDescriptor);
var bindingContext = new ActionBindingContext(actionContext,
Mock.Of<IModelMetadataProvider>(),
binder.Object,
Mock.Of<IValueProvider>(),
Mock.Of<IInputFormatterSelector>(),
Mock.Of<IModelValidatorProvider>());
var actionBindingContextProvider = new Mock<IActionBindingContextProvider>();
actionBindingContextProvider.Setup(p => p.GetActionBindingContextAsync(It.IsAny<ActionContext>()))
.Returns(Task.FromResult(bindingContext));
var inputFormattersProvider = new Mock<IInputFormattersProvider>();
inputFormattersProvider.SetupGet(o => o.InputFormatters)
.Returns(new List<IInputFormatter>());
var invoker = new ControllerActionInvoker(actionContext,
actionBindingContextProvider.Object,
Mock.Of<INestedProviderManager<FilterProviderContext>>(),
Mock.Of<IControllerFactory>(),
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<object, int> method = x => 1;
var actionDescriptor = new ControllerActionDescriptor
{
MethodInfo = method.Method,
Parameters = new List<ParameterDescriptor>
{
new ParameterDescriptor
{
Name = "foo",
ParameterBindingInfo = new ParameterBindingInfo("foo", typeof(object))
}
}
};
var value = "Hello world";
var binder = new Mock<IModelBinder>();
var metadataProvider = new EmptyModelMetadataProvider();
binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
.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<HttpContext>()),
actionDescriptor);
var bindingContext = new ActionBindingContext(actionContext,
Mock.Of<IModelMetadataProvider>(),
binder.Object,
Mock.Of<IValueProvider>(),
Mock.Of<IInputFormatterSelector>(),
Mock.Of<IModelValidatorProvider>());
var actionBindingContextProvider = new Mock<IActionBindingContextProvider>();
actionBindingContextProvider.Setup(p => p.GetActionBindingContextAsync(It.IsAny<ActionContext>()))
.Returns(Task.FromResult(bindingContext));
var inputFormattersProvider = new Mock<IInputFormattersProvider>();
inputFormattersProvider.SetupGet(o => o.InputFormatters)
.Returns(new List<IInputFormatter>());
var invoker = new ControllerActionInvoker(actionContext,
actionBindingContextProvider.Object,
Mock.Of<INestedProviderManager<FilterProviderContext>>(),
Mock.Of<IControllerFactory>(),
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<ParameterDescriptor>
{
new ParameterDescriptor
{
Name = "bodyParam",
BodyParameterInfo = new BodyParameterInfo(typeof(Person))
}
},
FilterDescriptors = new List<FilterDescriptor>()
};
var context = new DefaultHttpContext();
var routeContext = new RouteContext(context);
var actionContext = new ActionContext(routeContext,
actionDescriptor);
var bindingContext = new ActionBindingContext(actionContext,
Mock.Of<IModelMetadataProvider>(),
Mock.Of<IModelBinder>(),
Mock.Of<IValueProvider>(),
Mock.Of<IInputFormatterSelector>(),
Mock.Of<IModelValidatorProvider>());
var actionBindingContextProvider = new Mock<IActionBindingContextProvider>();
actionBindingContextProvider.Setup(p => p.GetActionBindingContextAsync(It.IsAny<ActionContext>()))
.Returns(Task.FromResult(bindingContext));
var controllerFactory = new Mock<IControllerFactory>();
controllerFactory.Setup(c => c.CreateController(It.IsAny<ActionContext>()))
.Returns(new TestController());
var inputFormattersProvider = new Mock<IInputFormattersProvider>();
inputFormattersProvider.SetupGet(o => o.InputFormatters)
.Returns(new List<IInputFormatter>());
var invoker = new ControllerActionInvoker(actionContext,
actionBindingContextProvider.Object,
Mock.Of<INestedProviderManager<FilterProviderContext>>(),
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<IInputFormatter>());
var invoker = new ControllerActionInvoker(actionContext,
actionBindingContextProvider.Object,
Mock.Of<INestedProviderManager<FilterProviderContext>>(),
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<FilterProviderContext> filterProvider,
Mock<IControllerFactory> controllerFactoryMock,
ControllerActionDescriptor descriptor,
IInputFormattersProvider inputFormattersProvider,
IBodyModelValidator bodyModelValidator) :
IControllerActionArgumentBinder controllerActionArgumentBinder) :
base(actionContext,
bindingContextProvider,
filterProvider,
controllerFactoryMock.Object,
descriptor,
inputFormattersProvider,
bodyModelValidator)
controllerActionArgumentBinder)
{
_factoryMock = controllerFactoryMock;
}

View File

@ -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 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<Person><Name>" + sampleName + "</Name></Person>";
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 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<User><Name>" + sampleName + "</Name><UserName>" + sampleUserName + "</UserName></User>";
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<IInputFormatter>());
return new ControllerActionInvoker(actionContext,
actionBindingContextProvider.Object,
Mock.Of<INestedProviderManager<FilterProviderContext>>(),
Mock.Of<IControllerFactory>(),
actionDescriptor,
inputFormattersProvider.Object,
new DefaultBodyModelValidator());
Mock.Of<IControllerActionArgumentBinder>());
}
private static ActionContext GetActionContext(byte[] contentBytes,

View File

@ -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<ParameterDescriptor>
{
new ParameterDescriptor
{
Name = "bodyParam",
ParameterType = typeof(Person),
},
new ParameterDescriptor
{
Name = "bodyParam1",
ParameterType = typeof(Person),
}
}
};
var binder = new Mock<IModelBinder>();
var metadataProvider = new DataAnnotationsModelMetadataProvider();
var actionContext = new ActionContext(new RouteContext(Mock.Of<HttpContext>()),
actionDescriptor);
actionContext.Controller = Mock.Of<object>();
var bindingContext = new ActionBindingContext(actionContext,
metadataProvider,
Mock.Of<IModelBinder>(),
Mock.Of<IValueProvider>(),
Mock.Of<IInputFormatterSelector>(),
Mock.Of<IModelValidatorProvider>());
var actionBindingContextProvider = new Mock<IActionBindingContextProvider>();
actionBindingContextProvider.Setup(p => p.GetActionBindingContextAsync(It.IsAny<ActionContext>()))
.Returns(Task.FromResult(bindingContext));
var invoker = new DefaultControllerActionArgumentBinder(
actionBindingContextProvider.Object, Mock.Of<IBodyModelValidator>());
// Act
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
() => 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<object, int> method = foo => 1;
var actionDescriptor = new ControllerActionDescriptor
{
MethodInfo = method.Method,
Parameters = new List<ParameterDescriptor>
{
new ParameterDescriptor
{
Name = "foo",
ParameterBindingInfo = new ParameterBindingInfo("foo", typeof(object))
}
}
};
var binder = new Mock<IModelBinder>();
binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
.Returns(Task.FromResult(result: false));
var actionContext = new ActionContext(new RouteContext(Mock.Of<HttpContext>()),
actionDescriptor);
actionContext.Controller = Mock.Of<object>();
var bindingContext = new ActionBindingContext(actionContext,
Mock.Of<IModelMetadataProvider>(),
binder.Object,
Mock.Of<IValueProvider>(),
Mock.Of<IInputFormatterSelector>(),
Mock.Of<IModelValidatorProvider>());
var inputFormattersProvider = new Mock<IInputFormattersProvider>();
inputFormattersProvider.SetupGet(o => o.InputFormatters)
.Returns(new List<IInputFormatter>());
var actionBindingContextProvider = new Mock<IActionBindingContextProvider>();
actionBindingContextProvider.Setup(p => p.GetActionBindingContextAsync(It.IsAny<ActionContext>()))
.Returns(Task.FromResult(bindingContext));
var invoker = new DefaultControllerActionArgumentBinder(
actionBindingContextProvider.Object, Mock.Of<IBodyModelValidator>());
// Act
var result = await invoker.GetActionArgumentsAsync(actionContext);
// Assert
Assert.Empty(result);
}
[Fact]
public async Task GetActionArgumentsAsync_AddsActionArguments_IfBinderReturnsTrue()
{
// Arrange
Func<object, int> method = foo => 1;
var actionDescriptor = new ControllerActionDescriptor
{
MethodInfo = method.Method,
Parameters = new List<ParameterDescriptor>
{
new ParameterDescriptor
{
Name = "foo",
ParameterType = typeof(string),
}
}
};
var value = "Hello world";
var binder = new Mock<IModelBinder>();
var metadataProvider = new EmptyModelMetadataProvider();
binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
.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<HttpContext>()),
actionDescriptor);
actionContext.Controller = Mock.Of<object>();
var bindingContext = new ActionBindingContext(actionContext,
metadataProvider,
binder.Object,
Mock.Of<IValueProvider>(),
Mock.Of<IInputFormatterSelector>(),
Mock.Of<IModelValidatorProvider>());
var actionBindingContextProvider = new Mock<IActionBindingContextProvider>();
actionBindingContextProvider.Setup(p => p.GetActionBindingContextAsync(It.IsAny<ActionContext>()))
.Returns(Task.FromResult(bindingContext));
var invoker = new DefaultControllerActionArgumentBinder(
actionBindingContextProvider.Object, Mock.Of<IBodyModelValidator>());
// 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
{
}
}
}

View File

@ -67,7 +67,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
{
{ "", null }
};
var valueProvider = new DictionaryBasedValueProvider(values);
var valueProvider = new DictionaryBasedValueProvider<TestValueBinderMarker>(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<TestValueBinderMarker>(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

View File

@ -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<IApplicationBuilder> _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<KeyValuePair<string, string>>
{
new KeyValuePair<string,string>("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<InvalidOperationException>(() =>
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<Employee>(
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<Person>(
await response.Content.ReadAsStringAsync());
Assert.NotNull(person);
Assert.Equal("somename", person.Name);
Assert.Equal(12, person.Age);
}
[Fact]
public async Task ModelBindCancellationTokenParameteres()
{

View File

@ -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;
}
}
}

View File

@ -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<object[]> 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<string, object>(StringComparer.OrdinalIgnoreCase);
var unrelatedMarker = new UnrelatedValueBinderMarker();
var valueProvider1 = GetMockValueProvider(binderMarker);
var valueProvider2 = GetMockValueProvider(unrelatedMarker);
var provider = new CompositeValueProvider(new List<IValueProvider>() { valueProvider1.Object, valueProvider2.Object });
// Act
var result = provider.Filter(binderMarker);
// Assert
var valueProvider = Assert.IsType<CompositeValueProvider>(result);
var filteredProvider = Assert.Single(valueProvider);
// should not be unrelated marker.
Assert.Same(valueProvider1.Object, filteredProvider);
}
private Mock<IMarkerAwareValueProvider> GetMockValueProvider(IValueBinderMarker marker)
{
var valueProvider = new Mock<IMarkerAwareValueProvider>();
valueProvider.Setup(o => o.Filter(marker))
.Returns(valueProvider.Object);
return valueProvider;
}
private class TestValueBinderMarker : IValueBinderMarker
{
}
private class DerivedValueBinder : TestValueBinderMarker
{
}
private class UnrelatedValueBinderMarker : IValueBinderMarker
{
}
}
}
#endif

View File

@ -18,7 +18,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
{ "test-key", "value" }
};
var provider = new DictionaryBasedValueProvider(values);
var provider = new DictionaryBasedValueProvider<TestValueBinderMarker>(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<TestValueBinderMarker>(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<TestValueBinderMarker>(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<TestValueBinderMarker>(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<TestValueBinderMarker>(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<object[]> 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<string, object>(StringComparer.OrdinalIgnoreCase);
var provider = new DictionaryBasedValueProvider<TestValueBinderMarker>(values);
// Act
var result = provider.Filter(binderMarker);
// Assert
Assert.NotNull(result);
Assert.IsType<DictionaryBasedValueProvider<TestValueBinderMarker>>(result);
}
private class TestValueBinderMarker : IValueBinderMarker
{
}
private class DerivedValueBinder :TestValueBinderMarker
{
}
}
}

View File

@ -43,7 +43,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
var result = factory.GetValueProvider(context);
// Assert
var valueProvider = Assert.IsType<ReadableStringCollectionValueProvider>(result);
var valueProvider = Assert.IsType<ReadableStringCollectionValueProvider<IFormDataMarker>>(result);
Assert.Equal(CultureInfo.CurrentCulture, valueProvider.Culture);
}

View File

@ -33,7 +33,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
var result = _factory.GetValueProvider(factoryContext);
// Assert
var valueProvider = Assert.IsType<ReadableStringCollectionValueProvider>(result);
var valueProvider = Assert.IsType<ReadableStringCollectionValueProvider<IQueryBinderMarker>>(result);
Assert.Equal(CultureInfo.InvariantCulture, valueProvider.Culture);
}
#endif

View File

@ -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<string, string[]>());
var valueProvider = new ReadableStringCollectionValueProvider(backingStore, null);
var valueProvider = new ReadableStringCollectionValueProvider<TestValueBinderMarker>(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<TestValueBinderMarker>(_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<TestValueBinderMarker>(_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<TestValueBinderMarker>(_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<TestValueBinderMarker>(_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<TestValueBinderMarker>(_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<TestValueBinderMarker>(_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<TestValueBinderMarker>(_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<TestValueBinderMarker>(_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<TestValueBinderMarker>(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<TestValueBinderMarker>(_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<object[]> 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<TestValueBinderMarker>(_backingStore, null);
// Act
var result = valueProvider.Filter(binderMarker);
// Assert
Assert.NotNull(result);
Assert.IsType<ReadableStringCollectionValueProvider<TestValueBinderMarker>>(result);
}
private class TestValueBinderMarker : IValueBinderMarker
{
}
private class DerivedValueBinder : TestValueBinderMarker
{
}
}
}

View File

@ -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]

View File

@ -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;
}
}
}

View File

@ -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)
{
}
}
}

View File

@ -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;
}
}
}

View File

@ -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)
{
}
}
}

View File

@ -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; }
}
}

View File

@ -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
{
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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;
}
}
}
}