[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:
parent
8cf74afc75
commit
8f933b2ac3
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
if (actionDescriptor == null)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
Resources.FormatDefaultControllerFactory_ActionDescriptorMustBeReflected(
|
||||
Resources.FormatActionDescriptorMustBeBasedOnControllerAction(
|
||||
typeof(ControllerActionDescriptor)),
|
||||
"actionContext");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
{
|
||||
CacheKey = prototype.CacheKey;
|
||||
PrototypeCache = prototype.PrototypeCache;
|
||||
|
||||
Marker = prototype.Marker;
|
||||
_isComplexType = prototype.IsComplexType;
|
||||
_isComplexTypeComputed = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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>();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue