Introducing ModelBinderFactory
This change separates model binding into IModelBinderProvider (decides which binder to use) and IModelBinder (does binding). The IModelBinderFactory is a new services with coordinates the creation of model binders.
This commit is contained in:
parent
7d43851952
commit
fb81a5e11e
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates <see cref="IModelBinder"/> instances. Register <see cref="IModelBinderProvider"/>
|
||||
/// instances in <c>MvcOptions</c>.
|
||||
/// </summary>
|
||||
public interface IModelBinderProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a <see cref="IModelBinder"/> based on <see cref="ModelBinderProviderContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="ModelBinderProviderContext"/>.</param>
|
||||
/// <returns>An <see cref="IModelBinder"/>.</returns>
|
||||
IModelBinder GetBinder(ModelBinderProviderContext context);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,13 +3,14 @@
|
|||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||
{
|
||||
/// <summary>
|
||||
/// A key type which identifies a <see cref="ModelMetadata"/>.
|
||||
/// </summary>
|
||||
public struct ModelMetadataIdentity
|
||||
public struct ModelMetadataIdentity : IEquatable<ModelMetadataIdentity>
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a <see cref="ModelMetadataIdentity"/> for the provided model <see cref="Type"/>.
|
||||
|
|
@ -98,5 +99,31 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
/// the current instance represents a type.
|
||||
/// </summary>
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Equals(ModelMetadataIdentity other)
|
||||
{
|
||||
return
|
||||
ContainerType == other.ContainerType &&
|
||||
ModelType == other.ModelType &&
|
||||
Name == other.Name;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var other = obj as ModelMetadataIdentity?;
|
||||
return other.HasValue && Equals(other.Value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hash = new HashCodeCombiner();
|
||||
hash.Add(ContainerType);
|
||||
hash.Add(ModelType);
|
||||
hash.Add(Name, StringComparer.Ordinal);
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
{
|
||||
/// <summary>
|
||||
/// A context object for <see cref="IModelBinderProvider.GetBinder"/>.
|
||||
/// </summary>
|
||||
public abstract class ModelBinderProviderContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an <see cref="IModelBinder"/> for the given <paramref name="metadata"/>.
|
||||
/// </summary>
|
||||
/// <param name="metadata">The <see cref="ModelMetadata"/> for the model.</param>
|
||||
/// <returns>An <see cref="IModelBinder"/>.</returns>
|
||||
public abstract IModelBinder CreateBinder(ModelMetadata metadata);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="BindingInfo"/>. May be <c>null</c>.
|
||||
/// </summary>
|
||||
public abstract BindingInfo BindingInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ModelMetadata"/>.
|
||||
/// </summary>
|
||||
public abstract ModelMetadata Metadata { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IModelMetadataProvider"/>.
|
||||
/// </summary>
|
||||
public abstract IModelMetadataProvider MetadataProvider { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -16,28 +16,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
/// </summary>
|
||||
public abstract string BinderModelName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Type"/> of an <see cref="IModelBinder"/> associated with the
|
||||
/// <see cref="Model"/>.
|
||||
/// </summary>
|
||||
public abstract Type BinderType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value which represents the <see cref="ModelBinding.BindingSource"/> associated with the
|
||||
/// <see cref="Model"/>.
|
||||
/// </summary>
|
||||
public abstract BindingSource BindingSource { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value that indicates whether the binder should use an empty prefix to look up
|
||||
/// values in <see cref="IValueProvider"/> when no values are found using the <see cref="ModelName"/> prefix.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Passed into the model binding system. Should not be <c>true</c> when <see cref="IsTopLevelObject"/> is
|
||||
/// <c>false</c>.
|
||||
/// </remarks>
|
||||
public abstract bool FallbackToEmptyPrefix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the current field being bound.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
/// A metadata representation of a model type, property or parameter.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("{DebuggerToString(),nq}")]
|
||||
public abstract class ModelMetadata
|
||||
public abstract class ModelMetadata : IEquatable<ModelMetadata>
|
||||
{
|
||||
/// <summary>
|
||||
/// The default value of <see cref="ModelMetadata.Order"/>.
|
||||
|
|
@ -378,6 +378,36 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
return DisplayName ?? PropertyName ?? ModelType.Name;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Equals(ModelMetadata other)
|
||||
{
|
||||
if (object.ReferenceEquals(this, other))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (other == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Identity.Equals(other.Identity);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return base.Equals(obj as ModelMetadata);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Identity.GetHashCode();
|
||||
}
|
||||
|
||||
private void InitializeTypeInformation()
|
||||
{
|
||||
Debug.Assert(ModelType != null);
|
||||
|
|
|
|||
|
|
@ -35,11 +35,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
/// </summary>
|
||||
public IValueProvider ValueProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="IModelBinder"/> associated with this context.
|
||||
/// </summary>
|
||||
public IModelBinder ModelBinder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="IModelMetadataProvider"/> associated with this context.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
{
|
||||
private ControllerContext _controllerContext;
|
||||
private IModelMetadataProvider _metadataProvider;
|
||||
private IModelBinderFactory _modelBinderFactory;
|
||||
private IObjectModelValidator _objectValidator;
|
||||
private IUrlHelper _url;
|
||||
|
||||
|
|
@ -141,6 +142,31 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="IModelBinderFactory"/>.
|
||||
/// </summary>
|
||||
public IModelBinderFactory ModelBinderFactory
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_modelBinderFactory == null)
|
||||
{
|
||||
_modelBinderFactory = HttpContext?.RequestServices?.GetRequiredService<IModelBinderFactory>();
|
||||
}
|
||||
|
||||
return _modelBinderFactory;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
_modelBinderFactory = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="IUrlHelper"/>.
|
||||
/// </summary>
|
||||
|
|
@ -1139,7 +1165,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
prefix,
|
||||
ControllerContext,
|
||||
MetadataProvider,
|
||||
new CompositeModelBinder(ControllerContext.ModelBinders),
|
||||
ModelBinderFactory,
|
||||
valueProvider,
|
||||
ControllerContext.InputFormatters,
|
||||
ObjectValidator,
|
||||
|
|
@ -1179,7 +1205,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
prefix,
|
||||
ControllerContext,
|
||||
MetadataProvider,
|
||||
new CompositeModelBinder(ControllerContext.ModelBinders),
|
||||
ModelBinderFactory,
|
||||
new CompositeValueProvider(ControllerContext.ValueProviders),
|
||||
ControllerContext.InputFormatters,
|
||||
ObjectValidator,
|
||||
|
|
@ -1219,7 +1245,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
prefix,
|
||||
ControllerContext,
|
||||
MetadataProvider,
|
||||
new CompositeModelBinder(ControllerContext.ModelBinders),
|
||||
ModelBinderFactory,
|
||||
new CompositeValueProvider(ControllerContext.ValueProviders),
|
||||
ControllerContext.InputFormatters,
|
||||
ObjectValidator,
|
||||
|
|
@ -1267,7 +1293,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
prefix,
|
||||
ControllerContext,
|
||||
MetadataProvider,
|
||||
new CompositeModelBinder(ControllerContext.ModelBinders),
|
||||
ModelBinderFactory,
|
||||
valueProvider,
|
||||
ControllerContext.InputFormatters,
|
||||
ObjectValidator,
|
||||
|
|
@ -1314,7 +1340,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
prefix,
|
||||
ControllerContext,
|
||||
MetadataProvider,
|
||||
new CompositeModelBinder(ControllerContext.ModelBinders),
|
||||
ModelBinderFactory,
|
||||
valueProvider,
|
||||
ControllerContext.InputFormatters,
|
||||
ObjectValidator,
|
||||
|
|
@ -1353,7 +1379,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
prefix,
|
||||
ControllerContext,
|
||||
MetadataProvider,
|
||||
new CompositeModelBinder(ControllerContext.ModelBinders),
|
||||
ModelBinderFactory,
|
||||
new CompositeValueProvider(ControllerContext.ValueProviders),
|
||||
ControllerContext.InputFormatters,
|
||||
ObjectValidator,
|
||||
|
|
@ -1405,7 +1431,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
prefix,
|
||||
ControllerContext,
|
||||
MetadataProvider,
|
||||
new CompositeModelBinder(ControllerContext.ModelBinders),
|
||||
ModelBinderFactory,
|
||||
valueProvider,
|
||||
ControllerContext.InputFormatters,
|
||||
ObjectValidator,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
public class ControllerContext : ActionContext
|
||||
{
|
||||
private FormatterCollection<IInputFormatter> _inputFormatters;
|
||||
private IList<IModelBinder> _modelBinders;
|
||||
private IList<IModelValidatorProvider> _validatorProviders;
|
||||
private IList<IValueProvider> _valueProviders;
|
||||
|
||||
|
|
@ -81,31 +80,6 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of <see cref="IModelBinder"/> instances for the current request.
|
||||
/// </summary>
|
||||
public virtual IList<IModelBinder> ModelBinders
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_modelBinders == null)
|
||||
{
|
||||
_modelBinders = new List<IModelBinder>();
|
||||
}
|
||||
|
||||
return _modelBinders;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
_modelBinders = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of <see cref="IModelValidatorProvider"/> instances for the current request.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -143,6 +143,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
var options = serviceProvider.GetRequiredService<IOptions<MvcOptions>>().Value;
|
||||
return new DefaultCompositeMetadataDetailsProvider(options.ModelMetadataDetailsProviders);
|
||||
}));
|
||||
services.TryAddSingleton<IModelBinderFactory, ModelBinderFactory>();
|
||||
services.TryAddSingleton<IObjectModelValidator, DefaultObjectValidator>();
|
||||
services.TryAddSingleton<ValidatorCache>();
|
||||
services.TryAddSingleton<ClientValidatorCache>();
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ControllerActionDescriptor descriptor,
|
||||
IReadOnlyList<IInputFormatter> inputFormatters,
|
||||
IControllerActionArgumentBinder argumentBinder,
|
||||
IReadOnlyList<IModelBinder> modelBinders,
|
||||
IReadOnlyList<IModelValidatorProvider> modelValidatorProviders,
|
||||
IReadOnlyList<IValueProviderFactory> valueProviderFactories,
|
||||
ILogger logger,
|
||||
|
|
@ -42,7 +41,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
actionContext,
|
||||
controllerActionInvokerCache,
|
||||
inputFormatters,
|
||||
modelBinders,
|
||||
modelValidatorProviders,
|
||||
valueProviderFactories,
|
||||
logger,
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
private readonly IControllerFactory _controllerFactory;
|
||||
private readonly ControllerActionInvokerCache _controllerActionInvokerCache;
|
||||
private readonly IReadOnlyList<IInputFormatter> _inputFormatters;
|
||||
private readonly IReadOnlyList<IModelBinder> _modelBinders;
|
||||
private readonly IReadOnlyList<IModelValidatorProvider> _modelValidatorProviders;
|
||||
private readonly IReadOnlyList<IValueProviderFactory> _valueProviderFactories;
|
||||
private readonly int _maxModelValidationErrors;
|
||||
|
|
@ -40,7 +39,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
_controllerActionInvokerCache = controllerActionInvokerCache;
|
||||
_argumentBinder = argumentBinder;
|
||||
_inputFormatters = optionsAccessor.Value.InputFormatters.ToArray();
|
||||
_modelBinders = optionsAccessor.Value.ModelBinders.ToArray();
|
||||
_modelValidatorProviders = optionsAccessor.Value.ModelValidatorProviders.ToArray();
|
||||
_valueProviderFactories = optionsAccessor.Value.ValueProviderFactories.ToArray();
|
||||
_maxModelValidationErrors = optionsAccessor.Value.MaxModelValidationErrors;
|
||||
|
|
@ -72,7 +70,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
actionDescriptor,
|
||||
_inputFormatters,
|
||||
_argumentBinder,
|
||||
_modelBinders,
|
||||
_modelValidatorProviders,
|
||||
_valueProviderFactories,
|
||||
_logger,
|
||||
|
|
|
|||
|
|
@ -24,14 +24,17 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
typeof(ControllerArgumentBinder).GetTypeInfo().GetDeclaredMethod(
|
||||
nameof(CallPropertyAddRange));
|
||||
|
||||
private readonly IModelBinderFactory _modelBinderFactory;
|
||||
private readonly IModelMetadataProvider _modelMetadataProvider;
|
||||
private readonly IObjectModelValidator _validator;
|
||||
|
||||
public ControllerArgumentBinder(
|
||||
IModelMetadataProvider modelMetadataProvider,
|
||||
IModelBinderFactory modelBinderFactory,
|
||||
IObjectModelValidator validator)
|
||||
{
|
||||
_modelMetadataProvider = modelMetadataProvider;
|
||||
_modelBinderFactory = modelBinderFactory;
|
||||
_validator = validator;
|
||||
}
|
||||
|
||||
|
|
@ -118,7 +121,31 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
parameter.BindingInfo,
|
||||
parameter.Name);
|
||||
|
||||
await operationContext.ModelBinder.BindModelAsync(modelBindingContext);
|
||||
if (parameter.BindingInfo?.BinderModelName != null)
|
||||
{
|
||||
// The name was set explicitly, always use that as the prefix.
|
||||
modelBindingContext.ModelName = parameter.BindingInfo.BinderModelName;
|
||||
}
|
||||
else if (modelBindingContext.ValueProvider.ContainsPrefix(parameter.Name))
|
||||
{
|
||||
// We have a match for the parameter name, use that as that prefix.
|
||||
modelBindingContext.ModelName = parameter.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No match, fallback to empty string as the prefix.
|
||||
modelBindingContext.ModelName = string.Empty;
|
||||
}
|
||||
|
||||
var binder = _modelBinderFactory.CreateBinder(new ModelBinderFactoryContext()
|
||||
{
|
||||
BindingInfo = parameter.BindingInfo,
|
||||
Metadata = metadata,
|
||||
CacheToken = parameter,
|
||||
});
|
||||
|
||||
await binder.BindModelAsync(modelBindingContext);
|
||||
|
||||
var modelBindingResult = modelBindingContext.Result;
|
||||
if (modelBindingResult != null && modelBindingResult.Value.IsModelSet)
|
||||
{
|
||||
|
|
@ -241,7 +268,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
ActionContext = context,
|
||||
InputFormatters = context.InputFormatters,
|
||||
ModelBinder = new CompositeModelBinder(context.ModelBinders),
|
||||
ValidatorProvider = new CompositeModelValidatorProvider(context.ValidatorProviders),
|
||||
MetadataProvider = _modelMetadataProvider,
|
||||
ValueProvider = new CompositeValueProvider(context.ValueProviders),
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
/// </summary>
|
||||
public class DefaultModelBindingContext : ModelBindingContext
|
||||
{
|
||||
private OperationBindingContext _operationBindingContext;
|
||||
|
||||
private State _state;
|
||||
private readonly Stack<State> _stack = new Stack<State>();
|
||||
|
||||
|
|
@ -57,17 +59,19 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var propertyPredicateProvider =
|
||||
bindingInfo?.PropertyBindingPredicateProvider ?? metadata.PropertyBindingPredicateProvider;
|
||||
|
||||
var valueProvider = operationBindingContext.ValueProvider;
|
||||
var bindingSource = bindingInfo?.BindingSource ?? metadata.BindingSource;
|
||||
if (bindingSource != null && !bindingSource.IsGreedy)
|
||||
{
|
||||
valueProvider = FilterValueProvider(operationBindingContext.ValueProvider, bindingSource);
|
||||
}
|
||||
|
||||
return new DefaultModelBindingContext()
|
||||
{
|
||||
BinderModelName = binderModelName,
|
||||
BindingSource = bindingInfo?.BindingSource ?? metadata.BindingSource,
|
||||
BinderType = bindingInfo?.BinderType ?? metadata.BinderType,
|
||||
BindingSource = bindingSource,
|
||||
PropertyFilter = propertyPredicateProvider?.PropertyFilter,
|
||||
|
||||
// We only support fallback to empty prefix in cases where the model name is inferred from
|
||||
// the parameter or property being bound.
|
||||
FallbackToEmptyPrefix = binderModelName == null,
|
||||
|
||||
// Because this is the top-level context, FieldName and ModelName should be the same.
|
||||
FieldName = binderModelName ?? modelName,
|
||||
ModelName = binderModelName ?? modelName,
|
||||
|
|
@ -76,7 +80,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
ModelMetadata = metadata,
|
||||
ModelState = operationBindingContext.ActionContext.ModelState,
|
||||
OperationBindingContext = operationBindingContext,
|
||||
ValueProvider = operationBindingContext.ValueProvider,
|
||||
ValueProvider = valueProvider,
|
||||
|
||||
ValidationState = new ValidationStateDictionary(),
|
||||
};
|
||||
|
|
@ -106,16 +110,21 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
|
||||
var scope = EnterNestedScope();
|
||||
|
||||
// Only filter if the new BindingSource affects the value providers. Otherwise we want
|
||||
// to preserve the currrent state.
|
||||
if (modelMetadata.BindingSource != null && !modelMetadata.BindingSource.IsGreedy)
|
||||
{
|
||||
ValueProvider = FilterValueProvider(_operationBindingContext.ValueProvider, modelMetadata.BindingSource);
|
||||
}
|
||||
|
||||
Model = model;
|
||||
ModelMetadata = modelMetadata;
|
||||
ModelName = modelName;
|
||||
FieldName = fieldName;
|
||||
BinderModelName = modelMetadata.BinderModelName;
|
||||
BinderType = modelMetadata.BinderType;
|
||||
BindingSource = modelMetadata.BindingSource;
|
||||
PropertyFilter = modelMetadata.PropertyBindingPredicateProvider?.PropertyFilter;
|
||||
|
||||
FallbackToEmptyPrefix = false;
|
||||
IsTopLevelObject = false;
|
||||
|
||||
return scope;
|
||||
|
|
@ -140,14 +149,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
/// <inheritdoc />
|
||||
public override OperationBindingContext OperationBindingContext
|
||||
{
|
||||
get { return _state.OperationBindingContext; }
|
||||
get { return _operationBindingContext; }
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
_state.OperationBindingContext = value;
|
||||
_operationBindingContext = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -231,20 +240,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
set { _state.BindingSource = value; }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type BinderType
|
||||
{
|
||||
get { return _state.BinderType; }
|
||||
set { _state.BinderType = value; }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool FallbackToEmptyPrefix
|
||||
{
|
||||
get { return _state.FallbackToEmptyPrefix; }
|
||||
set { _state.FallbackToEmptyPrefix = value; }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsTopLevelObject
|
||||
{
|
||||
|
|
@ -298,9 +293,24 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
}
|
||||
}
|
||||
|
||||
private static IValueProvider FilterValueProvider(IValueProvider valueProvider, BindingSource bindingSource)
|
||||
{
|
||||
if (bindingSource == null || bindingSource.IsGreedy)
|
||||
{
|
||||
return valueProvider;
|
||||
}
|
||||
|
||||
var bindingSourceValueProvider = valueProvider as IBindingSourceValueProvider;
|
||||
if (bindingSourceValueProvider == null)
|
||||
{
|
||||
return valueProvider;
|
||||
}
|
||||
|
||||
return bindingSourceValueProvider.Filter(bindingSource) ?? new CompositeValueProvider();
|
||||
}
|
||||
|
||||
private struct State
|
||||
{
|
||||
public OperationBindingContext OperationBindingContext;
|
||||
public string FieldName;
|
||||
public object Model;
|
||||
public ModelMetadata ModelMetadata;
|
||||
|
|
@ -313,8 +323,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
|
||||
public string BinderModelName;
|
||||
public BindingSource BindingSource;
|
||||
public Type BinderType;
|
||||
public bool FallbackToEmptyPrefix;
|
||||
public bool IsTopLevelObject;
|
||||
|
||||
public ModelBindingResult? Result;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
private readonly ControllerActionInvokerCache _controllerActionInvokerCache;
|
||||
private readonly IReadOnlyList<IInputFormatter> _inputFormatters;
|
||||
private readonly IReadOnlyList<IModelBinder> _modelBinders;
|
||||
private readonly IReadOnlyList<IModelValidatorProvider> _modelValidatorProviders;
|
||||
private readonly IReadOnlyList<IValueProviderFactory> _valueProviderFactories;
|
||||
private readonly DiagnosticSource _diagnosticSource;
|
||||
|
|
@ -47,7 +46,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ActionContext actionContext,
|
||||
ControllerActionInvokerCache controllerActionInvokerCache,
|
||||
IReadOnlyList<IInputFormatter> inputFormatters,
|
||||
IReadOnlyList<IModelBinder> modelBinders,
|
||||
IReadOnlyList<IModelValidatorProvider> modelValidatorProviders,
|
||||
IReadOnlyList<IValueProviderFactory> valueProviderFactories,
|
||||
ILogger logger,
|
||||
|
|
@ -69,11 +67,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
throw new ArgumentNullException(nameof(inputFormatters));
|
||||
}
|
||||
|
||||
if (modelBinders == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modelBinders));
|
||||
}
|
||||
|
||||
if (modelValidatorProviders == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modelValidatorProviders));
|
||||
|
|
@ -98,7 +91,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
_controllerActionInvokerCache = controllerActionInvokerCache;
|
||||
_inputFormatters = inputFormatters;
|
||||
_modelBinders = modelBinders;
|
||||
_modelValidatorProviders = modelValidatorProviders;
|
||||
_valueProviderFactories = valueProviderFactories;
|
||||
Logger = logger;
|
||||
|
|
@ -356,7 +348,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
// binding.
|
||||
Context.InputFormatters = new FormatterCollection<IInputFormatter>(
|
||||
new CopyOnWriteList<IInputFormatter>(_inputFormatters));
|
||||
Context.ModelBinders = new CopyOnWriteList<IModelBinder>(_modelBinders);
|
||||
Context.ValidatorProviders = new CopyOnWriteList<IModelValidatorProvider>(_modelValidatorProviders);
|
||||
|
||||
var valueProviders = new List<IValueProvider>();
|
||||
|
|
|
|||
|
|
@ -6,9 +6,8 @@ using System.Threading;
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Core;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
|
|
@ -37,17 +36,20 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
messageProvider.ValueMustBeANumberAccessor = Resources.FormatHtmlGeneration_ValueMustBeNumber;
|
||||
|
||||
// Set up ModelBinding
|
||||
options.ModelBinders.Add(new BinderTypeBasedModelBinder());
|
||||
options.ModelBinders.Add(new ServicesModelBinder());
|
||||
options.ModelBinders.Add(new BodyModelBinder(readerFactory));
|
||||
options.ModelBinders.Add(new HeaderModelBinder());
|
||||
options.ModelBinders.Add(new SimpleTypeModelBinder());
|
||||
options.ModelBinders.Add(new CancellationTokenModelBinder());
|
||||
options.ModelBinders.Add(new ByteArrayModelBinder());
|
||||
options.ModelBinders.Add(new FormFileModelBinder());
|
||||
options.ModelBinders.Add(new FormCollectionModelBinder());
|
||||
options.ModelBinders.Add(new GenericModelBinder());
|
||||
options.ModelBinders.Add(new MutableObjectModelBinder());
|
||||
options.ModelBinderProviders.Add(new BinderTypeModelBinderProvider());
|
||||
options.ModelBinderProviders.Add(new ServicesModelBinderProvider());
|
||||
options.ModelBinderProviders.Add(new BodyModelBinderProvider(readerFactory));
|
||||
options.ModelBinderProviders.Add(new HeaderModelBinderProvider());
|
||||
options.ModelBinderProviders.Add(new SimpleTypeModelBinderProvider());
|
||||
options.ModelBinderProviders.Add(new CancellationTokenModelBinderProvider());
|
||||
options.ModelBinderProviders.Add(new ByteArrayModelBinderProvider());
|
||||
options.ModelBinderProviders.Add(new FormFileModelBinderProvider());
|
||||
options.ModelBinderProviders.Add(new FormCollectionModelBinderProvider());
|
||||
options.ModelBinderProviders.Add(new KeyValuePairModelBinderProvider());
|
||||
options.ModelBinderProviders.Add(new DictionaryModelBinderProvider());
|
||||
options.ModelBinderProviders.Add(new ArrayModelBinderProvider());
|
||||
options.ModelBinderProviders.Add(new CollectionModelBinderProvider());
|
||||
options.ModelBinderProviders.Add(new ComplexTypeModelBinderProvider());
|
||||
|
||||
// Set up filters
|
||||
options.Filters.Add(new UnsupportedContentTypeFilter());
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) .NET Foundation. 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.AspNetCore.Mvc.ModelBinding.Internal
|
||||
{
|
||||
// Used as a placeholder to break cycles while building a tree of model binders in ModelBinderFactory.
|
||||
//
|
||||
// When a cycle is detected by a call to Create(...), we create an instance of this class and return it
|
||||
// to break the cycle. Later when the 'real' binder is created we set Inner to point to that.
|
||||
public class PlaceholderBinder : IModelBinder
|
||||
{
|
||||
public IModelBinder Inner { get; set; }
|
||||
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
return Inner.BindModelAsync(bindingContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. 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.Concurrent;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Core;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IModelBinder"/> which can bind a model based on the value of
|
||||
/// <see cref="ModelMetadata.BinderType"/>. The supplied <see cref="IModelBinder"/>
|
||||
/// type will be used to bind the model.
|
||||
/// </summary>
|
||||
public class BinderTypeBasedModelBinder : IModelBinder
|
||||
{
|
||||
private readonly Func<Type, ObjectFactory> _createFactory =
|
||||
(t) => ActivatorUtilities.CreateFactory(t, Type.EmptyTypes);
|
||||
private readonly ConcurrentDictionary<Type, ObjectFactory> _typeActivatorCache =
|
||||
new ConcurrentDictionary<Type, ObjectFactory>();
|
||||
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
if (bindingContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(bindingContext));
|
||||
}
|
||||
|
||||
// This method is optimized to use cached tasks when possible and avoid allocating
|
||||
// using Task.FromResult. If you need to make changes of this nature, profile
|
||||
// allocations afterwards and look for Task<ModelBindingResult>.
|
||||
|
||||
if (bindingContext.BinderType == null)
|
||||
{
|
||||
// Return null so that we are able to continue with the default set of model binders,
|
||||
// if there is no specific model binder provided.
|
||||
return TaskCache.CompletedTask;
|
||||
}
|
||||
|
||||
return BindModelCoreAsync(bindingContext);
|
||||
}
|
||||
|
||||
private async Task BindModelCoreAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
var requestServices = bindingContext.OperationBindingContext.HttpContext.RequestServices;
|
||||
var createFactory = _typeActivatorCache.GetOrAdd(bindingContext.BinderType, _createFactory);
|
||||
var instance = createFactory(requestServices, arguments: null);
|
||||
|
||||
var modelBinder = instance as IModelBinder;
|
||||
if (modelBinder == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatBinderType_MustBeIModelBinder(
|
||||
bindingContext.BinderType.FullName,
|
||||
typeof(IModelBinder).FullName));
|
||||
}
|
||||
|
||||
await modelBinder.BindModelAsync(bindingContext);
|
||||
|
||||
// A model binder was specified by metadata and this binder handles all such cases.
|
||||
// Always tell the model binding system to skip other model binders i.e. return non-null.
|
||||
if (bindingContext.Result == null)
|
||||
{
|
||||
bindingContext.Result = ModelBindingResult.Failed(bindingContext.ModelName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,10 +5,8 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="IModelBinder"/> implementation for binding array values.
|
||||
|
|
@ -16,20 +14,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
/// <typeparam name="TElement">Type of elements in the array.</typeparam>
|
||||
public class ArrayModelBinder<TElement> : CollectionModelBinder<TElement>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ArrayModelBinder{TElement}"/>.
|
||||
/// </summary>
|
||||
/// <param name="elementBinder">
|
||||
/// The <see cref="IModelBinder"/> for binding <typeparamref name="TElement"/>.
|
||||
/// </param>
|
||||
public ArrayModelBinder(IModelBinder elementBinder)
|
||||
: base(elementBinder)
|
||||
{
|
||||
if (bindingContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(bindingContext));
|
||||
}
|
||||
|
||||
if (bindingContext.ModelMetadata.IsReadOnly)
|
||||
{
|
||||
return TaskCache.CompletedTask;
|
||||
}
|
||||
|
||||
return base.BindModelAsync(bindingContext);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -62,8 +55,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
{
|
||||
throw new ArgumentNullException(nameof(target));
|
||||
}
|
||||
|
||||
// Do not attempt to copy values into an array because an array's length is immutable. This choice is also
|
||||
// consistent with MutableObjectModelBinder's handling of a read-only array property.
|
||||
// consistent with our handling of a read-only array property.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IModelBinderProvider"/> for arrays.
|
||||
/// </summary>
|
||||
public class ArrayModelBinderProvider : IModelBinderProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IModelBinder GetBinder(ModelBinderProviderContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
// We don't support binding readonly properties of arrays because we can't resize the
|
||||
// existing value.
|
||||
if (context.Metadata.ModelType.IsArray && !context.Metadata.IsReadOnly)
|
||||
{
|
||||
var elementType = context.Metadata.ElementMetadata.ModelType;
|
||||
var elementBinder = context.CreateBinder(context.Metadata.ElementMetadata);
|
||||
|
||||
var binderType = typeof(ArrayModelBinder<>).MakeGenericType(elementType);
|
||||
return (IModelBinder)Activator.CreateInstance(binderType, elementBinder);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright (c) .NET Foundation. 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.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Core;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IModelBinder"/> for models which specify an <see cref="IModelBinder"/> using
|
||||
/// <see cref="BindingInfo.BinderType"/>.
|
||||
/// </summary>
|
||||
public class BinderTypeModelBinder : IModelBinder
|
||||
{
|
||||
private readonly ObjectFactory _factory;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="BinderTypeModelBinder"/>.
|
||||
/// </summary>
|
||||
/// <param name="binderType">The <see cref="Type"/> of the <see cref="IModelBinder"/>.</param>
|
||||
public BinderTypeModelBinder(Type binderType)
|
||||
{
|
||||
if (binderType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(binderType));
|
||||
}
|
||||
|
||||
if (!typeof(IModelBinder).GetTypeInfo().IsAssignableFrom(binderType.GetTypeInfo()))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
Resources.FormatBinderType_MustBeIModelBinder(
|
||||
binderType.FullName,
|
||||
typeof(IModelBinder).FullName),
|
||||
nameof(binderType));
|
||||
}
|
||||
|
||||
_factory = ActivatorUtilities.CreateFactory(binderType, Type.EmptyTypes);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
if (bindingContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(bindingContext));
|
||||
}
|
||||
|
||||
var requestServices = bindingContext.OperationBindingContext.HttpContext.RequestServices;
|
||||
var binder = (IModelBinder)_factory(requestServices, arguments: null);
|
||||
|
||||
await binder.BindModelAsync(bindingContext);
|
||||
|
||||
if (bindingContext.Result == null)
|
||||
{
|
||||
bindingContext.Result = ModelBindingResult.Failed(bindingContext.ModelName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IModelBinderProvider"/> for models which specify an <see cref="IModelBinder"/>
|
||||
/// using <see cref="BindingInfo.BinderType"/>.
|
||||
/// </summary>
|
||||
public class BinderTypeModelBinderProvider : IModelBinderProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IModelBinder GetBinder(ModelBinderProviderContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (context.BindingInfo?.BinderType != null)
|
||||
{
|
||||
return new BinderTypeModelBinder(context.BindingInfo.BinderType);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Mvc.Core;
|
|||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IModelBinder"/> which binds models from the request body using an <see cref="IInputFormatter"/>
|
||||
|
|
@ -33,37 +33,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
if (bindingContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(bindingContext));
|
||||
}
|
||||
|
||||
// This method is optimized to use cached tasks when possible and avoid allocating
|
||||
// using Task.FromResult. If you need to make changes of this nature, profile
|
||||
// allocations afterwards and look for Task<ModelBindingResult>.
|
||||
|
||||
var allowedBindingSource = bindingContext.BindingSource;
|
||||
if (allowedBindingSource == null ||
|
||||
!allowedBindingSource.CanAcceptDataFrom(BindingSource.Body))
|
||||
{
|
||||
// Formatters are opt-in. This model either didn't specify [FromBody] or specified something
|
||||
// incompatible so let other binders run.
|
||||
return TaskCache.CompletedTask;
|
||||
}
|
||||
|
||||
return BindModelCoreAsync(bindingContext);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to bind the model using formatters.
|
||||
/// </summary>
|
||||
/// <param name="bindingContext">The <see cref="ModelBindingContext"/>.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Task{ModelBindingResult}"/> which when completed returns a <see cref="ModelBindingResult"/>.
|
||||
/// </returns>
|
||||
private async Task BindModelCoreAsync(ModelBindingContext bindingContext)
|
||||
public async Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
if (bindingContext == null)
|
||||
{
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright (c) .NET Foundation. 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.AspNetCore.Mvc.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IModelBinderProvider"/> for deserializing the request body using a formatter.
|
||||
/// </summary>
|
||||
public class BodyModelBinderProvider : IModelBinderProvider
|
||||
{
|
||||
private readonly IHttpRequestStreamReaderFactory _readerFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="BodyModelBinderProvider"/>.
|
||||
/// </summary>
|
||||
/// <param name="readerFactory">The <see cref="IHttpRequestStreamReaderFactory"/>.</param>
|
||||
public BodyModelBinderProvider(IHttpRequestStreamReaderFactory readerFactory)
|
||||
{
|
||||
if (readerFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(readerFactory));
|
||||
}
|
||||
|
||||
_readerFactory = readerFactory;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IModelBinder GetBinder(ModelBinderProviderContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (context.BindingInfo?.BindingSource != null &&
|
||||
context.BindingInfo.BindingSource.CanAcceptDataFrom(BindingSource.Body))
|
||||
{
|
||||
return new BodyModelBinder(_readerFactory);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,10 +5,10 @@ using System;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// ModelBinder to bind Byte Arrays.
|
||||
/// ModelBinder to bind byte Arrays.
|
||||
/// </summary>
|
||||
public class ByteArrayModelBinder : IModelBinder
|
||||
{
|
||||
|
|
@ -20,16 +20,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
throw new ArgumentNullException(nameof(bindingContext));
|
||||
}
|
||||
|
||||
// This method is optimized to use cached tasks when possible and avoid allocating
|
||||
// using Task.FromResult. If you need to make changes of this nature, profile
|
||||
// allocations afterwards and look for Task<ModelBindingResult>.
|
||||
|
||||
// Check if this binder applies.
|
||||
if (bindingContext.ModelType != typeof(byte[]))
|
||||
{
|
||||
return TaskCache.CompletedTask;
|
||||
}
|
||||
|
||||
// Check for missing data case 1: There was no <input ... /> element containing this data.
|
||||
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
|
||||
if (valueProviderResult == ValueProviderResult.None)
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IModelBinderProvider"/> for binding base64 encoded byte arrays.
|
||||
/// </summary>
|
||||
public class ByteArrayModelBinderProvider : IModelBinderProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IModelBinder GetBinder(ModelBinderProviderContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (context.Metadata.ModelType == typeof(byte[]))
|
||||
{
|
||||
return new ByteArrayModelBinder();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="IModelBinder"/> implementation to bind models of type <see cref="CancellationToken"/>.
|
||||
|
|
@ -22,16 +22,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
throw new ArgumentNullException(nameof(bindingContext));
|
||||
}
|
||||
|
||||
if (bindingContext.ModelType == typeof(CancellationToken))
|
||||
{
|
||||
// We need to force boxing now, so we can insert the same reference to the boxed CancellationToken
|
||||
// in both the ValidationState and ModelBindingResult.
|
||||
//
|
||||
// DO NOT simplify this code by removing the cast.
|
||||
var model = (object)bindingContext.OperationBindingContext.HttpContext.RequestAborted;
|
||||
bindingContext.ValidationState.Add(model, new ValidationStateEntry() { SuppressValidation = true });
|
||||
bindingContext.Result = ModelBindingResult.Success(bindingContext.ModelName, model);
|
||||
}
|
||||
// We need to force boxing now, so we can insert the same reference to the boxed CancellationToken
|
||||
// in both the ValidationState and ModelBindingResult.
|
||||
//
|
||||
// DO NOT simplify this code by removing the cast.
|
||||
var model = (object)bindingContext.OperationBindingContext.HttpContext.RequestAborted;
|
||||
bindingContext.ValidationState.Add(model, new ValidationStateEntry() { SuppressValidation = true });
|
||||
bindingContext.Result = ModelBindingResult.Success(bindingContext.ModelName, model);
|
||||
|
||||
return TaskCache.CompletedTask;
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) .NET Foundation. 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IModelBinderProvider"/> for <see cref="CancellationToken"/>.
|
||||
/// </summary>
|
||||
public class CancellationTokenModelBinderProvider : IModelBinderProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IModelBinder GetBinder(ModelBinderProviderContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (context.Metadata.ModelType == typeof(CancellationToken))
|
||||
{
|
||||
return new CancellationTokenModelBinder();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="IModelBinder"/> implementation for binding collection values.
|
||||
|
|
@ -20,6 +20,25 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
/// <typeparam name="TElement">Type of elements in the collection.</typeparam>
|
||||
public class CollectionModelBinder<TElement> : ICollectionModelBinder
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="CollectionModelBinder{TElement}"/>.
|
||||
/// </summary>
|
||||
/// <param name="elementBinder">The <see cref="IModelBinder"/> for binding elements.</param>
|
||||
public CollectionModelBinder(IModelBinder elementBinder)
|
||||
{
|
||||
if (elementBinder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(elementBinder));
|
||||
}
|
||||
|
||||
ElementBinder = elementBinder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IModelBinder"/> instances for binding collection elements.
|
||||
/// </summary>
|
||||
protected IModelBinder ElementBinder { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual async Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
|
|
@ -28,8 +47,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
throw new ArgumentNullException(nameof(bindingContext));
|
||||
}
|
||||
|
||||
ModelBindingHelper.ValidateBindingContext(bindingContext);
|
||||
|
||||
var model = bindingContext.Model;
|
||||
if (!bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName))
|
||||
{
|
||||
|
|
@ -163,7 +180,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
modelName: bindingContext.ModelName,
|
||||
model: null))
|
||||
{
|
||||
await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(bindingContext);
|
||||
await ElementBinder.BindModelAsync(bindingContext);
|
||||
|
||||
if (bindingContext.Result != null && bindingContext.Result.Value.IsModelSet)
|
||||
{
|
||||
|
|
@ -224,7 +241,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
modelName: fullChildName,
|
||||
model: null))
|
||||
{
|
||||
await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(bindingContext);
|
||||
await ElementBinder.BindModelAsync(bindingContext);
|
||||
result = bindingContext.Result;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
// Copyright (c) .NET Foundation. 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.Reflection;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IModelBinderProvider"/> for <see cref="ICollection{T}"/>.
|
||||
/// </summary>
|
||||
public class CollectionModelBinderProvider : IModelBinderProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IModelBinder GetBinder(ModelBinderProviderContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var modelType = context.Metadata.ModelType;
|
||||
|
||||
// Arrays are handled by another binder.
|
||||
if (modelType.IsArray)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// If the model type is ICollection<> then we can call its Add method, so we can always support it.
|
||||
var collectionType = ClosedGenericMatcher.ExtractGenericInterface(modelType, typeof(ICollection<>));
|
||||
if (collectionType != null)
|
||||
{
|
||||
var elementType = collectionType.GenericTypeArguments[0];
|
||||
var elementBinder = context.CreateBinder(context.MetadataProvider.GetMetadataForType(elementType));
|
||||
|
||||
var binderType = typeof(CollectionModelBinder<>).MakeGenericType(collectionType.GenericTypeArguments);
|
||||
return (IModelBinder)Activator.CreateInstance(binderType, elementBinder);
|
||||
}
|
||||
|
||||
// If the model type is IEnumerable<> then we need to know if we can assign a List<> to it, since
|
||||
// that's what we would create. (The cases handled here are IEnumerable<>, IReadOnlyColection<> and
|
||||
// IReadOnlyList<>).
|
||||
//
|
||||
// We need to check IsReadOnly because we need to know if we can SET the property.
|
||||
var enumerableType = ClosedGenericMatcher.ExtractGenericInterface(modelType, typeof(IEnumerable<>));
|
||||
if (enumerableType != null && !context.Metadata.IsReadOnly)
|
||||
{
|
||||
var listType = typeof(List<>).MakeGenericType(enumerableType.GenericTypeArguments);
|
||||
if (modelType.GetTypeInfo().IsAssignableFrom(listType.GetTypeInfo()))
|
||||
{
|
||||
var elementType = enumerableType.GenericTypeArguments[0];
|
||||
var elementBinder = context.CreateBinder(context.MetadataProvider.GetMetadataForType(elementType));
|
||||
|
||||
var binderType = typeof(CollectionModelBinder<>).MakeGenericType(enumerableType.GenericTypeArguments);
|
||||
return (IModelBinder)Activator.CreateInstance(binderType, elementBinder);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,17 +2,36 @@
|
|||
// 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.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="IModelBinder"/> implementation for binding complex values.
|
||||
/// <see cref="IModelBinder"/> implementation for binding complex types.
|
||||
/// </summary>
|
||||
public class MutableObjectModelBinder : IModelBinder
|
||||
public class ComplexTypeModelBinder : IModelBinder
|
||||
{
|
||||
private readonly IDictionary<ModelMetadata, IModelBinder> _propertyBinders;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ComplexTypeModelBinder"/>.
|
||||
/// </summary>
|
||||
/// <param name="propertyBinders">
|
||||
/// The <see cref="IDictionary{TKey, TValue}"/> of binders to use for binding properties.
|
||||
/// </param>
|
||||
public ComplexTypeModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders)
|
||||
{
|
||||
if (propertyBinders == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(propertyBinders));
|
||||
}
|
||||
|
||||
_propertyBinders = propertyBinders;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
|
|
@ -21,12 +40,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
throw new ArgumentNullException(nameof(bindingContext));
|
||||
}
|
||||
|
||||
ModelBindingHelper.ValidateBindingContext(bindingContext);
|
||||
if (!CanBindType(bindingContext.ModelMetadata))
|
||||
{
|
||||
return TaskCache.CompletedTask;
|
||||
}
|
||||
|
||||
if (!CanCreateModel(bindingContext))
|
||||
{
|
||||
return TaskCache.CompletedTask;
|
||||
|
|
@ -135,7 +148,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
/// </returns>
|
||||
protected virtual Task BindProperty(ModelBindingContext bindingContext)
|
||||
{
|
||||
return bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(bindingContext);
|
||||
var binder = _propertyBinders[bindingContext.ModelMetadata];
|
||||
return binder.BindModelAsync(bindingContext);
|
||||
}
|
||||
|
||||
internal bool CanCreateModel(ModelBindingContext bindingContext)
|
||||
|
|
@ -284,22 +298,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
return false;
|
||||
}
|
||||
|
||||
private static bool CanBindType(ModelMetadata modelMetadata)
|
||||
{
|
||||
// Simple types cannot use this binder
|
||||
if (!modelMetadata.IsComplexType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (modelMetadata.IsEnumerableType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Internal for tests
|
||||
internal static bool CanUpdatePropertyInternal(ModelMetadata propertyMetadata)
|
||||
{
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) .NET Foundation. 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IModelBinderProvider"/> for complex types.
|
||||
/// </summary>
|
||||
public class ComplexTypeModelBinderProvider : IModelBinderProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IModelBinder GetBinder(ModelBinderProviderContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType)
|
||||
{
|
||||
var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
|
||||
foreach (var property in context.Metadata.Properties)
|
||||
{
|
||||
propertyBinders.Add(property, context.CreateBinder(property));
|
||||
}
|
||||
|
||||
return new ComplexTypeModelBinder(propertyBinders);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="IModelBinder"/> implementation for binding dictionary values.
|
||||
|
|
@ -21,6 +21,24 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
/// <typeparam name="TValue">Type of values in the dictionary.</typeparam>
|
||||
public class DictionaryModelBinder<TKey, TValue> : CollectionModelBinder<KeyValuePair<TKey, TValue>>
|
||||
{
|
||||
private readonly IModelBinder _valueBinder;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="DictionaryModelBinder{TKey, TValue}"/>.
|
||||
/// </summary>
|
||||
/// <param name="keyBinder">The <see cref="IModelBinder"/> for <typeparamref name="TKey"/>.</param>
|
||||
/// <param name="valueBinder">The <see cref="IModelBinder"/> for <typeparamref name="TValue"/>.</param>
|
||||
public DictionaryModelBinder(IModelBinder keyBinder, IModelBinder valueBinder)
|
||||
: base(new KeyValuePairModelBinder<TKey, TValue>(keyBinder, valueBinder))
|
||||
{
|
||||
if (valueBinder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(valueBinder));
|
||||
}
|
||||
|
||||
_valueBinder = valueBinder;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
|
|
@ -66,8 +84,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider;
|
||||
var valueMetadata = metadataProvider.GetMetadataForType(typeof(TValue));
|
||||
|
||||
var modelBinder = bindingContext.OperationBindingContext.ModelBinder;
|
||||
|
||||
var keyMappings = new Dictionary<string, TKey>(StringComparer.Ordinal);
|
||||
foreach (var kvp in keys)
|
||||
{
|
||||
|
|
@ -81,7 +97,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
modelName: kvp.Value,
|
||||
model: null))
|
||||
{
|
||||
await modelBinder.BindModelAsync(bindingContext);
|
||||
await _valueBinder.BindModelAsync(bindingContext);
|
||||
|
||||
var valueResult = bindingContext.Result;
|
||||
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright (c) .NET Foundation. 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 Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IModelBinderProvider"/> for binding <see cref="IDictionary{TKey, TValue}"/>.
|
||||
/// </summary>
|
||||
public class DictionaryModelBinderProvider : IModelBinderProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IModelBinder GetBinder(ModelBinderProviderContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var modelType = context.Metadata.ModelType;
|
||||
var dictionaryType = ClosedGenericMatcher.ExtractGenericInterface(modelType, typeof(IDictionary<,>));
|
||||
if (dictionaryType != null)
|
||||
{
|
||||
var keyType = dictionaryType.GenericTypeArguments[0];
|
||||
var keyBinder = context.CreateBinder(context.MetadataProvider.GetMetadataForType(keyType));
|
||||
|
||||
var valueType = dictionaryType.GenericTypeArguments[1];
|
||||
var valueBinder = context.CreateBinder(context.MetadataProvider.GetMetadataForType(valueType));
|
||||
|
||||
var binderType = typeof(DictionaryModelBinder<,>).MakeGenericType(dictionaryType.GenericTypeArguments);
|
||||
return (IModelBinder)Activator.CreateInstance(binderType, keyBinder, valueBinder);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,11 +7,10 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="IModelBinder"/> implementation to bind form values to <see cref="IFormCollection"/>.
|
||||
|
|
@ -19,27 +18,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public class FormCollectionModelBinder : IModelBinder
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
public async Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
if (bindingContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(bindingContext));
|
||||
}
|
||||
|
||||
// This method is optimized to use cached tasks when possible and avoid allocating
|
||||
// using Task.FromResult. If you need to make changes of this nature, profile
|
||||
// allocations afterwards and look for Task<ModelBindingResult>.
|
||||
|
||||
if (bindingContext.ModelType != typeof(IFormCollection))
|
||||
{
|
||||
return TaskCache.CompletedTask;
|
||||
}
|
||||
|
||||
return BindModelCoreAsync(bindingContext);
|
||||
}
|
||||
|
||||
private async Task BindModelCoreAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
object model;
|
||||
var request = bindingContext.OperationBindingContext.HttpContext.Request;
|
||||
if (request.HasFormContentType)
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) .NET Foundation. 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.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IModelBinderProvider"/> for <see cref="IFormCollection"/>.
|
||||
/// </summary>
|
||||
public class FormCollectionModelBinderProvider : IModelBinderProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IModelBinder GetBinder(ModelBinderProviderContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (context.Metadata.ModelType == typeof(IFormCollection))
|
||||
{
|
||||
return new FormCollectionModelBinder();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,10 +11,9 @@ using System.Reflection;
|
|||
#endif
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="IModelBinder"/> implementation to bind posted files to <see cref="IFormFile"/>.
|
||||
|
|
@ -22,31 +21,22 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public class FormFileModelBinder : IModelBinder
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
public async Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
if (bindingContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(bindingContext));
|
||||
}
|
||||
|
||||
// This method is optimized to use cached tasks when possible and avoid allocating
|
||||
// using Task.FromResult or async state machines.
|
||||
|
||||
var modelType = bindingContext.ModelType;
|
||||
if (modelType != typeof(IFormFile) && !typeof(IEnumerable<IFormFile>).IsAssignableFrom(modelType))
|
||||
{
|
||||
// Not a type this model binder supports. Let other binders run.
|
||||
return TaskCache.CompletedTask;
|
||||
}
|
||||
|
||||
var createFileCollection = modelType == typeof(IFormFileCollection) &&
|
||||
var createFileCollection =
|
||||
bindingContext.ModelType == typeof(IFormFileCollection) &&
|
||||
!bindingContext.ModelMetadata.IsReadOnly;
|
||||
if (!createFileCollection && !ModelBindingHelper.CanGetCompatibleCollection<IFormFile>(bindingContext))
|
||||
{
|
||||
// Silently fail and stop other model binders running if unable to create an instance or use the
|
||||
// current instance.
|
||||
bindingContext.Result = ModelBindingResult.Failed(bindingContext.ModelName);
|
||||
return TaskCache.CompletedTask;
|
||||
return;
|
||||
}
|
||||
|
||||
ICollection<IFormFile> postedFiles;
|
||||
|
|
@ -59,13 +49,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
postedFiles = ModelBindingHelper.GetCompatibleCollection<IFormFile>(bindingContext);
|
||||
}
|
||||
|
||||
return BindModelCoreAsync(bindingContext, postedFiles);
|
||||
}
|
||||
|
||||
private async Task BindModelCoreAsync(ModelBindingContext bindingContext, ICollection<IFormFile> postedFiles)
|
||||
{
|
||||
Debug.Assert(postedFiles != null);
|
||||
|
||||
// If we're at the top level, then use the FieldName (parameter or property name).
|
||||
// This handles the fact that there will be nothing in the ValueProviders for this parameter
|
||||
// and so we'll do the right thing even though we 'fell-back' to the empty prefix.
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) .NET Foundation. 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.Reflection;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IModelBinderProvider"/> for <see cref="IFormFile"/>, collections
|
||||
/// of <see cref="IFormFile"/>, and <see cref="IFormFileCollection"/>.
|
||||
/// </summary>
|
||||
public class FormFileModelBinderProvider : IModelBinderProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IModelBinder GetBinder(ModelBinderProviderContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var modelType = context.Metadata.ModelType;
|
||||
if (modelType == typeof(IFormFile) ||
|
||||
modelType == typeof(IFormFileCollection) ||
|
||||
typeof(IEnumerable<IFormFile>).GetTypeInfo().IsAssignableFrom(modelType.GetTypeInfo()))
|
||||
{
|
||||
return new FormFileModelBinder();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IModelBinder"/> which binds models from the request headers when a model
|
||||
|
|
@ -25,36 +25,21 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
throw new ArgumentNullException(nameof(bindingContext));
|
||||
}
|
||||
|
||||
// This method is optimized to use cached tasks when possible and avoid allocating
|
||||
// using Task.FromResult or async state machines.
|
||||
|
||||
var allowedBindingSource = bindingContext.BindingSource;
|
||||
if (allowedBindingSource == null ||
|
||||
!allowedBindingSource.CanAcceptDataFrom(BindingSource.Header))
|
||||
{
|
||||
// Headers are opt-in. This model either didn't specify [FromHeader] or specified something
|
||||
// incompatible so let other binders run.
|
||||
return TaskCache.CompletedTask;
|
||||
}
|
||||
|
||||
var request = bindingContext.OperationBindingContext.HttpContext.Request;
|
||||
|
||||
// Property name can be null if the model metadata represents a type (rather than a property or parameter).
|
||||
var headerName = bindingContext.FieldName;
|
||||
|
||||
object model;
|
||||
if (ModelBindingHelper.CanGetCompatibleCollection<string>(bindingContext))
|
||||
if (bindingContext.ModelType == typeof(string))
|
||||
{
|
||||
if (bindingContext.ModelType == typeof(string))
|
||||
{
|
||||
var value = request.Headers[headerName];
|
||||
model = (string)value;
|
||||
}
|
||||
else
|
||||
{
|
||||
var values = request.Headers.GetCommaSeparatedValues(headerName);
|
||||
model = GetCompatibleCollection(bindingContext, values);
|
||||
}
|
||||
var value = request.Headers[headerName];
|
||||
model = (string)value;
|
||||
}
|
||||
else if (ModelBindingHelper.CanGetCompatibleCollection<string>(bindingContext))
|
||||
{
|
||||
var values = request.Headers.GetCommaSeparatedValues(headerName);
|
||||
model = GetCompatibleCollection(bindingContext, values);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IModelBinderProvider"/> for binding header values.
|
||||
/// </summary>
|
||||
public class HeaderModelBinderProvider : IModelBinderProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IModelBinder GetBinder(ModelBinderProviderContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (context.BindingInfo?.BindingSource != null &&
|
||||
context.BindingInfo.BindingSource.CanAcceptDataFrom(BindingSource.Header))
|
||||
{
|
||||
// We only support strings and collections of strings. Some cases can fail
|
||||
// at runtime due to collections we can't modify.
|
||||
if (context.Metadata.ModelType == typeof(string) ||
|
||||
context.Metadata.ElementType == typeof(string))
|
||||
{
|
||||
return new HeaderModelBinder();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,10 +5,40 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public sealed class KeyValuePairModelBinder<TKey, TValue> : IModelBinder
|
||||
/// <summary>
|
||||
/// An <see cref="IModelBinder"/> for <see cref="KeyValuePair{TKey, TValue}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The key type.</typeparam>
|
||||
/// <typeparam name="TValue">The value type.</typeparam>
|
||||
public class KeyValuePairModelBinder<TKey, TValue> : IModelBinder
|
||||
{
|
||||
private readonly IModelBinder _keyBinder;
|
||||
private readonly IModelBinder _valueBinder;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="KeyValuePair{TKey, TValue}"/>.
|
||||
/// </summary>
|
||||
/// <param name="keyBinder">The <see cref="IModelBinder"/> for <typeparamref name="TKey"/>.</param>
|
||||
/// <param name="valueBinder">The <see cref="IModelBinder"/> for <typeparamref name="TValue"/>.</param>
|
||||
public KeyValuePairModelBinder(IModelBinder keyBinder, IModelBinder valueBinder)
|
||||
{
|
||||
if (keyBinder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(keyBinder));
|
||||
}
|
||||
|
||||
if (valueBinder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(valueBinder));
|
||||
}
|
||||
|
||||
_keyBinder = keyBinder;
|
||||
_valueBinder = valueBinder;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
if (bindingContext == null)
|
||||
|
|
@ -16,14 +46,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
throw new ArgumentNullException(nameof(bindingContext));
|
||||
}
|
||||
|
||||
if (bindingContext.ModelType != typeof(KeyValuePair<TKey, TValue>))
|
||||
{
|
||||
// This binder does not apply.
|
||||
return;
|
||||
}
|
||||
|
||||
var keyResult = await TryBindStrongModel<TKey>(bindingContext, "Key");
|
||||
var valueResult = await TryBindStrongModel<TValue>(bindingContext, "Value");
|
||||
var keyResult = await TryBindStrongModel<TKey>(bindingContext, _keyBinder, "Key");
|
||||
var valueResult = await TryBindStrongModel<TValue>(bindingContext, _valueBinder, "Value");
|
||||
|
||||
if (keyResult.IsModelSet && valueResult.IsModelSet)
|
||||
{
|
||||
|
|
@ -70,6 +94,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
|
||||
internal async Task<ModelBindingResult> TryBindStrongModel<TModel>(
|
||||
ModelBindingContext bindingContext,
|
||||
IModelBinder binder,
|
||||
string propertyName)
|
||||
{
|
||||
var propertyModelMetadata =
|
||||
|
|
@ -83,9 +108,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
modelName: propertyModelName,
|
||||
model: null))
|
||||
{
|
||||
await binder.BindModelAsync(bindingContext);
|
||||
|
||||
await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(
|
||||
bindingContext);
|
||||
var result = bindingContext.Result;
|
||||
if (result != null && result.Value.IsModelSet)
|
||||
{
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) .NET Foundation. 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.Reflection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IModelBinderProvider"/> for <see cref="KeyValuePair{TKey, TValue}"/>.
|
||||
/// </summary>
|
||||
public class KeyValuePairModelBinderProvider : IModelBinderProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IModelBinder GetBinder(ModelBinderProviderContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var modelTypeInfo = context.Metadata.ModelType.GetTypeInfo();
|
||||
if (modelTypeInfo.IsGenericType &&
|
||||
modelTypeInfo.GetGenericTypeDefinition().GetTypeInfo() == typeof(KeyValuePair<,>).GetTypeInfo())
|
||||
{
|
||||
var typeArguments = modelTypeInfo.GenericTypeArguments;
|
||||
|
||||
var keyMetadata = context.MetadataProvider.GetMetadataForType(typeArguments[0]);
|
||||
var keyBinder = context.CreateBinder(keyMetadata);
|
||||
|
||||
var valueMetadata = context.MetadataProvider.GetMetadataForType(typeArguments[1]);
|
||||
var valueBinder = context.CreateBinder(valueMetadata);
|
||||
|
||||
var binderType = typeof(KeyValuePairModelBinder<,>).MakeGenericType(typeArguments);
|
||||
return (IModelBinder)Activator.CreateInstance(binderType, keyBinder, valueBinder);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,13 +2,12 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IModelBinder"/> which binds models from the request services when a model
|
||||
|
|
@ -24,19 +23,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
throw new ArgumentNullException(nameof(bindingContext));
|
||||
}
|
||||
|
||||
// This method is optimized to use cached tasks when possible and avoid allocating
|
||||
// using Task.FromResult. If you need to make changes of this nature, profile
|
||||
// allocations afterwards and look for Task<ModelBindingResult>.
|
||||
|
||||
var allowedBindingSource = bindingContext.BindingSource;
|
||||
if (allowedBindingSource == null ||
|
||||
!allowedBindingSource.CanAcceptDataFrom(BindingSource.Services))
|
||||
{
|
||||
// Services are opt-in. This model either didn't specify [FromService] or specified something
|
||||
// incompatible so let other binders run.
|
||||
return TaskCache.CompletedTask;
|
||||
}
|
||||
|
||||
var requestServices = bindingContext.OperationBindingContext.HttpContext.RequestServices;
|
||||
var model = requestServices.GetRequiredService(bindingContext.ModelType);
|
||||
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IModelBinderProvider"/> for binding from the <see cref="IServiceProvider"/>.
|
||||
/// </summary>
|
||||
public class ServicesModelBinderProvider : IModelBinderProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IModelBinder GetBinder(ModelBinderProviderContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (context.BindingInfo?.BindingSource != null &&
|
||||
context.BindingInfo.BindingSource.CanAcceptDataFrom(BindingSource.Services))
|
||||
{
|
||||
return new ServicesModelBinder();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,10 +5,14 @@ using System;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IModelBinder"/> for simple types.
|
||||
/// </summary>
|
||||
public class SimpleTypeModelBinder : IModelBinder
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
if (bindingContext == null)
|
||||
|
|
@ -16,20 +20,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
throw new ArgumentNullException(nameof(bindingContext));
|
||||
}
|
||||
|
||||
// This method is optimized to use cached tasks when possible and avoid allocating
|
||||
// using Task.FromResult. If you need to make changes of this nature, profile
|
||||
// allocations afterwards and look for Task<ModelBindingResult>.
|
||||
|
||||
if (bindingContext.ModelMetadata.IsComplexType)
|
||||
{
|
||||
// this type cannot be converted
|
||||
return TaskCache.CompletedTask;
|
||||
}
|
||||
|
||||
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
|
||||
if (valueProviderResult == ValueProviderResult.None)
|
||||
{
|
||||
// no entry
|
||||
bindingContext.Result = ModelBindingResult.Failed(bindingContext.ModelName);
|
||||
return TaskCache.CompletedTask;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IModelBinderProvider"/> for binding simple data types.
|
||||
/// </summary>
|
||||
public class SimpleTypeModelBinderProvider : IModelBinderProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IModelBinder GetBinder(ModelBinderProviderContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (!context.Metadata.IsComplexType)
|
||||
{
|
||||
return new SimpleTypeModelBinder();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,171 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an <see cref="IModelBinder"/> that delegates to one of a collection of <see cref="IModelBinder"/>
|
||||
/// instances.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If no binder is available and the <see cref="ModelBindingContext"/> allows it,
|
||||
/// this class tries to find a binder using an empty prefix.
|
||||
/// </remarks>
|
||||
public class CompositeModelBinder : ICompositeModelBinder
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the CompositeModelBinder class.
|
||||
/// </summary>
|
||||
/// <param name="modelBinders">A collection of <see cref="IModelBinder"/> instances.</param>
|
||||
public CompositeModelBinder(IList<IModelBinder> modelBinders)
|
||||
{
|
||||
if (modelBinders == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modelBinders));
|
||||
}
|
||||
|
||||
ModelBinders = modelBinders;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IList<IModelBinder> ModelBinders { get; }
|
||||
|
||||
public virtual Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
if (bindingContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(bindingContext));
|
||||
}
|
||||
|
||||
return RunModelBinders(bindingContext);
|
||||
}
|
||||
|
||||
private async Task RunModelBinders(ModelBindingContext bindingContext)
|
||||
{
|
||||
RuntimeHelpers.EnsureSufficientExecutionStack();
|
||||
|
||||
ModelBindingResult? overallResult = null;
|
||||
try
|
||||
{
|
||||
using (bindingContext.EnterNestedScope())
|
||||
{
|
||||
if (PrepareBindingContext(bindingContext))
|
||||
{
|
||||
// Perf: Avoid allocations
|
||||
for (var i = 0; i < ModelBinders.Count; i++)
|
||||
{
|
||||
var binder = ModelBinders[i];
|
||||
await binder.BindModelAsync(bindingContext);
|
||||
if (bindingContext.Result != null)
|
||||
{
|
||||
var result = bindingContext.Result.Value;
|
||||
// This condition is necessary because the ModelState entry would never be validated if
|
||||
// caller fell back to the empty prefix, leading to an possibly-incorrect !IsValid. In most
|
||||
// (hopefully all) cases, the ModelState entry exists because some binders add errors before
|
||||
// returning a result with !IsModelSet. Those binders often cannot run twice anyhow.
|
||||
if (result.IsModelSet ||
|
||||
bindingContext.ModelState.ContainsKey(bindingContext.ModelName))
|
||||
{
|
||||
if (bindingContext.IsTopLevelObject && result.Model != null)
|
||||
{
|
||||
ValidationStateEntry entry;
|
||||
if (!bindingContext.ValidationState.TryGetValue(result.Model, out entry))
|
||||
{
|
||||
entry = new ValidationStateEntry()
|
||||
{
|
||||
Key = result.Key,
|
||||
Metadata = bindingContext.ModelMetadata,
|
||||
};
|
||||
bindingContext.ValidationState.Add(result.Model, entry);
|
||||
}
|
||||
}
|
||||
|
||||
overallResult = bindingContext.Result;
|
||||
return;
|
||||
}
|
||||
|
||||
// Current binder should have been able to bind value but found nothing. Exit loop in a way that
|
||||
// tells caller to fall back to the empty prefix, if appropriate. Do not return result because it
|
||||
// means only "other binders are not applicable".
|
||||
|
||||
// overallResult MUST still be null at this return statement.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
bindingContext.Result = overallResult;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool PrepareBindingContext(ModelBindingContext bindingContext)
|
||||
{
|
||||
// If the property has a specified data binding sources, we need to filter the set of value providers
|
||||
// to just those that match. We can skip filtering when IsGreedy == true, because that can't use
|
||||
// value providers.
|
||||
//
|
||||
// We also want to base this filtering on the - top-level value provider in case the data source
|
||||
// on this property doesn't intersect with the ambient data source.
|
||||
//
|
||||
// Ex:
|
||||
//
|
||||
// public class Person
|
||||
// {
|
||||
// [FromQuery]
|
||||
// public int Id { get; set; }
|
||||
// }
|
||||
//
|
||||
// public IActionResult UpdatePerson([FromForm] Person person) { }
|
||||
//
|
||||
// In this example, [FromQuery] overrides the ambient data source (form).
|
||||
|
||||
var valueProvider = bindingContext.ValueProvider;
|
||||
var bindingSource = bindingContext.BindingSource;
|
||||
var modelName = bindingContext.ModelName;
|
||||
var fallbackToEmptyPrefix = bindingContext.FallbackToEmptyPrefix;
|
||||
|
||||
if (bindingSource != null && !bindingSource.IsGreedy)
|
||||
{
|
||||
var bindingSourceValueProvider = valueProvider as IBindingSourceValueProvider;
|
||||
if (bindingSourceValueProvider != null)
|
||||
{
|
||||
valueProvider = bindingSourceValueProvider.Filter(bindingSource);
|
||||
if (valueProvider == null)
|
||||
{
|
||||
// Unable to find a value provider for this binding source.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bindingSource != null && bindingSource.IsGreedy)
|
||||
{
|
||||
bindingContext.ModelName = modelName;
|
||||
}
|
||||
else if (
|
||||
!fallbackToEmptyPrefix ||
|
||||
valueProvider.ContainsPrefix(bindingContext.ModelName))
|
||||
{
|
||||
bindingContext.ModelName = modelName;
|
||||
}
|
||||
else
|
||||
{
|
||||
bindingContext.ModelName = string.Empty;
|
||||
}
|
||||
|
||||
bindingContext.ValueProvider = valueProvider;
|
||||
bindingContext.FallbackToEmptyPrefix = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,175 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. 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.Diagnostics;
|
||||
#if NETSTANDARD1_5
|
||||
using System.Reflection;
|
||||
#endif
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
{
|
||||
public class GenericModelBinder : IModelBinder
|
||||
{
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
if (bindingContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(bindingContext));
|
||||
}
|
||||
|
||||
// This method is optimized to use cached tasks when possible and avoid allocating
|
||||
// using Task.FromResult. If you need to make changes of this nature, profile
|
||||
// allocations afterwards and look for Task<ModelBindingResult>.
|
||||
|
||||
var binderType = ResolveBinderType(bindingContext);
|
||||
if (binderType == null)
|
||||
{
|
||||
return TaskCache.CompletedTask;
|
||||
}
|
||||
|
||||
var binder = (IModelBinder)Activator.CreateInstance(binderType);
|
||||
|
||||
var collectionBinder = binder as ICollectionModelBinder;
|
||||
if (collectionBinder != null &&
|
||||
bindingContext.Model == null &&
|
||||
!collectionBinder.CanCreateInstance(bindingContext.ModelType))
|
||||
{
|
||||
// Able to resolve a binder type but need a new model instance and that binder cannot create it.
|
||||
return TaskCache.CompletedTask;
|
||||
}
|
||||
|
||||
return BindModelCoreAsync(bindingContext, binder);
|
||||
}
|
||||
|
||||
private async Task BindModelCoreAsync(ModelBindingContext bindingContext, IModelBinder binder)
|
||||
{
|
||||
Debug.Assert(binder != null);
|
||||
|
||||
await binder.BindModelAsync(bindingContext);
|
||||
if (bindingContext.Result == null)
|
||||
{
|
||||
// Always tell the model binding system to skip other model binders.
|
||||
bindingContext.Result = ModelBindingResult.Failed(bindingContext.ModelName);
|
||||
}
|
||||
}
|
||||
|
||||
private static Type ResolveBinderType(ModelBindingContext context)
|
||||
{
|
||||
var modelType = context.ModelType;
|
||||
|
||||
return GetArrayBinder(modelType) ??
|
||||
GetDictionaryBinder(modelType) ??
|
||||
GetCollectionBinder(modelType) ??
|
||||
GetEnumerableBinder(context) ??
|
||||
GetKeyValuePairBinder(modelType);
|
||||
}
|
||||
|
||||
private static Type GetArrayBinder(Type modelType)
|
||||
{
|
||||
if (modelType.IsArray)
|
||||
{
|
||||
var elementType = modelType.GetElementType();
|
||||
return typeof(ArrayModelBinder<>).MakeGenericType(elementType);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Type GetCollectionBinder(Type modelType)
|
||||
{
|
||||
return GetGenericBinderType(
|
||||
typeof(ICollection<>),
|
||||
typeof(CollectionModelBinder<>),
|
||||
modelType);
|
||||
}
|
||||
|
||||
private static Type GetDictionaryBinder(Type modelType)
|
||||
{
|
||||
return GetGenericBinderType(
|
||||
typeof(IDictionary<,>),
|
||||
typeof(DictionaryModelBinder<,>),
|
||||
modelType);
|
||||
}
|
||||
|
||||
private static Type GetEnumerableBinder(ModelBindingContext context)
|
||||
{
|
||||
var modelTypeArguments = GetGenericBinderTypeArgs(typeof(IEnumerable<>), context.ModelType);
|
||||
if (modelTypeArguments == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (context.Model == null)
|
||||
{
|
||||
// GetCollectionBinder has already confirmed modelType is not compatible with ICollection<T>. Can a
|
||||
// List<T> (the default CollectionModelBinder type) instance be used instead of that exact type?
|
||||
// Likely this will succeed only if the property type is exactly IEnumerable<T>.
|
||||
var closedListType = typeof(List<>).MakeGenericType(modelTypeArguments);
|
||||
if (!context.ModelType.IsAssignableFrom(closedListType))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// A non-null instance must be updated in-place. For that the instance must also implement
|
||||
// ICollection<T>. For example an IEnumerable<T> property may have a List<T> default value. Do not use
|
||||
// IsAssignableFrom() because that does not handle explicit interface implementations and binders all
|
||||
// perform explicit casts.
|
||||
if (!context.ModelMetadata.IsCollectionType)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return typeof(CollectionModelBinder<>).MakeGenericType(modelTypeArguments);
|
||||
}
|
||||
|
||||
private static Type GetKeyValuePairBinder(Type modelType)
|
||||
{
|
||||
Debug.Assert(modelType != null);
|
||||
|
||||
// Since KeyValuePair is a value type, ExtractGenericInterface() succeeds only on an exact match.
|
||||
var closedGenericType = ClosedGenericMatcher.ExtractGenericInterface(modelType, typeof(KeyValuePair<,>));
|
||||
if (closedGenericType != null)
|
||||
{
|
||||
return typeof(KeyValuePairModelBinder<,>).MakeGenericType(modelType.GenericTypeArguments);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Example: GetGenericBinderType(typeof(IList<T>), typeof(ListBinder<T>), ...) means that the ListBinder<T>
|
||||
// type can update models that implement IList<T>. This method will return
|
||||
// ListBinder<T> or null, depending on whether the type checks succeed.
|
||||
private static Type GetGenericBinderType(Type supportedInterfaceType, Type openBinderType, Type modelType)
|
||||
{
|
||||
Debug.Assert(openBinderType != null);
|
||||
|
||||
var modelTypeArguments = GetGenericBinderTypeArgs(supportedInterfaceType, modelType);
|
||||
if (modelTypeArguments == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return openBinderType.MakeGenericType(modelTypeArguments);
|
||||
}
|
||||
|
||||
// Get the generic arguments for the binder, based on the model type. Or null if not compatible.
|
||||
private static Type[] GetGenericBinderTypeArgs(Type supportedInterfaceType, Type modelType)
|
||||
{
|
||||
Debug.Assert(supportedInterfaceType != null);
|
||||
Debug.Assert(modelType != null);
|
||||
|
||||
var closedGenericInterface =
|
||||
ClosedGenericMatcher.ExtractGenericInterface(modelType, supportedInterfaceType);
|
||||
|
||||
return closedGenericInterface?.GenericTypeArguments;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
{
|
||||
/// <summary>
|
||||
/// A factory abstraction for creating <see cref="IModelBinder"/> instances.
|
||||
/// </summary>
|
||||
public interface IModelBinderFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="IModelBinder"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="ModelBinderFactoryContext"/>.</param>
|
||||
/// <returns>An <see cref="IModelBinder"/> instance.</returns>
|
||||
IModelBinder CreateBinder(ModelBinderFactoryContext context);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,233 @@
|
|||
// Copyright (c) .NET Foundation. 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.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Core;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Internal;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
{
|
||||
/// <summary>
|
||||
/// A factory for <see cref="IModelBinder"/> instances.
|
||||
/// </summary>
|
||||
public class ModelBinderFactory : IModelBinderFactory
|
||||
{
|
||||
private readonly IModelMetadataProvider _metadataProvider;
|
||||
private readonly IModelBinderProvider[] _providers;
|
||||
|
||||
private readonly ConcurrentDictionary<object, IModelBinder> _cache;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ModelBinderFactory"/>.
|
||||
/// </summary>
|
||||
/// <param name="metadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
|
||||
/// <param name="options">The <see cref="IOptions{TOptions}"/> for <see cref="MvcOptions"/>.</param>
|
||||
public ModelBinderFactory(IModelMetadataProvider metadataProvider, IOptions<MvcOptions> options)
|
||||
{
|
||||
_metadataProvider = metadataProvider;
|
||||
_providers = options.Value.ModelBinderProviders.ToArray();
|
||||
|
||||
_cache = new ConcurrentDictionary<object, IModelBinder>(ReferenceEqualityComparer.Instance);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IModelBinder CreateBinder(ModelBinderFactoryContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
// We perform caching in CreateBinder (not in CreateBinderCore) because we only want to
|
||||
// cache the top-level binder.
|
||||
IModelBinder binder;
|
||||
if (context.CacheToken != null && _cache.TryGetValue(context.CacheToken, out binder))
|
||||
{
|
||||
return binder;
|
||||
}
|
||||
|
||||
var providerContext = new DefaultModelBinderProviderContext(this, context);
|
||||
binder = CreateBinderCore(providerContext, context.CacheToken);
|
||||
if (binder == null)
|
||||
{
|
||||
var message = Resources.FormatCouldNotCreateIModelBinder(providerContext.Metadata.ModelType);
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
if (context.CacheToken != null)
|
||||
{
|
||||
_cache.TryAdd(context.CacheToken, binder);
|
||||
}
|
||||
|
||||
return binder;
|
||||
}
|
||||
|
||||
private IModelBinder CreateBinderCore(DefaultModelBinderProviderContext providerContext, object token)
|
||||
{
|
||||
// A non-null token will usually be passed in at the the top level (ParameterDescriptor likely).
|
||||
// This prevents us from treating a parameter the same as a collection-element - which could
|
||||
// happen looking at just model metadata.
|
||||
var key = new Key(providerContext.Metadata, token);
|
||||
|
||||
// If we're currently recursively building a binder for this type, just return
|
||||
// a PlaceholderBinder. We'll fix it up later to point to the 'real' binder
|
||||
// when the stack unwinds.
|
||||
var stack = providerContext.Stack;
|
||||
for (var i = 0; i < stack.Count; i++)
|
||||
{
|
||||
var entry = stack[i];
|
||||
if (key.Equals(entry.Key))
|
||||
{
|
||||
if (entry.Value == null)
|
||||
{
|
||||
// Recursion detected, create a DelegatingBinder.
|
||||
var binder = new PlaceholderBinder();
|
||||
stack[i] = new KeyValuePair<Key, PlaceholderBinder>(entry.Key, binder);
|
||||
return binder;
|
||||
}
|
||||
else
|
||||
{
|
||||
return entry.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OK this isn't a recursive case (yet) so "push" an entry on the stack and then ask the providers
|
||||
// to create the binder.
|
||||
stack.Add(new KeyValuePair<Key, PlaceholderBinder>(key, null));
|
||||
|
||||
IModelBinder result = null;
|
||||
|
||||
for (var i = 0; i < _providers.Length; i++)
|
||||
{
|
||||
var provider = _providers[i];
|
||||
result = provider.GetBinder(providerContext);
|
||||
if (result != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (result == null && stack.Count > 1)
|
||||
{
|
||||
// Use a no-op binder if we're below the top level. At the top level, we throw.
|
||||
result = NoOpBinder.Instance;
|
||||
}
|
||||
|
||||
// "pop"
|
||||
Debug.Assert(stack.Count > 0);
|
||||
var delegatingBinder = stack[stack.Count - 1].Value;
|
||||
stack.RemoveAt(stack.Count - 1);
|
||||
|
||||
// If the DelegatingBinder was created, then it means we recursed. Hook it up to the 'real' binder.
|
||||
if (delegatingBinder != null)
|
||||
{
|
||||
delegatingBinder.Inner = result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private class DefaultModelBinderProviderContext : ModelBinderProviderContext
|
||||
{
|
||||
private readonly ModelBinderFactory _factory;
|
||||
|
||||
public DefaultModelBinderProviderContext(
|
||||
ModelBinderFactory factory,
|
||||
ModelBinderFactoryContext factoryContext)
|
||||
{
|
||||
_factory = factory;
|
||||
Metadata = factoryContext.Metadata;
|
||||
BindingInfo = factoryContext.BindingInfo;
|
||||
|
||||
MetadataProvider = _factory._metadataProvider;
|
||||
Stack = new List<KeyValuePair<Key, PlaceholderBinder>>();
|
||||
}
|
||||
|
||||
private DefaultModelBinderProviderContext(
|
||||
DefaultModelBinderProviderContext parent,
|
||||
ModelMetadata metadata)
|
||||
{
|
||||
Metadata = metadata;
|
||||
|
||||
_factory = parent._factory;
|
||||
MetadataProvider = parent.MetadataProvider;
|
||||
Stack = parent.Stack;
|
||||
|
||||
BindingInfo = new BindingInfo()
|
||||
{
|
||||
BinderModelName = metadata.BinderModelName,
|
||||
BinderType = metadata.BinderType,
|
||||
BindingSource = metadata.BindingSource,
|
||||
PropertyBindingPredicateProvider = metadata.PropertyBindingPredicateProvider,
|
||||
};
|
||||
}
|
||||
|
||||
public override BindingInfo BindingInfo { get; }
|
||||
|
||||
public override ModelMetadata Metadata { get; }
|
||||
|
||||
public override IModelMetadataProvider MetadataProvider { get; }
|
||||
|
||||
// Not using a 'real' Stack<> because we want random access to modify the entries.
|
||||
public List<KeyValuePair<Key, PlaceholderBinder>> Stack { get; }
|
||||
|
||||
public override IModelBinder CreateBinder(ModelMetadata metadata)
|
||||
{
|
||||
var nestedContext = new DefaultModelBinderProviderContext(this, metadata);
|
||||
return _factory.CreateBinderCore(nestedContext, token: null);
|
||||
}
|
||||
}
|
||||
|
||||
private class NoOpBinder : IModelBinder
|
||||
{
|
||||
public static readonly IModelBinder Instance = new NoOpBinder();
|
||||
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
bindingContext.Result = ModelBindingResult.Failed(bindingContext.ModelName);
|
||||
return TaskCache.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private struct Key : IEquatable<Key>
|
||||
{
|
||||
private readonly ModelMetadata _metadata;
|
||||
private readonly object _token; // Explicitly using ReferenceEquality for tokens.
|
||||
|
||||
public Key(ModelMetadata metadata, object token)
|
||||
{
|
||||
_metadata = metadata;
|
||||
_token = token;
|
||||
}
|
||||
|
||||
public bool Equals(Key other)
|
||||
{
|
||||
return _metadata.Equals(other._metadata) && object.ReferenceEquals(_token, other._token);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var other = obj as Key?;
|
||||
return other.HasValue && Equals(other.Value);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hash = new HashCodeCombiner();
|
||||
hash.Add(_metadata);
|
||||
hash.Add(RuntimeHelpers.GetHashCode(_token));
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
{
|
||||
/// <summary>
|
||||
/// A context object for <see cref="ModelBinderFactory.CreateBinder"/>.
|
||||
/// </summary>
|
||||
public class ModelBinderFactoryContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="ModelBinding.BindingInfo"/>.
|
||||
/// </summary>
|
||||
public BindingInfo BindingInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="ModelMetadata"/>.
|
||||
/// </summary>
|
||||
public ModelMetadata Metadata { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the cache token. If <c>non-null</c> the resulting <see cref="IModelBinder"/>
|
||||
/// will be cached.
|
||||
/// </summary>
|
||||
public object CacheToken { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -20,9 +20,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public static class ModelBindingHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Updates the specified <paramref name="model"/> instance using the specified <paramref name="modelBinder"/>
|
||||
/// and the specified <paramref name="valueProvider"/> and executes validation using the specified
|
||||
/// <paramref name="validatorProvider"/>.
|
||||
/// Updates the specified <paramref name="model"/> instance using the specified
|
||||
/// <paramref name="modelBinderFactory"/> and the specified <paramref name="valueProvider"/> and executes
|
||||
/// validation using the specified <paramref name="validatorProvider"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The type of the model object.</typeparam>
|
||||
/// <param name="model">The model instance to update and validate.</param>
|
||||
|
|
@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
/// </param>
|
||||
/// <param name="actionContext">The <see cref="ActionContext"/> for the current executing request.</param>
|
||||
/// <param name="metadataProvider">The provider used for reading metadata for the model type.</param>
|
||||
/// <param name="modelBinder">The <see cref="IModelBinder"/> used for binding.</param>
|
||||
/// <param name="modelBinderFactory">The <see cref="IModelBinderFactory"/> used for binding.</param>
|
||||
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
|
||||
/// <param name="inputFormatters">
|
||||
/// The set of <see cref="IInputFormatter"/> instances for deserializing the body.
|
||||
|
|
@ -45,7 +45,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
string prefix,
|
||||
ActionContext actionContext,
|
||||
IModelMetadataProvider metadataProvider,
|
||||
IModelBinder modelBinder,
|
||||
IModelBinderFactory modelBinderFactory,
|
||||
IValueProvider valueProvider,
|
||||
IList<IInputFormatter> inputFormatters,
|
||||
IObjectModelValidator objectModelValidator,
|
||||
|
|
@ -72,9 +72,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
throw new ArgumentNullException(nameof(metadataProvider));
|
||||
}
|
||||
|
||||
if (modelBinder == null)
|
||||
if (modelBinderFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modelBinder));
|
||||
throw new ArgumentNullException(nameof(modelBinderFactory));
|
||||
}
|
||||
|
||||
if (valueProvider == null)
|
||||
|
|
@ -103,7 +103,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
prefix,
|
||||
actionContext,
|
||||
metadataProvider,
|
||||
modelBinder,
|
||||
modelBinderFactory,
|
||||
valueProvider,
|
||||
inputFormatters,
|
||||
objectModelValidator,
|
||||
|
|
@ -112,7 +112,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the specified <paramref name="model"/> instance using the specified <paramref name="modelBinder"/>
|
||||
/// Updates the specified <paramref name="model"/> instance using the specified <paramref name="modelBinderFactory"/>
|
||||
/// and the specified <paramref name="valueProvider"/> and executes validation using the specified
|
||||
/// <paramref name="validatorProvider"/>.
|
||||
/// </summary>
|
||||
|
|
@ -122,7 +122,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
/// </param>
|
||||
/// <param name="actionContext">The <see cref="ActionContext"/> for the current executing request.</param>
|
||||
/// <param name="metadataProvider">The provider used for reading metadata for the model type.</param>
|
||||
/// <param name="modelBinder">The <see cref="IModelBinder"/> used for binding.</param>
|
||||
/// <param name="modelBinderFactory">The <see cref="IModelBinderFactory"/> used for binding.</param>
|
||||
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
|
||||
/// <param name="inputFormatters">
|
||||
/// The set of <see cref="IInputFormatter"/> instances for deserializing the body.
|
||||
|
|
@ -140,7 +140,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
string prefix,
|
||||
ActionContext actionContext,
|
||||
IModelMetadataProvider metadataProvider,
|
||||
IModelBinder modelBinder,
|
||||
IModelBinderFactory modelBinderFactory,
|
||||
IValueProvider valueProvider,
|
||||
IList<IInputFormatter> inputFormatters,
|
||||
IObjectModelValidator objectModelValidator,
|
||||
|
|
@ -168,9 +168,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
throw new ArgumentNullException(nameof(metadataProvider));
|
||||
}
|
||||
|
||||
if (modelBinder == null)
|
||||
if (modelBinderFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modelBinder));
|
||||
throw new ArgumentNullException(nameof(modelBinderFactory));
|
||||
}
|
||||
|
||||
if (valueProvider == null)
|
||||
|
|
@ -206,7 +206,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
prefix,
|
||||
actionContext,
|
||||
metadataProvider,
|
||||
modelBinder,
|
||||
modelBinderFactory,
|
||||
valueProvider,
|
||||
inputFormatters,
|
||||
objectModelValidator,
|
||||
|
|
@ -215,7 +215,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the specified <paramref name="model"/> instance using the specified <paramref name="modelBinder"/>
|
||||
/// Updates the specified <paramref name="model"/> instance using the specified <paramref name="modelBinderFactory"/>
|
||||
/// and the specified <paramref name="valueProvider"/> and executes validation using the specified
|
||||
/// <paramref name="validatorProvider"/>.
|
||||
/// </summary>
|
||||
|
|
@ -225,7 +225,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
/// </param>
|
||||
/// <param name="actionContext">The <see cref="ActionContext"/> for the current executing request.</param>
|
||||
/// <param name="metadataProvider">The provider used for reading metadata for the model type.</param>
|
||||
/// <param name="modelBinder">The <see cref="IModelBinder"/> used for binding.</param>
|
||||
/// <param name="modelBinderFactory">The <see cref="IModelBinderFactory"/> used for binding.</param>
|
||||
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
|
||||
/// <param name="inputFormatters">
|
||||
/// The set of <see cref="IInputFormatter"/> instances for deserializing the body.
|
||||
|
|
@ -242,7 +242,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
string prefix,
|
||||
ActionContext actionContext,
|
||||
IModelMetadataProvider metadataProvider,
|
||||
IModelBinder modelBinder,
|
||||
IModelBinderFactory modelBinderFactory,
|
||||
IValueProvider valueProvider,
|
||||
IList<IInputFormatter> inputFormatters,
|
||||
IObjectModelValidator objectModelValidator,
|
||||
|
|
@ -270,9 +270,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
throw new ArgumentNullException(nameof(metadataProvider));
|
||||
}
|
||||
|
||||
if (modelBinder == null)
|
||||
if (modelBinderFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modelBinder));
|
||||
throw new ArgumentNullException(nameof(modelBinderFactory));
|
||||
}
|
||||
|
||||
if (valueProvider == null)
|
||||
|
|
@ -306,7 +306,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
prefix,
|
||||
actionContext,
|
||||
metadataProvider,
|
||||
modelBinder,
|
||||
modelBinderFactory,
|
||||
valueProvider,
|
||||
inputFormatters,
|
||||
objectModelValidator,
|
||||
|
|
@ -315,7 +315,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the specified <paramref name="model"/> instance using the specified <paramref name="modelBinder"/>
|
||||
/// Updates the specified <paramref name="model"/> instance using the specified <paramref name="modelBinderFactory"/>
|
||||
/// and the specified <paramref name="valueProvider"/> and executes validation using the specified
|
||||
/// <paramref name="validatorProvider"/>.
|
||||
/// </summary>
|
||||
|
|
@ -325,7 +325,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
/// </param>
|
||||
/// <param name="actionContext">The <see cref="ActionContext"/> for the current executing request.</param>
|
||||
/// <param name="metadataProvider">The provider used for reading metadata for the model type.</param>
|
||||
/// <param name="modelBinder">The <see cref="IModelBinder"/> used for binding.</param>
|
||||
/// <param name="modelBinderFactory">The <see cref="IModelBinderFactory"/> used for binding.</param>
|
||||
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
|
||||
/// <param name="inputFormatters">
|
||||
/// The set of <see cref="IInputFormatter"/> instances for deserializing the body.
|
||||
|
|
@ -341,7 +341,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
string prefix,
|
||||
ActionContext actionContext,
|
||||
IModelMetadataProvider metadataProvider,
|
||||
IModelBinder modelBinder,
|
||||
IModelBinderFactory modelBinderFactory,
|
||||
IValueProvider valueProvider,
|
||||
IList<IInputFormatter> inputFormatters,
|
||||
IObjectModelValidator objectModelValidator,
|
||||
|
|
@ -372,9 +372,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
throw new ArgumentNullException(nameof(metadataProvider));
|
||||
}
|
||||
|
||||
if (modelBinder == null)
|
||||
if (modelBinderFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modelBinder));
|
||||
throw new ArgumentNullException(nameof(modelBinderFactory));
|
||||
}
|
||||
|
||||
if (valueProvider == null)
|
||||
|
|
@ -404,7 +404,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
prefix,
|
||||
actionContext,
|
||||
metadataProvider,
|
||||
modelBinder,
|
||||
modelBinderFactory,
|
||||
valueProvider,
|
||||
inputFormatters,
|
||||
objectModelValidator,
|
||||
|
|
@ -413,7 +413,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the specified <paramref name="model"/> instance using the specified <paramref name="modelBinder"/>
|
||||
/// Updates the specified <paramref name="model"/> instance using the specified <paramref name="modelBinderFactory"/>
|
||||
/// and the specified <paramref name="valueProvider"/> and executes validation using the specified
|
||||
/// <paramref name="validatorProvider"/>.
|
||||
/// </summary>
|
||||
|
|
@ -423,7 +423,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
/// </param>
|
||||
/// <param name="actionContext">The <see cref="ActionContext"/> for the current executing request.</param>
|
||||
/// <param name="metadataProvider">The provider used for reading metadata for the model type.</param>
|
||||
/// <param name="modelBinder">The <see cref="IModelBinder"/> used for binding.</param>
|
||||
/// <param name="modelBinderFactory">The <see cref="IModelBinderFactory"/> used for binding.</param>
|
||||
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
|
||||
/// <param name="inputFormatters">
|
||||
/// The set of <see cref="IInputFormatter"/> instances for deserializing the body.
|
||||
|
|
@ -441,7 +441,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
string prefix,
|
||||
ActionContext actionContext,
|
||||
IModelMetadataProvider metadataProvider,
|
||||
IModelBinder modelBinder,
|
||||
IModelBinderFactory modelBinderFactory,
|
||||
IValueProvider valueProvider,
|
||||
IList<IInputFormatter> inputFormatters,
|
||||
IObjectModelValidator objectModelValidator,
|
||||
|
|
@ -473,9 +473,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
throw new ArgumentNullException(nameof(metadataProvider));
|
||||
}
|
||||
|
||||
if (modelBinder == null)
|
||||
if (modelBinderFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modelBinder));
|
||||
throw new ArgumentNullException(nameof(modelBinderFactory));
|
||||
}
|
||||
|
||||
if (valueProvider == null)
|
||||
|
|
@ -517,7 +517,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var operationBindingContext = new OperationBindingContext
|
||||
{
|
||||
InputFormatters = inputFormatters,
|
||||
ModelBinder = modelBinder,
|
||||
ValidatorProvider = validatorProvider,
|
||||
MetadataProvider = metadataProvider,
|
||||
ActionContext = actionContext,
|
||||
|
|
@ -532,7 +531,25 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
modelBindingContext.Model = model;
|
||||
modelBindingContext.PropertyFilter = predicate;
|
||||
|
||||
await modelBinder.BindModelAsync(modelBindingContext);
|
||||
var factoryContext = new ModelBinderFactoryContext()
|
||||
{
|
||||
Metadata = modelMetadata,
|
||||
BindingInfo = new BindingInfo()
|
||||
{
|
||||
BinderModelName = modelMetadata.BinderModelName,
|
||||
BinderType = modelMetadata.BinderType,
|
||||
BindingSource = modelMetadata.BindingSource,
|
||||
PropertyBindingPredicateProvider = modelMetadata.PropertyBindingPredicateProvider,
|
||||
},
|
||||
|
||||
// We're using the model metadata as the cache token here so that TryUpdateModelAsync calls
|
||||
// for the same model type can share a binder. This won't overlap with normal model binding
|
||||
// operations because they use the ParameterDescriptor for the token.
|
||||
CacheToken = modelMetadata,
|
||||
};
|
||||
var binder = modelBinderFactory.CreateBinder(factoryContext);
|
||||
|
||||
await binder.BindModelAsync(modelBindingContext);
|
||||
var modelBindingResult = modelBindingContext.Result;
|
||||
if (modelBindingResult != null && modelBindingResult.Value.IsModelSet)
|
||||
{
|
||||
|
|
@ -730,19 +747,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
}
|
||||
}
|
||||
|
||||
internal static void ValidateBindingContext(ModelBindingContext bindingContext)
|
||||
{
|
||||
if (bindingContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(bindingContext));
|
||||
}
|
||||
|
||||
if (bindingContext.ModelMetadata == null)
|
||||
{
|
||||
throw new ArgumentException(Resources.ModelBinderUtil_ModelMetadataCannotBeNull, nameof(bindingContext));
|
||||
}
|
||||
}
|
||||
|
||||
internal static TModel CastOrDefault<TModel>(object model)
|
||||
{
|
||||
return (model is TModel) ? (TModel)model : default(TModel);
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
FormatterMappings = new FormatterMappings();
|
||||
InputFormatters = new FormatterCollection<IInputFormatter>();
|
||||
OutputFormatters = new FormatterCollection<IOutputFormatter>();
|
||||
ModelBinders = new List<IModelBinder>();
|
||||
ModelBinderProviders = new List<IModelBinderProvider>();
|
||||
ModelBindingMessageProvider = new ModelBindingMessageProvider();
|
||||
ModelMetadataDetailsProviders = new List<IMetadataDetailsProvider>();
|
||||
ModelValidatorProviders = new List<IModelValidatorProvider>();
|
||||
|
|
@ -81,9 +81,9 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="IModelBinder"/>s used by this application.
|
||||
/// Gets a list of <see cref="IModelBinderProvider"/>s used by this application.
|
||||
/// </summary>
|
||||
public IList<IModelBinder> ModelBinders { get; }
|
||||
public IList<IModelBinderProvider> ModelBinderProviders { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default <see cref="IModelBindingMessageProvider"/>. Changes here are copied to the
|
||||
|
|
|
|||
|
|
@ -1162,6 +1162,22 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("MustSpecifyAtLeastOneAuthenticationScheme"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Could not create a model binder for model object of type '{0}'.
|
||||
/// </summary>
|
||||
internal static string CouldNotCreateIModelBinder
|
||||
{
|
||||
get { return GetString("CouldNotCreateIModelBinder"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Could not create a model binder for model object of type '{0}'.
|
||||
/// </summary>
|
||||
internal static string FormatCouldNotCreateIModelBinder(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("CouldNotCreateIModelBinder"), p0);
|
||||
}
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
|
|
@ -26,36 +26,36 @@
|
|||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
|
@ -343,4 +343,7 @@
|
|||
<data name="MustSpecifyAtLeastOneAuthenticationScheme" xml:space="preserve">
|
||||
<value>At least one authentication scheme must be specified.</value>
|
||||
</data>
|
||||
<data name="CouldNotCreateIModelBinder" xml:space="preserve">
|
||||
<value>Could not create a model binder for model object of type '{0}'.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -17,12 +17,9 @@ namespace Microsoft.AspNetCore.Mvc.WebApiCompatShim
|
|||
/// <inheritdoc />
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
if (bindingContext.ModelType == typeof(HttpRequestMessage))
|
||||
{
|
||||
var model = bindingContext.OperationBindingContext.HttpContext.GetHttpRequestMessage();
|
||||
bindingContext.ValidationState.Add(model, new ValidationStateEntry() { SuppressValidation = true });
|
||||
bindingContext.Result = ModelBindingResult.Success(bindingContext.ModelName, model);
|
||||
}
|
||||
var model = bindingContext.OperationBindingContext.HttpContext.GetHttpRequestMessage();
|
||||
bindingContext.ValidationState.Add(model, new ValidationStateEntry() { SuppressValidation = true });
|
||||
bindingContext.Result = ModelBindingResult.Success(bindingContext.ModelName, model);
|
||||
|
||||
return TaskCache.CompletedTask;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Net.Http;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.WebApiCompatShim
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="IModelBinderProvider"/> implementation to bind models of type <see cref="HttpRequestMessage"/>.
|
||||
/// </summary>
|
||||
public class HttpRequestMessageModelBinderProvider : IModelBinderProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IModelBinder GetBinder(ModelBinderProviderContext context)
|
||||
{
|
||||
if (context.Metadata.ModelType == typeof(HttpRequestMessage))
|
||||
{
|
||||
return new HttpRequestMessageModelBinder();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.WebApiCompatShim
|
|||
options.Filters.Add(new HttpResponseExceptionActionFilter());
|
||||
|
||||
// Add a model binder to be able to bind HttpRequestMessage
|
||||
options.ModelBinders.Insert(0, new HttpRequestMessageModelBinder());
|
||||
options.ModelBinderProviders.Insert(0, new HttpRequestMessageModelBinderProvider());
|
||||
|
||||
// Add a formatter to write out an HttpResponseMessage to the response
|
||||
options.OutputFormatters.Insert(0, new HttpResponseMessageOutputFormatter());
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Mvc.Filters;
|
|||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Constraints;
|
||||
|
|
|
|||
|
|
@ -1638,7 +1638,6 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
|
|||
var controllerContext = new ControllerContext()
|
||||
{
|
||||
HttpContext = httpContext,
|
||||
ModelBinders = new[] { binder, },
|
||||
ValueProviders = new[] { valueProvider, },
|
||||
ValidatorProviders = new[]
|
||||
{
|
||||
|
|
@ -1649,10 +1648,16 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
|
|||
},
|
||||
};
|
||||
|
||||
var binderFactory = new Mock<IModelBinderFactory>();
|
||||
binderFactory
|
||||
.Setup(f => f.CreateBinder(It.IsAny<ModelBinderFactoryContext>()))
|
||||
.Returns(binder);
|
||||
|
||||
var controller = new TestableController()
|
||||
{
|
||||
ControllerContext = controllerContext,
|
||||
MetadataProvider = metadataProvider,
|
||||
ModelBinderFactory = binderFactory.Object,
|
||||
ObjectValidator = new DefaultObjectValidator(metadataProvider, new ValidatorCache()),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2057,7 +2057,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
actionDescriptor,
|
||||
new IInputFormatter[0],
|
||||
actionArgumentsBinder.Object,
|
||||
new IModelBinder[0],
|
||||
new IModelValidatorProvider[0],
|
||||
new IValueProviderFactory[0],
|
||||
new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(),
|
||||
|
|
@ -2090,9 +2089,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
FilterDescriptors = new List<FilterDescriptor>()
|
||||
};
|
||||
|
||||
var binder = new Mock<IModelBinder>();
|
||||
binder.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
|
||||
.Returns(TaskCache.CompletedTask);
|
||||
var context = new Mock<HttpContext>();
|
||||
context.SetupGet(c => c.Items)
|
||||
.Returns(new Dictionary<object, object>());
|
||||
|
|
@ -2115,8 +2111,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
new IInputFormatter[0],
|
||||
new ControllerArgumentBinder(
|
||||
metadataProvider,
|
||||
TestModelBinderFactory.CreateDefault(metadataProvider),
|
||||
new DefaultObjectValidator(metadataProvider, new ValidatorCache())),
|
||||
new IModelBinder[] { binder.Object },
|
||||
new IModelValidatorProvider[0],
|
||||
new IValueProviderFactory[0],
|
||||
new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(),
|
||||
|
|
@ -2241,7 +2237,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ControllerActionDescriptor descriptor,
|
||||
IReadOnlyList<IInputFormatter> inputFormatters,
|
||||
IControllerActionArgumentBinder controllerActionArgumentBinder,
|
||||
IReadOnlyList<IModelBinder> modelBinders,
|
||||
IReadOnlyList<IModelValidatorProvider> modelValidatorProviders,
|
||||
IReadOnlyList<IValueProviderFactory> valueProviderFactories,
|
||||
ILogger logger,
|
||||
|
|
@ -2254,7 +2249,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
descriptor,
|
||||
inputFormatters,
|
||||
controllerActionArgumentBinder,
|
||||
modelBinders,
|
||||
modelValidatorProviders,
|
||||
valueProviderFactories,
|
||||
logger,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ using Microsoft.AspNetCore.Http.Internal;
|
|||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Test;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Moq;
|
||||
|
|
@ -36,17 +35,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
binder
|
||||
.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
|
||||
.Returns(TaskCache.CompletedTask);
|
||||
var factory = GetModelBinderFactory(binder.Object);
|
||||
var argumentBinder = GetArgumentBinder(factory);
|
||||
|
||||
var controllerContext = GetControllerContext(actionDescriptor);
|
||||
controllerContext.ModelBinders.Add(binder.Object);
|
||||
controllerContext.ValueProviders.Add(new SimpleValueProvider());
|
||||
|
||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||
var argumentBinder = GetArgumentBinder();
|
||||
|
||||
// Act
|
||||
var result = await argumentBinder
|
||||
.BindActionArgumentsAsync(controllerContext, new TestController());
|
||||
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, new TestController());
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result);
|
||||
|
|
@ -69,17 +64,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
binder
|
||||
.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
|
||||
.Returns(TaskCache.CompletedTask);
|
||||
var factory = GetModelBinderFactory(binder.Object);
|
||||
var argumentBinder = GetArgumentBinder(factory);
|
||||
|
||||
var controllerContext = GetControllerContext(actionDescriptor);
|
||||
controllerContext.ModelBinders.Add(binder.Object);
|
||||
controllerContext.ValueProviders.Add(new SimpleValueProvider());
|
||||
|
||||
var argumentBinder = GetArgumentBinder();
|
||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||
|
||||
// Act
|
||||
var result = await argumentBinder
|
||||
.BindActionArgumentsAsync(controllerContext, new TestController());
|
||||
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, new TestController());
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result);
|
||||
|
|
@ -111,16 +102,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
context.Result = ModelBindingResult.Success(string.Empty, value);
|
||||
})
|
||||
.Returns(TaskCache.CompletedTask);
|
||||
var factory = GetModelBinderFactory(binder.Object);
|
||||
var argumentBinder = GetArgumentBinder(factory);
|
||||
|
||||
var controllerContext = GetControllerContext(actionDescriptor);
|
||||
controllerContext.ModelBinders.Add(binder.Object);
|
||||
controllerContext.ValueProviders.Add(new SimpleValueProvider());
|
||||
|
||||
var argumentBinder = GetArgumentBinder();
|
||||
|
||||
// Act
|
||||
var result = await argumentBinder
|
||||
.BindActionArgumentsAsync(controllerContext, new TestController());
|
||||
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, new TestController());
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, result.Count);
|
||||
|
|
@ -139,7 +127,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ParameterType = typeof(object),
|
||||
});
|
||||
|
||||
var controllerContext = GetControllerContext(actionDescriptor, "Hello");
|
||||
var controllerContext = GetControllerContext(actionDescriptor);
|
||||
|
||||
var factory = GetModelBinderFactory("Hello");
|
||||
|
||||
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
||||
mockValidator
|
||||
|
|
@ -150,7 +140,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
It.IsAny<string>(),
|
||||
It.IsAny<object>()));
|
||||
|
||||
var argumentBinder = GetArgumentBinder(mockValidator.Object);
|
||||
var argumentBinder = GetArgumentBinder(factory, mockValidator.Object);
|
||||
|
||||
// Act
|
||||
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, new TestController());
|
||||
|
|
@ -180,15 +170,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
BindingInfo = new BindingInfo(),
|
||||
});
|
||||
|
||||
var controllerContext = GetControllerContext(actionDescriptor);
|
||||
|
||||
var binder = new Mock<IModelBinder>();
|
||||
binder
|
||||
.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
|
||||
.Returns(TaskCache.CompletedTask);
|
||||
|
||||
var controllerContext = GetControllerContext(actionDescriptor);
|
||||
controllerContext.ModelBinders.Add(binder.Object);
|
||||
controllerContext.ValueProviders.Add(new SimpleValueProvider());
|
||||
|
||||
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
||||
mockValidator
|
||||
.Setup(o => o.Validate(
|
||||
|
|
@ -198,7 +186,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
It.IsAny<string>(),
|
||||
It.IsAny<object>()));
|
||||
|
||||
var argumentBinder = GetArgumentBinder(mockValidator.Object);
|
||||
var factory = GetModelBinderFactory(binder.Object);
|
||||
var argumentBinder = GetArgumentBinder(factory, mockValidator.Object);
|
||||
|
||||
// Act
|
||||
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, new TestController());
|
||||
|
|
@ -226,7 +215,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ParameterType = typeof(string),
|
||||
});
|
||||
|
||||
var controllerContext = GetControllerContext(actionDescriptor, "Hello");
|
||||
var controllerContext = GetControllerContext(actionDescriptor);
|
||||
|
||||
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
||||
mockValidator
|
||||
|
|
@ -237,7 +226,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
It.IsAny<string>(),
|
||||
It.IsAny<object>()));
|
||||
|
||||
var argumentBinder = GetArgumentBinder(mockValidator.Object);
|
||||
var factory = GetModelBinderFactory("Hello");
|
||||
var argumentBinder = GetArgumentBinder(factory, mockValidator.Object);
|
||||
|
||||
// Act
|
||||
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, new TestController());
|
||||
|
|
@ -266,15 +256,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ParameterType = typeof(string),
|
||||
});
|
||||
|
||||
var controllerContext = GetControllerContext(actionDescriptor);
|
||||
|
||||
var binder = new Mock<IModelBinder>();
|
||||
binder
|
||||
.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
|
||||
.Returns(TaskCache.CompletedTask);
|
||||
|
||||
var controllerContext = GetControllerContext(actionDescriptor);
|
||||
controllerContext.ModelBinders.Add(binder.Object);
|
||||
controllerContext.ValueProviders.Add(new SimpleValueProvider());
|
||||
|
||||
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
||||
mockValidator
|
||||
.Setup(o => o.Validate(
|
||||
|
|
@ -284,7 +272,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
It.IsAny<string>(),
|
||||
It.IsAny<object>()));
|
||||
|
||||
var argumentBinder = GetArgumentBinder(mockValidator.Object);
|
||||
var factory = GetModelBinderFactory(binder.Object);
|
||||
var argumentBinder = GetArgumentBinder(factory, mockValidator.Object);
|
||||
|
||||
// Act
|
||||
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, new TestController());
|
||||
|
|
@ -313,8 +302,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ParameterType = typeof(string)
|
||||
});
|
||||
|
||||
var controllerContext = GetControllerContext(actionDescriptor, "Hello");
|
||||
var argumentBinder = GetArgumentBinder();
|
||||
var controllerContext = GetControllerContext(actionDescriptor);
|
||||
|
||||
var factory = GetModelBinderFactory("Hello");
|
||||
var argumentBinder = GetArgumentBinder(factory);
|
||||
|
||||
var controller = new TestController();
|
||||
|
||||
// Act
|
||||
|
|
@ -339,10 +331,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ParameterType = typeof(ICollection<string>),
|
||||
});
|
||||
|
||||
var expected = new List<string> { "Hello", "World", "!!" };
|
||||
var controllerContext = GetControllerContext(actionDescriptor, expected);
|
||||
var controllerContext = GetControllerContext(actionDescriptor);
|
||||
|
||||
var expected = new List<string> { "Hello", "World", "!!" };
|
||||
var factory = GetModelBinderFactory(expected);
|
||||
var argumentBinder = GetArgumentBinder(factory);
|
||||
|
||||
var argumentBinder = GetArgumentBinder();
|
||||
var controller = new TestController();
|
||||
|
||||
// Act
|
||||
|
|
@ -369,13 +363,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ParameterType = typeof(int)
|
||||
});
|
||||
|
||||
var binder = new StubModelBinder(ModelBindingResult.Success(string.Empty, model: null));
|
||||
|
||||
var controllerContext = GetControllerContext(actionDescriptor);
|
||||
controllerContext.ModelBinders.Add(binder);
|
||||
controllerContext.ValueProviders.Add(new SimpleValueProvider());
|
||||
|
||||
var argumentBinder = GetArgumentBinder();
|
||||
var binder = new StubModelBinder(ModelBindingResult.Success(string.Empty, model: null));
|
||||
var factory = GetModelBinderFactory(binder);
|
||||
var argumentBinder = GetArgumentBinder(factory);
|
||||
|
||||
var controller = new TestController();
|
||||
|
||||
// Some non default value.
|
||||
|
|
@ -401,13 +394,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ParameterType = typeof(int?)
|
||||
});
|
||||
|
||||
var binder = new StubModelBinder(ModelBindingResult.Success(key: string.Empty, model: null));
|
||||
|
||||
var controllerContext = GetControllerContext(actionDescriptor);
|
||||
controllerContext.ModelBinders.Add(binder);
|
||||
controllerContext.ValueProviders.Add(new SimpleValueProvider());
|
||||
|
||||
var argumentBinder = GetArgumentBinder();
|
||||
var binder = new StubModelBinder(ModelBindingResult.Success(key: string.Empty, model: null));
|
||||
var factory = GetModelBinderFactory(binder);
|
||||
var argumentBinder = GetArgumentBinder(factory);
|
||||
|
||||
var controller = new TestController();
|
||||
|
||||
// Some non default value.
|
||||
|
|
@ -478,8 +470,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ParameterType = propertyType,
|
||||
});
|
||||
|
||||
var controllerContext = GetControllerContext(actionDescriptor, inputValue);
|
||||
var argumentBinder = GetArgumentBinder();
|
||||
var controllerContext = GetControllerContext(actionDescriptor);
|
||||
|
||||
var factory = GetModelBinderFactory(inputValue);
|
||||
var argumentBinder = GetArgumentBinder(factory);
|
||||
|
||||
var controller = new TestController();
|
||||
|
||||
// Act
|
||||
|
|
@ -533,8 +528,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
}
|
||||
|
||||
var controllerContext = GetControllerContext(actionDescriptor);
|
||||
var argumentBinder = GetArgumentBinder();
|
||||
var controller = new TestController();
|
||||
|
||||
var binder = new StubModelBinder(bindingContext =>
|
||||
{
|
||||
|
|
@ -550,9 +543,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
bindingContext.Result = ModelBindingResult.Failed(bindingContext.ModelName);
|
||||
}
|
||||
});
|
||||
controllerContext.ModelBinders.Add(binder.Object);
|
||||
|
||||
var factory = GetModelBinderFactory(binder);
|
||||
controllerContext.ValueProviders.Add(new SimpleValueProvider());
|
||||
|
||||
var argumentBinder = GetArgumentBinder(factory);
|
||||
var controller = new TestController();
|
||||
|
||||
// Act
|
||||
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, controller);
|
||||
|
||||
|
|
@ -574,6 +571,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
RouteData = new RouteData(),
|
||||
};
|
||||
|
||||
context.ValueProviders.Add(new SimpleValueProvider());
|
||||
return context;
|
||||
}
|
||||
|
||||
|
|
@ -589,37 +587,47 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
};
|
||||
}
|
||||
|
||||
private static ControllerContext GetControllerContext(ControllerActionDescriptor descriptor = null, object model = null)
|
||||
private static ModelBinderFactory GetModelBinderFactory(object model = null)
|
||||
{
|
||||
var context = new ControllerContext()
|
||||
{
|
||||
ActionDescriptor = descriptor ?? GetActionDescriptor(),
|
||||
HttpContext = new DefaultHttpContext(),
|
||||
RouteData = new RouteData(),
|
||||
};
|
||||
|
||||
var binder = new Mock<IModelBinder>();
|
||||
binder.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
|
||||
.Returns<DefaultModelBindingContext>(mbc =>
|
||||
{
|
||||
mbc.Result = ModelBindingResult.Success(string.Empty, model);
|
||||
return TaskCache.CompletedTask;
|
||||
});
|
||||
binder
|
||||
.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
|
||||
.Returns<DefaultModelBindingContext>(mbc =>
|
||||
{
|
||||
mbc.Result = ModelBindingResult.Success(string.Empty, model);
|
||||
return TaskCache.CompletedTask;
|
||||
});
|
||||
|
||||
context.ModelBinders.Add(binder.Object);
|
||||
context.ValueProviders.Add(new SimpleValueProvider());
|
||||
return context;
|
||||
return GetModelBinderFactory(binder.Object);
|
||||
}
|
||||
|
||||
private static ControllerArgumentBinder GetArgumentBinder(IObjectModelValidator validator = null)
|
||||
private static ModelBinderFactory GetModelBinderFactory(IModelBinder binder)
|
||||
{
|
||||
var provider = new Mock<IModelBinderProvider>();
|
||||
provider
|
||||
.Setup(p => p.GetBinder(It.IsAny<ModelBinderProviderContext>()))
|
||||
.Returns(binder);
|
||||
|
||||
return TestModelBinderFactory.Create(provider.Object);
|
||||
}
|
||||
|
||||
private static ControllerArgumentBinder GetArgumentBinder(
|
||||
IModelBinderFactory factory = null,
|
||||
IObjectModelValidator validator = null)
|
||||
{
|
||||
if (validator == null)
|
||||
{
|
||||
validator = CreateMockValidator();
|
||||
}
|
||||
|
||||
if (factory == null)
|
||||
{
|
||||
factory = TestModelBinderFactory.CreateDefault();
|
||||
}
|
||||
|
||||
return new ControllerArgumentBinder(
|
||||
TestModelMetadataProvider.CreateDefaultProvider(),
|
||||
factory,
|
||||
validator);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using Microsoft.AspNetCore.Mvc.Core;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||
using Xunit;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
// Copyright (c) .NET Foundation. 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 Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public class ArrayModelBinderProviderTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(typeof(object))]
|
||||
[InlineData(typeof(TestClass))]
|
||||
[InlineData(typeof(IList<int>))]
|
||||
public void Create_ForNonArrayTypes_ReturnsNull(Type modelType)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new ArrayModelBinderProvider();
|
||||
var context = new TestModelBinderProviderContext(modelType);
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(byte[]))]
|
||||
[InlineData(typeof(string[]))]
|
||||
[InlineData(typeof(TestClass[]))]
|
||||
[InlineData(typeof(DateTime?[]))]
|
||||
public void Create_ForArrayTypes_ReturnsBinder(Type modelType)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new ArrayModelBinderProvider();
|
||||
var context = new TestModelBinderProviderContext(modelType);
|
||||
context.OnCreatingBinder((m) =>
|
||||
{
|
||||
// Expect to be called with the element type to create a binder for elements.
|
||||
Assert.Equal(modelType.GetElementType(), m.ModelType);
|
||||
return Mock.Of<IModelBinder>();
|
||||
});
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.IsType(typeof(ArrayModelBinder<>).MakeGenericType(modelType.GetElementType()), result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_ForModelMetadataReadOnly_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
metadataProvider.ForProperty(
|
||||
typeof(ModelWithIntArrayProperty),
|
||||
nameof(ModelWithIntArrayProperty.ArrayProperty)).BindingDetails(bd => bd.IsReadOnly = true);
|
||||
|
||||
var modelMetadata = metadataProvider.GetMetadataForProperty(
|
||||
typeof(ModelWithIntArrayProperty),
|
||||
nameof(ModelWithIntArrayProperty.ArrayProperty));
|
||||
|
||||
var provider = new ArrayModelBinderProvider();
|
||||
var context = new TestModelBinderProviderContext(typeof(int[]));
|
||||
context.OnCreatingBinder((m) =>
|
||||
{
|
||||
// Expect to be called with the element type to create a binder for elements.
|
||||
Assert.Equal(typeof(int), m.ModelType);
|
||||
return Mock.Of<IModelBinder>();
|
||||
});
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<ArrayModelBinder<int>>(result);
|
||||
}
|
||||
|
||||
private class TestClass
|
||||
{
|
||||
}
|
||||
|
||||
private class ModelWithIntArrayProperty
|
||||
{
|
||||
public int[] ArrayProperty { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,10 +4,9 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public class ArrayModelBinderTest
|
||||
{
|
||||
|
|
@ -22,7 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
};
|
||||
var bindingContext = GetBindingContext(valueProvider);
|
||||
var modelState = bindingContext.ModelState;
|
||||
var binder = new ArrayModelBinder<int>();
|
||||
var binder = new ArrayModelBinder<int>(new SimpleTypeModelBinder());
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelResultAsync(bindingContext);
|
||||
|
|
@ -32,14 +31,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
|
||||
var array = Assert.IsType<int[]>(result.Model);
|
||||
Assert.Equal(new[] { 42, 84 }, array);
|
||||
Assert.True(modelState.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ArrayModelBinder_CreatesEmptyCollection_IfIsTopLevelObject()
|
||||
{
|
||||
// Arrange
|
||||
var binder = new ArrayModelBinder<string>();
|
||||
var binder = new ArrayModelBinder<string>(new SimpleTypeModelBinder());
|
||||
|
||||
var context = CreateContext();
|
||||
context.IsTopLevelObject = true;
|
||||
|
|
@ -69,7 +67,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
public async Task ArrayModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(string prefix)
|
||||
{
|
||||
// Arrange
|
||||
var binder = new ArrayModelBinder<string>();
|
||||
var binder = new ArrayModelBinder<string>(new SimpleTypeModelBinder());
|
||||
|
||||
var context = CreateContext();
|
||||
context.ModelName = ModelNames.CreatePropertyModelName(prefix, "ArrayProperty");
|
||||
|
|
@ -101,28 +99,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[MemberData(nameof(ArrayModelData))]
|
||||
public async Task BindModelAsync_ModelMetadataReadOnly_ReturnsNull(int[] model)
|
||||
{
|
||||
// Arrange
|
||||
var valueProvider = new SimpleValueProvider
|
||||
{
|
||||
{ "someName[0]", "42" },
|
||||
{ "someName[1]", "84" },
|
||||
};
|
||||
var bindingContext = GetBindingContext(valueProvider, isReadOnly: true);
|
||||
bindingContext.Model = model;
|
||||
var binder = new ArrayModelBinder<int>();
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelResultAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(default(ModelBindingResult), result);
|
||||
}
|
||||
|
||||
// Here "fails silently" means the call does not update the array but also does not throw or set an error.
|
||||
[Theory]
|
||||
[MemberData(nameof(ArrayModelData))]
|
||||
|
|
@ -139,7 +115,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
var bindingContext = GetBindingContext(valueProvider, isReadOnly: false);
|
||||
var modelState = bindingContext.ModelState;
|
||||
bindingContext.Model = model;
|
||||
var binder = new ArrayModelBinder<int>();
|
||||
|
||||
var binder = new ArrayModelBinder<int>(new SimpleTypeModelBinder());
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelResultAsync(bindingContext);
|
||||
|
|
@ -148,8 +125,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
Assert.NotEqual(default(ModelBindingResult), result);
|
||||
Assert.True(result.IsModelSet);
|
||||
Assert.Same(model, result.Model);
|
||||
|
||||
Assert.True(modelState.IsValid);
|
||||
|
||||
for (var i = 0; i < arrayLength; i++)
|
||||
{
|
||||
// Array should be unchanged.
|
||||
|
|
@ -191,7 +167,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
ValueProvider = valueProvider,
|
||||
OperationBindingContext = new OperationBindingContext
|
||||
{
|
||||
ModelBinder = CreateIntBinder(),
|
||||
MetadataProvider = metadataProvider,
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public class BinderTypeModelBinderProviderTest
|
||||
{
|
||||
[Fact]
|
||||
public void Create_WhenBinderTypeIsNull_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new BinderTypeModelBinderProvider();
|
||||
|
||||
var context = new TestModelBinderProviderContext(typeof(Person));
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_WhenBinderTypeIsSet_ReturnsBinder()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new BinderTypeModelBinderProvider();
|
||||
|
||||
var context = new TestModelBinderProviderContext(typeof(Person));
|
||||
context.BindingInfo.BinderType = typeof(NullModelBinder);
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<BinderTypeModelBinder>(result);
|
||||
}
|
||||
|
||||
private class Person
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public int Age { get; set; }
|
||||
}
|
||||
|
||||
private class NullModelBinder : IModelBinder
|
||||
{
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,36 +5,22 @@ using System;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public class BinderTypeBasedModelBinderModelBinderTest
|
||||
public class BinderTypeModelBinderTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task BindModel_ReturnsNothing_IfNoBinderTypeIsSet()
|
||||
{
|
||||
// Arrange
|
||||
var bindingContext = GetBindingContext(typeof(Person));
|
||||
|
||||
var binder = new BinderTypeBasedModelBinder();
|
||||
|
||||
// Act
|
||||
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(default(ModelBindingResult), binderResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModel_ReturnsFailedResult_EvenIfSelectedBinderReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var bindingContext = GetBindingContext(typeof(Person), binderType: typeof(NullModelBinder));
|
||||
|
||||
var binder = new BinderTypeBasedModelBinder();
|
||||
var binder = new BinderTypeModelBinder(typeof(NullModelBinder));
|
||||
|
||||
// Act
|
||||
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
||||
|
|
@ -57,7 +43,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
|
||||
bindingContext.OperationBindingContext.HttpContext.RequestServices = serviceProvider;
|
||||
|
||||
var binder = new BinderTypeBasedModelBinder();
|
||||
var binder = new BinderTypeModelBinder(typeof(NotNullModelBinder));
|
||||
|
||||
// Act
|
||||
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
||||
|
|
@ -69,21 +55,19 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModel_ForNonModelBinder_Throws()
|
||||
public void BindModel_ForNonModelBinder_Throws()
|
||||
{
|
||||
// Arrange
|
||||
var bindingContext = GetBindingContext(typeof(Person), binderType: typeof(Person));
|
||||
var binder = new BinderTypeBasedModelBinder();
|
||||
|
||||
var expected = $"The type '{typeof(Person).FullName}' must implement " +
|
||||
$"'{typeof(IModelBinder).FullName}' to be used as a model binder.";
|
||||
|
||||
// Act
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => binder.BindModelResultAsync(bindingContext));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, ex.Message);
|
||||
// Act & Assert
|
||||
ExceptionAssert.ThrowsArgument(
|
||||
() => new BinderTypeModelBinder(typeof(Person)),
|
||||
"binderType",
|
||||
expected);
|
||||
}
|
||||
|
||||
private static DefaultModelBindingContext GetBindingContext(Type modelType, Type binderType = null)
|
||||
|
|
@ -108,7 +92,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
ValueProvider = Mock.Of<IValueProvider>(),
|
||||
ModelState = new ModelStateDictionary(),
|
||||
OperationBindingContext = operationBindingContext,
|
||||
BinderType = binderType
|
||||
};
|
||||
|
||||
return bindingContext;
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public class BodyModelBinderProviderTest
|
||||
{
|
||||
public static TheoryData<BindingSource> NonBodyBindingSources
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<BindingSource>()
|
||||
{
|
||||
BindingSource.Header,
|
||||
BindingSource.Form,
|
||||
null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(NonBodyBindingSources))]
|
||||
public void Create_WhenBindingSourceIsNotFromBody_ReturnsNull(BindingSource source)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new BodyModelBinderProvider(new TestHttpRequestStreamReaderFactory());
|
||||
|
||||
var context = new TestModelBinderProviderContext(typeof(Person));
|
||||
context.BindingInfo.BindingSource = source;
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_WhenBindingSourceIsFromBody_ReturnsBinder()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new BodyModelBinderProvider(new TestHttpRequestStreamReaderFactory());
|
||||
|
||||
var context = new TestModelBinderProviderContext(typeof(Person));
|
||||
context.BindingInfo.BindingSource = BindingSource.Body;
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<BodyModelBinder>(result);
|
||||
}
|
||||
|
||||
private class Person
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public int Age { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ using Microsoft.Net.Http.Headers;
|
|||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public class BodyModelBinderTests
|
||||
{
|
||||
|
|
@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
new[] { inputFormatter },
|
||||
metadataProvider: provider);
|
||||
|
||||
var binder = new BodyModelBinder(new TestHttpRequestStreamReaderFactory());
|
||||
var binder = CreateBinder();
|
||||
|
||||
// Act
|
||||
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
||||
|
|
@ -60,7 +60,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
|
||||
var bindingContext = GetBindingContext(typeof(Person), metadataProvider: provider);
|
||||
|
||||
var binder = bindingContext.OperationBindingContext.ModelBinder;
|
||||
var binder = CreateBinder();
|
||||
|
||||
// Act
|
||||
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
||||
|
|
@ -88,7 +88,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var bindingContext = GetBindingContext(typeof(Person), metadataProvider: provider);
|
||||
bindingContext.BinderModelName = "custom";
|
||||
|
||||
var binder = bindingContext.OperationBindingContext.ModelBinder;
|
||||
var binder = CreateBinder();
|
||||
|
||||
// Act
|
||||
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
||||
|
|
@ -115,7 +115,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
|
||||
var bindingContext = GetBindingContext(typeof(Person), metadataProvider: provider);
|
||||
|
||||
var binder = bindingContext.OperationBindingContext.ModelBinder;
|
||||
var binder = CreateBinder();
|
||||
|
||||
// Act
|
||||
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
||||
|
|
@ -125,44 +125,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
Assert.False(binderResult.IsModelSet);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModel_IsGreedy_IgnoresWrongSource()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new TestModelMetadataProvider();
|
||||
provider.ForType<Person>().BindingDetails(d => d.BindingSource = BindingSource.Header);
|
||||
|
||||
var bindingContext = GetBindingContext(typeof(Person), metadataProvider: provider);
|
||||
bindingContext.BindingSource = BindingSource.Header;
|
||||
|
||||
var binder = bindingContext.OperationBindingContext.ModelBinder;
|
||||
|
||||
// Act
|
||||
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(default(ModelBindingResult), binderResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModel_IsGreedy_IgnoresMetadataWithNoSource()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new TestModelMetadataProvider();
|
||||
provider.ForType<Person>().BindingDetails(d => d.BindingSource = null);
|
||||
|
||||
var bindingContext = GetBindingContext(typeof(Person), metadataProvider: provider);
|
||||
bindingContext.BindingSource = null;
|
||||
|
||||
var binder = bindingContext.OperationBindingContext.ModelBinder;
|
||||
|
||||
// Act
|
||||
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(default(ModelBindingResult), binderResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CustomFormatterDeserializationException_AddedToModelState()
|
||||
{
|
||||
|
|
@ -180,7 +142,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
httpContext: httpContext,
|
||||
metadataProvider: provider);
|
||||
|
||||
var binder = bindingContext.OperationBindingContext.ModelBinder;
|
||||
var binder = CreateBinder();
|
||||
|
||||
// Act
|
||||
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
||||
|
|
@ -215,7 +177,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
httpContext: httpContext,
|
||||
metadataProvider: provider);
|
||||
|
||||
var binder = bindingContext.OperationBindingContext.ModelBinder;
|
||||
var binder = CreateBinder();
|
||||
|
||||
// Act
|
||||
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
||||
|
|
@ -250,7 +212,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var provider = new TestModelMetadataProvider();
|
||||
provider.ForType<Person>().BindingDetails(d => d.BindingSource = BindingSource.Body);
|
||||
var bindingContext = GetBindingContext(typeof(Person), inputFormatters, metadataProvider: provider);
|
||||
var binder = bindingContext.OperationBindingContext.ModelBinder;
|
||||
|
||||
var binder = CreateBinder();
|
||||
|
||||
// Act
|
||||
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
||||
|
|
@ -288,7 +251,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
HttpContext = httpContext,
|
||||
},
|
||||
InputFormatters = inputFormatters.ToList(),
|
||||
ModelBinder = new BodyModelBinder(new TestHttpRequestStreamReaderFactory()),
|
||||
MetadataProvider = metadataProvider,
|
||||
};
|
||||
|
||||
|
|
@ -307,6 +269,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
return bindingContext;
|
||||
}
|
||||
|
||||
private static BodyModelBinder CreateBinder()
|
||||
{
|
||||
return new BodyModelBinder(new TestHttpRequestStreamReaderFactory());
|
||||
}
|
||||
|
||||
private class Person
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright (c) .NET Foundation. 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 Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public class ByteArrayModelBinderProviderTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(typeof(object))]
|
||||
[InlineData(typeof(TestClass))]
|
||||
[InlineData(typeof(IList<byte>))]
|
||||
[InlineData(typeof(int[]))]
|
||||
public void Create_ForNonByteArrayTypes_ReturnsNull(Type modelType)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new ByteArrayModelBinderProvider();
|
||||
var context = new TestModelBinderProviderContext(modelType);
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_ForByteArray_ReturnsBinder()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new ByteArrayModelBinderProvider();
|
||||
var context = new TestModelBinderProviderContext(typeof(byte[]));
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<ByteArrayModelBinder>(result);
|
||||
}
|
||||
|
||||
private class TestClass
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ using System;
|
|||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public class ByteArrayModelBinderTests
|
||||
{
|
||||
|
|
@ -106,20 +106,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
Assert.Empty(bindingContext.ModelState); // No submitted data for "foo".
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModel_ReturnsNull_ForOtherTypes()
|
||||
{
|
||||
// Arrange
|
||||
var bindingContext = GetBindingContext(new SimpleValueProvider(), typeof(int[]));
|
||||
var binder = new ByteArrayModelBinder();
|
||||
|
||||
// Act
|
||||
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(default(ModelBindingResult), binderResult);
|
||||
}
|
||||
|
||||
private static DefaultModelBindingContext GetBindingContext(IValueProvider valueProvider, Type modelType)
|
||||
{
|
||||
var metadataProvider = new EmptyModelMetadataProvider();
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright (c) .NET Foundation. 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;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public class CancellationTokenModelBinderProviderTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(typeof(object))]
|
||||
[InlineData(typeof(TestClass))]
|
||||
[InlineData(typeof(IList<int>))]
|
||||
[InlineData(typeof(int[]))]
|
||||
public void Create_ForNonCancellationTokenTypes_ReturnsNull(Type modelType)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new CancellationTokenModelBinderProvider();
|
||||
var context = new TestModelBinderProviderContext(modelType);
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_ForCancellationToken_ReturnsBinder()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new CancellationTokenModelBinderProvider();
|
||||
var context = new TestModelBinderProviderContext(typeof(CancellationToken));
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<CancellationTokenModelBinder>(result);
|
||||
}
|
||||
|
||||
private class TestClass
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Http.Internal;
|
|||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public class CancellationTokenModelBinderTests
|
||||
{
|
||||
|
|
@ -28,23 +28,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
Assert.Equal(bindingContext.OperationBindingContext.HttpContext.RequestAborted, result.Model);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(int))]
|
||||
[InlineData(typeof(object))]
|
||||
[InlineData(typeof(CancellationTokenModelBinderTests))]
|
||||
public async Task CancellationTokenModelBinder_ReturnsNull_ForNonCancellationTokenType(Type t)
|
||||
{
|
||||
// Arrange
|
||||
var bindingContext = GetBindingContext(t);
|
||||
var binder = new CancellationTokenModelBinder();
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelResultAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(default(ModelBindingResult), result);
|
||||
}
|
||||
|
||||
private static DefaultModelBindingContext GetBindingContext(Type modelType)
|
||||
{
|
||||
var metadataProvider = new EmptyModelMetadataProvider();
|
||||
|
|
@ -59,7 +42,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
{
|
||||
HttpContext = new DefaultHttpContext(),
|
||||
},
|
||||
ModelBinder = new CancellationTokenModelBinder(),
|
||||
MetadataProvider = metadataProvider,
|
||||
},
|
||||
ValidationState = new ValidationStateDictionary(),
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
// Copyright (c) .NET Foundation. 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.Collections.ObjectModel;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public class CollectionModelBinderProviderTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(typeof(object))]
|
||||
[InlineData(typeof(int))]
|
||||
[InlineData(typeof(Person))]
|
||||
[InlineData(typeof(int[]))]
|
||||
public void Create_ForNonSupportedTypes_ReturnsNull(Type modelType)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new CollectionModelBinderProvider();
|
||||
|
||||
var context = new TestModelBinderProviderContext(modelType);
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
||||
// These aren't ICollection<> - we can handle them by creating a List<>
|
||||
[InlineData(typeof(IEnumerable<int>))]
|
||||
[InlineData(typeof(IReadOnlyCollection<int>))]
|
||||
[InlineData(typeof(IReadOnlyList<int>))]
|
||||
|
||||
// These are ICollection<> - we can handle them by adding items to the existing collection or
|
||||
// creating a new one.
|
||||
[InlineData(typeof(ICollection<int>))]
|
||||
[InlineData(typeof(IList<int>))]
|
||||
[InlineData(typeof(List<int>))]
|
||||
[InlineData(typeof(Collection<int>))]
|
||||
public void Create_ForSupportedTypes_ReturnsBinder(Type modelType)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new CollectionModelBinderProvider();
|
||||
|
||||
var context = new TestModelBinderProviderContext(modelType);
|
||||
|
||||
Type elementType = null;
|
||||
context.OnCreatingBinder(m =>
|
||||
{
|
||||
Assert.Equal(typeof(int), m.ModelType);
|
||||
elementType = m.ModelType;
|
||||
return Mock.Of<IModelBinder>();
|
||||
});
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(elementType);
|
||||
Assert.IsType<CollectionModelBinder<int>>(result);
|
||||
}
|
||||
|
||||
// These aren't ICollection<> - we can handle them by creating a List<> - but in this case
|
||||
// we can't set the property so we can't bind.
|
||||
[Theory]
|
||||
[InlineData(nameof(ReadOnlyProperties.Enumerable))]
|
||||
[InlineData(nameof(ReadOnlyProperties.ReadOnlyCollection))]
|
||||
[InlineData(nameof(ReadOnlyProperties.ReadOnlyList))]
|
||||
public void Create_ForNonICollectionTypes_ReadOnlyProperty_ReturnsNull(string propertyName)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new CollectionModelBinderProvider();
|
||||
|
||||
var metadataProvider = TestModelBinderProviderContext.CachedMetadataProvider;
|
||||
|
||||
var metadata = metadataProvider.GetMetadataForProperty(typeof(ReadOnlyProperties), propertyName);
|
||||
Assert.NotNull(metadata);
|
||||
Assert.True(metadata.IsReadOnly);
|
||||
|
||||
var context = new TestModelBinderProviderContext(metadata, bindingInfo: null);
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
private class Person
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public int Age { get; set; }
|
||||
}
|
||||
|
||||
private class ReadOnlyProperties
|
||||
{
|
||||
public IEnumerable<int> Enumerable { get; }
|
||||
|
||||
public IReadOnlyCollection<int> ReadOnlyCollection { get; }
|
||||
|
||||
public IReadOnlyList<int> ReadOnlyList { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,11 +8,10 @@ using System.Linq;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Test;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public class CollectionModelBinderTest
|
||||
{
|
||||
|
|
@ -26,7 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
{ "someName[baz]", "200" }
|
||||
};
|
||||
var bindingContext = GetModelBindingContext(valueProvider);
|
||||
var binder = new CollectionModelBinder<int>();
|
||||
var binder = new CollectionModelBinder<int>(CreateIntBinder());
|
||||
|
||||
// Act
|
||||
var collectionResult = await binder.BindComplexCollectionFromIndexes(
|
||||
|
|
@ -52,7 +51,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
{ "someName[3]", "400" }
|
||||
};
|
||||
var bindingContext = GetModelBindingContext(valueProvider);
|
||||
var binder = new CollectionModelBinder<int>();
|
||||
var binder = new CollectionModelBinder<int>(CreateIntBinder());
|
||||
|
||||
// Act
|
||||
var boundCollection = await binder.BindComplexCollectionFromIndexes(bindingContext, indexNames: null);
|
||||
|
|
@ -79,7 +78,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
};
|
||||
var bindingContext = GetModelBindingContext(valueProvider, isReadOnly);
|
||||
var modelState = bindingContext.ModelState;
|
||||
var binder = new CollectionModelBinder<int>();
|
||||
var binder = new CollectionModelBinder<int>(CreateIntBinder());
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelResultAsync(bindingContext);
|
||||
|
|
@ -111,7 +110,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var modelState = bindingContext.ModelState;
|
||||
var list = new List<int>();
|
||||
bindingContext.Model = list;
|
||||
var binder = new CollectionModelBinder<int>();
|
||||
var binder = new CollectionModelBinder<int>(CreateIntBinder());
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelResultAsync(bindingContext);
|
||||
|
|
@ -138,7 +137,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
};
|
||||
var bindingContext = GetModelBindingContext(valueProvider, isReadOnly);
|
||||
var modelState = bindingContext.ModelState;
|
||||
var binder = new CollectionModelBinder<int>();
|
||||
var binder = new CollectionModelBinder<int>(CreateIntBinder());
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelResultAsync(bindingContext);
|
||||
|
|
@ -165,7 +164,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var modelState = bindingContext.ModelState;
|
||||
var list = new List<int>();
|
||||
bindingContext.Model = list;
|
||||
var binder = new CollectionModelBinder<int>();
|
||||
var binder = new CollectionModelBinder<int>(CreateIntBinder());
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelResultAsync(bindingContext);
|
||||
|
|
@ -182,7 +181,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public async Task BindModelAsync_SimpleCollectionWithNullValue_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
var binder = new CollectionModelBinder<int>();
|
||||
var binder = new CollectionModelBinder<int>(CreateIntBinder());
|
||||
var valueProvider = new SimpleValueProvider
|
||||
{
|
||||
{ "someName", null },
|
||||
|
|
@ -206,7 +205,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public async Task BindSimpleCollection_RawValueIsEmptyCollection_ReturnsEmptyList()
|
||||
{
|
||||
// Arrange
|
||||
var binder = new CollectionModelBinder<int>();
|
||||
var binder = new CollectionModelBinder<int>(CreateIntBinder());
|
||||
var context = GetModelBindingContext(new SimpleValueProvider());
|
||||
|
||||
// Act
|
||||
|
|
@ -221,7 +220,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public async Task CollectionModelBinder_CreatesEmptyCollection_IfIsTopLevelObject()
|
||||
{
|
||||
// Arrange
|
||||
var binder = new CollectionModelBinder<string>();
|
||||
var binder = new CollectionModelBinder<string>(new StubModelBinder(result: null));
|
||||
|
||||
var context = CreateContext();
|
||||
context.IsTopLevelObject = true;
|
||||
|
|
@ -251,7 +250,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public async Task CollectionModelBinder_DoesNotCreateEmptyCollection_IfModelNonNull()
|
||||
{
|
||||
// Arrange
|
||||
var binder = new CollectionModelBinder<string>();
|
||||
var binder = new CollectionModelBinder<string>(new StubModelBinder(result: null));
|
||||
|
||||
var context = CreateContext();
|
||||
context.IsTopLevelObject = true;
|
||||
|
|
@ -285,7 +284,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public async Task CollectionModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(string prefix)
|
||||
{
|
||||
// Arrange
|
||||
var binder = new CollectionModelBinder<string>();
|
||||
var binder = new CollectionModelBinder<string>(new StubModelBinder(result: null));
|
||||
|
||||
var context = CreateContext();
|
||||
context.ModelName = ModelNames.CreatePropertyModelName(prefix, "ListProperty");
|
||||
|
|
@ -326,7 +325,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public void CanCreateInstance_ReturnsExpectedValue(Type modelType, bool expectedResult)
|
||||
{
|
||||
// Arrange
|
||||
var binder = new CollectionModelBinder<int>();
|
||||
var binder = new CollectionModelBinder<int>(CreateIntBinder());
|
||||
|
||||
// Act
|
||||
var result = binder.CanCreateInstance(modelType);
|
||||
|
|
@ -342,13 +341,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var culture = new CultureInfo("fr-FR");
|
||||
var bindingContext = GetModelBindingContext(new SimpleValueProvider());
|
||||
|
||||
bindingContext.OperationBindingContext.ModelBinder = new StubModelBinder(mbc =>
|
||||
var elementBinder = new StubModelBinder(mbc =>
|
||||
{
|
||||
Assert.Equal("someName", mbc.ModelName);
|
||||
mbc.Result = ModelBindingResult.Success(mbc.ModelName, 42);
|
||||
});
|
||||
|
||||
var modelBinder = new CollectionModelBinder<int>();
|
||||
var modelBinder = new CollectionModelBinder<int>(elementBinder);
|
||||
|
||||
// Act
|
||||
var boundCollection = await modelBinder.BindSimpleCollection(
|
||||
|
|
@ -379,7 +378,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
ValueProvider = valueProvider,
|
||||
OperationBindingContext = new OperationBindingContext
|
||||
{
|
||||
ModelBinder = CreateIntBinder(),
|
||||
MetadataProvider = metadataProvider
|
||||
},
|
||||
ValidationState = new ValidationStateDictionary(),
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
// Copyright (c) .NET Foundation. 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 Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public class ComplexTypeModelBinderProviderTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(typeof(string))]
|
||||
[InlineData(typeof(int))]
|
||||
[InlineData(typeof(List<int>))]
|
||||
public void Create_ForNonComplexType_ReturnsNull(Type modelType)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new ComplexTypeModelBinderProvider();
|
||||
|
||||
var context = new TestModelBinderProviderContext(modelType);
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_ForSupportedTypes_ReturnsBinder()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new ComplexTypeModelBinderProvider();
|
||||
|
||||
var context = new TestModelBinderProviderContext(typeof(Person));
|
||||
context.OnCreatingBinder(m =>
|
||||
{
|
||||
if (m.ModelType == typeof(int) || m.ModelType == typeof(string))
|
||||
{
|
||||
return Mock.Of<IModelBinder>();
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.False(true, "Not the right model type");
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<ComplexTypeModelBinder>(result);
|
||||
}
|
||||
|
||||
private class Person
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public int Age { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,20 +5,21 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Test;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public class MutableObjectModelBinderTest
|
||||
public class ComplexTypeModelBinderTest
|
||||
{
|
||||
private static readonly IModelMetadataProvider _metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||
|
||||
|
|
@ -32,7 +33,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var bindingContext = CreateContext(GetMetadataForType(typeof(Person)));
|
||||
bindingContext.IsTopLevelObject = isTopLevelObject;
|
||||
|
||||
var binder = new TestableMutableObjectModelBinder();
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
// Act
|
||||
var canCreate = binder.CanCreateModel(bindingContext);
|
||||
|
|
@ -49,7 +50,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var bindingContext = CreateContext(modelMetadata);
|
||||
bindingContext.IsTopLevelObject = false;
|
||||
|
||||
var binder = new MutableObjectModelBinder();
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
// Act
|
||||
var canCreate = binder.CanCreateModel(bindingContext);
|
||||
|
|
@ -64,7 +65,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var bindingContext = CreateContext(GetMetadataForType(typeof(Document)));
|
||||
bindingContext.IsTopLevelObject = true;
|
||||
|
||||
var binder = new MutableObjectModelBinder();
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
// Act
|
||||
var canCreate = binder.CanCreateModel(bindingContext);
|
||||
|
|
@ -81,7 +82,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var bindingContext = CreateContext(GetMetadataForType(typeof(HasAllGreedyProperties)));
|
||||
bindingContext.IsTopLevelObject = isTopLevelObject;
|
||||
|
||||
var binder = new TestableMutableObjectModelBinder();
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
// Act
|
||||
var canCreate = binder.CanCreateModel(bindingContext);
|
||||
|
|
@ -109,7 +110,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
bindingContext.ValueProvider = valueProvider.Object;
|
||||
bindingContext.OperationBindingContext.ValueProvider = valueProvider.Object;
|
||||
|
||||
var binder = new MutableObjectModelBinder();
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
// Act
|
||||
var canCreate = binder.CanCreateModel(bindingContext);
|
||||
|
|
@ -126,7 +127,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var bindingContext = CreateContext(GetMetadataForType(typeof(PersonWithNoProperties)));
|
||||
bindingContext.IsTopLevelObject = false;
|
||||
|
||||
var binder = new TestableMutableObjectModelBinder();
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
// Act
|
||||
var canCreate = binder.CanCreateModel(bindingContext);
|
||||
|
|
@ -142,7 +143,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var bindingContext = CreateContext(GetMetadataForType(typeof(PersonWithNoProperties)));
|
||||
bindingContext.IsTopLevelObject = true;
|
||||
|
||||
var binder = new TestableMutableObjectModelBinder();
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
// Act
|
||||
var canCreate = binder.CanCreateModel(bindingContext);
|
||||
|
|
@ -172,7 +173,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
bindingContext.ValueProvider = valueProvider.Object;
|
||||
bindingContext.OperationBindingContext.ValueProvider = valueProvider.Object;
|
||||
|
||||
var binder = new TestableMutableObjectModelBinder();
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
// Act
|
||||
var canCreate = binder.CanCreateModel(bindingContext);
|
||||
|
|
@ -207,7 +208,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
bindingContext.ValueProvider = valueProvider.Object;
|
||||
bindingContext.OperationBindingContext.ValueProvider = originalValueProvider.Object;
|
||||
|
||||
var binder = new TestableMutableObjectModelBinder();
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
// Act
|
||||
var canCreate = binder.CanCreateModel(bindingContext);
|
||||
|
|
@ -240,7 +241,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
bindingContext.ValueProvider = valueProvider.Object;
|
||||
bindingContext.OperationBindingContext.ValueProvider = originalValueProvider.Object;
|
||||
|
||||
var binder = new TestableMutableObjectModelBinder();
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
// Act
|
||||
var canCreate = binder.CanCreateModel(bindingContext);
|
||||
|
|
@ -269,7 +270,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
ValueProvider = mockValueProvider.Object,
|
||||
OperationBindingContext = new OperationBindingContext
|
||||
{
|
||||
ModelBinder = mockBinder,
|
||||
MetadataProvider = _metadataProvider,
|
||||
ValidatorProvider = Mock.Of<IModelValidatorProvider>()
|
||||
},
|
||||
|
|
@ -278,7 +278,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
|
||||
var model = new Person();
|
||||
|
||||
var testableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
|
||||
var testableBinder = new Mock<TestableComplexTypeModelBinder> { CallBase = true };
|
||||
testableBinder
|
||||
.Setup(o => o.CreateModelPublic(bindingContext))
|
||||
.Returns(model)
|
||||
|
|
@ -311,7 +311,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var propertyMetadata = GetMetadataForProperty(typeof(MyModelTestingCanUpdateProperty), propertyName);
|
||||
|
||||
// Act
|
||||
var canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(propertyMetadata);
|
||||
var canUpdate = ComplexTypeModelBinder.CanUpdatePropertyInternal(propertyMetadata);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, canUpdate);
|
||||
|
|
@ -331,7 +331,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var metadata = metadataProvider.GetMetadataForProperty(typeof(CollectionContainer), propertyName);
|
||||
|
||||
// Act
|
||||
var canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(metadata);
|
||||
var canUpdate = ComplexTypeModelBinder.CanUpdatePropertyInternal(metadata);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, canUpdate);
|
||||
|
|
@ -346,10 +346,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
ModelMetadata = GetMetadataForType(typeof(Person))
|
||||
};
|
||||
|
||||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
// Act
|
||||
var model = testableBinder.CreateModelPublic(bindingContext);
|
||||
var model = binder.CreateModelPublic(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<Person>(model);
|
||||
|
|
@ -362,7 +362,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var bindingContext = CreateContext(GetMetadataForType(typeof(Person)), new Person());
|
||||
var originalModel = bindingContext.Model;
|
||||
|
||||
var binder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
|
||||
var binder = new Mock<TestableComplexTypeModelBinder>(){ CallBase = true };
|
||||
binder
|
||||
.Setup(b => b.CreateModelPublic(It.IsAny<ModelBindingContext>()))
|
||||
.Verifiable();
|
||||
|
|
@ -381,7 +381,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
// Arrange
|
||||
var bindingContext = CreateContext(GetMetadataForType(typeof(Person)), model: null);
|
||||
|
||||
var testableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
|
||||
var testableBinder = new Mock<TestableComplexTypeModelBinder> { CallBase = true };
|
||||
testableBinder
|
||||
.Setup(o => o.CreateModelPublic(bindingContext))
|
||||
.Returns(new Person())
|
||||
|
|
@ -402,10 +402,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public void CanBindProperty_GetSetProperty(string property)
|
||||
{
|
||||
// Arrange
|
||||
var binder = new TestableMutableObjectModelBinder();
|
||||
|
||||
var metadata = GetMetadataForProperty(typeof(PersonWithBindExclusion), property);
|
||||
var context = new DefaultModelBindingContext()
|
||||
var bindingContext = new DefaultModelBindingContext()
|
||||
{
|
||||
ModelMetadata = GetMetadataForType(typeof(PersonWithBindExclusion)),
|
||||
OperationBindingContext = new OperationBindingContext()
|
||||
|
|
@ -420,8 +418,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
},
|
||||
};
|
||||
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
// Act
|
||||
var result = binder.CanBindPropertyPublic(context, metadata);
|
||||
var result = binder.CanBindPropertyPublic(bindingContext, metadata);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
|
|
@ -432,10 +432,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public void CanBindProperty_GetOnlyProperty_WithBindNever(string property)
|
||||
{
|
||||
// Arrange
|
||||
var binder = new TestableMutableObjectModelBinder();
|
||||
|
||||
var metadata = GetMetadataForProperty(typeof(PersonWithBindExclusion), property);
|
||||
var context = new DefaultModelBindingContext()
|
||||
var bindingContext = new DefaultModelBindingContext()
|
||||
{
|
||||
ModelMetadata = GetMetadataForType(typeof(PersonWithBindExclusion)),
|
||||
OperationBindingContext = new OperationBindingContext()
|
||||
|
|
@ -450,8 +448,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
},
|
||||
};
|
||||
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
// Act
|
||||
var result = binder.CanBindPropertyPublic(context, metadata);
|
||||
var result = binder.CanBindPropertyPublic(bindingContext, metadata);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
|
|
@ -463,10 +463,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public void CanBindProperty_GetSetProperty_WithBindNever(string property)
|
||||
{
|
||||
// Arrange
|
||||
var binder = new TestableMutableObjectModelBinder();
|
||||
|
||||
var metadata = GetMetadataForProperty(typeof(PersonWithBindExclusion), property);
|
||||
var context = new DefaultModelBindingContext()
|
||||
var bindingContext = new DefaultModelBindingContext()
|
||||
{
|
||||
ModelMetadata = GetMetadataForType(typeof(PersonWithBindExclusion)),
|
||||
OperationBindingContext = new OperationBindingContext()
|
||||
|
|
@ -481,8 +479,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
},
|
||||
};
|
||||
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
// Act
|
||||
var result = binder.CanBindPropertyPublic(context, metadata);
|
||||
var result = binder.CanBindPropertyPublic(bindingContext, metadata);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
|
|
@ -496,10 +496,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public void CanBindProperty_WithPredicate(string property, bool expected)
|
||||
{
|
||||
// Arrange
|
||||
var binder = new TestableMutableObjectModelBinder();
|
||||
|
||||
var metadata = GetMetadataForProperty(typeof(TypeWithExcludedPropertiesUsingBindAttribute), property);
|
||||
var context = new DefaultModelBindingContext()
|
||||
var bindingContext = new DefaultModelBindingContext()
|
||||
{
|
||||
ModelMetadata = GetMetadataForType(typeof(TypeWithExcludedPropertiesUsingBindAttribute)),
|
||||
OperationBindingContext = new OperationBindingContext()
|
||||
|
|
@ -514,8 +512,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
},
|
||||
};
|
||||
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
// Act
|
||||
var result = binder.CanBindPropertyPublic(context, metadata);
|
||||
var result = binder.CanBindPropertyPublic(bindingContext, metadata);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result);
|
||||
|
|
@ -529,10 +529,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public void CanBindProperty_WithBindInclude(string property, bool expected)
|
||||
{
|
||||
// Arrange
|
||||
var binder = new TestableMutableObjectModelBinder();
|
||||
|
||||
var metadata = GetMetadataForProperty(typeof(TypeWithIncludedPropertiesUsingBindAttribute), property);
|
||||
var context = new DefaultModelBindingContext()
|
||||
var bindingContext = new DefaultModelBindingContext()
|
||||
{
|
||||
ModelMetadata = GetMetadataForType(typeof(TypeWithIncludedPropertiesUsingBindAttribute)),
|
||||
OperationBindingContext = new OperationBindingContext()
|
||||
|
|
@ -547,8 +545,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
},
|
||||
};
|
||||
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
// Act
|
||||
var result = binder.CanBindPropertyPublic(context, metadata);
|
||||
var result = binder.CanBindPropertyPublic(bindingContext, metadata);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result);
|
||||
|
|
@ -561,10 +561,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public void CanBindProperty_BindingAttributes_OverridingBehavior(string property, bool expected)
|
||||
{
|
||||
// Arrange
|
||||
var binder = new TestableMutableObjectModelBinder();
|
||||
|
||||
var metadata = GetMetadataForProperty(typeof(ModelWithMixedBindingBehaviors), property);
|
||||
var context = new DefaultModelBindingContext()
|
||||
var bindingContext = new DefaultModelBindingContext()
|
||||
{
|
||||
ModelMetadata = GetMetadataForType(typeof(ModelWithMixedBindingBehaviors)),
|
||||
OperationBindingContext = new OperationBindingContext()
|
||||
|
|
@ -579,8 +577,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
},
|
||||
};
|
||||
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
// Act
|
||||
var result = binder.CanBindPropertyPublic(context, metadata);
|
||||
var result = binder.CanBindPropertyPublic(bindingContext, metadata);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result);
|
||||
|
|
@ -597,13 +597,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
Age = -20
|
||||
};
|
||||
|
||||
var binder = new TestableMutableObjectModelBinder();
|
||||
|
||||
var property = GetMetadataForProperty(model.GetType(), nameof(ModelWithBindRequired.Age));
|
||||
binder.Results[property] = ModelBindingResult.Failed("theModel.Age");
|
||||
|
||||
var bindingContext = CreateContext(GetMetadataForType(model.GetType()), model);
|
||||
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
binder.Results[property] = ModelBindingResult.Failed("theModel.Age");
|
||||
|
||||
// Act
|
||||
await binder.BindModelAsync(bindingContext);
|
||||
|
||||
|
|
@ -632,13 +632,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
Age = -20
|
||||
};
|
||||
|
||||
var binder = new TestableMutableObjectModelBinder();
|
||||
var bindingContext = CreateContext(GetMetadataForType(model.GetType()), model);
|
||||
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
var property = GetMetadataForProperty(model.GetType(), nameof(ModelWithDataMemberIsRequired.Age));
|
||||
binder.Results[property] = ModelBindingResult.Failed("theModel.Age");
|
||||
|
||||
var bindingContext = CreateContext(GetMetadataForType(model.GetType()), model);
|
||||
|
||||
// Act
|
||||
await binder.BindModelAsync(bindingContext);
|
||||
|
||||
|
|
@ -667,15 +667,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
Age = -20
|
||||
};
|
||||
|
||||
var binder = new TestableMutableObjectModelBinder();
|
||||
var bindingContext = CreateContext(GetMetadataForType(model.GetType()), model);
|
||||
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
// Attempt to set non-Nullable property to null. BindRequiredAttribute should not be relevant in this
|
||||
// case because the property did have a result.
|
||||
var property = GetMetadataForProperty(model.GetType(), nameof(ModelWithBindRequired.Age));
|
||||
binder.Results[property] = ModelBindingResult.Success("theModel.Age", model: null);
|
||||
|
||||
var bindingContext = CreateContext(GetMetadataForType(model.GetType()), model);
|
||||
|
||||
// Act
|
||||
await binder.BindModelAsync(bindingContext);
|
||||
|
||||
|
|
@ -701,7 +701,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var model = new BindingOptionalProperty();
|
||||
var bindingContext = CreateContext(GetMetadataForType(model.GetType()), model);
|
||||
|
||||
var binder = new TestableMutableObjectModelBinder();
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
var property = GetMetadataForProperty(model.GetType(), nameof(BindingOptionalProperty.ValueTypeRequired));
|
||||
binder.Results[property] = ModelBindingResult.Failed("theModel.ValueTypeRequired");
|
||||
|
|
@ -721,7 +721,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var model = new NullableValueTypeProperty();
|
||||
var bindingContext = CreateContext(GetMetadataForType(model.GetType()), model);
|
||||
|
||||
var binder = new TestableMutableObjectModelBinder();
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
var property = GetMetadataForProperty(model.GetType(), nameof(NullableValueTypeProperty.NullableValueType));
|
||||
binder.Results[property] = ModelBindingResult.Failed("theModel.NullableValueType");
|
||||
|
|
@ -743,7 +743,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
|
||||
var bindingContext = CreateContext(containerMetadata, model);
|
||||
|
||||
var binder = new TestableMutableObjectModelBinder();
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
var property = GetMetadataForProperty(model.GetType(), nameof(Person.ValueTypeRequired));
|
||||
binder.Results[property] = ModelBindingResult.Failed("theModel." + nameof(Person.ValueTypeRequired));
|
||||
|
|
@ -765,7 +765,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
|
||||
var bindingContext = CreateContext(containerMetadata, model);
|
||||
|
||||
var binder = new TestableMutableObjectModelBinder();
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
var property = GetMetadataForProperty(model.GetType(), nameof(Person.ValueTypeRequired));
|
||||
binder.Results[property] = ModelBindingResult.Success(
|
||||
|
|
@ -794,7 +794,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
|
||||
var bindingContext = CreateContext(containerMetadata, model);
|
||||
|
||||
var binder = new TestableMutableObjectModelBinder();
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
foreach (var property in containerMetadata.Properties)
|
||||
{
|
||||
|
|
@ -833,10 +833,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var propertyMetadata = metadata.Properties[nameof(model.PropertyWithDefaultValue)];
|
||||
|
||||
var result = ModelBindingResult.Failed("foo");
|
||||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
// Act
|
||||
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
||||
binder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
||||
|
||||
// Assert
|
||||
var person = Assert.IsType<Person>(bindingContext.Model);
|
||||
|
|
@ -858,10 +858,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
// The null model value won't be used because IsModelBound = false.
|
||||
var result = ModelBindingResult.Failed("foo");
|
||||
|
||||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
// Act
|
||||
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
||||
binder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
||||
|
||||
// Assert
|
||||
var person = Assert.IsType<Person>(bindingContext.Model);
|
||||
|
|
@ -883,10 +883,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
// The null model value won't be used because IsModelBound = false.
|
||||
var result = ModelBindingResult.Failed("foo");
|
||||
|
||||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
// Act
|
||||
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
||||
binder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
||||
|
||||
// Assert
|
||||
var person = Assert.IsType<Person>(bindingContext.Model);
|
||||
|
|
@ -906,10 +906,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var propertyMetadata = metadata.Properties[nameof(model.NonUpdateableProperty)];
|
||||
|
||||
var result = ModelBindingResult.Failed("foo");
|
||||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
// Act
|
||||
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
||||
binder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
||||
|
||||
// Assert
|
||||
// If didn't throw, success!
|
||||
|
|
@ -953,10 +953,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
propertyName,
|
||||
new Simple { Name = "Hanna" });
|
||||
|
||||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
// Act
|
||||
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
||||
binder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Joe", propertyAccessor(model));
|
||||
|
|
@ -977,7 +977,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var bindingContext = CreateContext(modelMetadata, model);
|
||||
var result = ModelBindingResult.Success(propertyMetadata.PropertyName, new List<string>() { "hi" });
|
||||
|
||||
var binder = new TestableMutableObjectModelBinder();
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
// Act
|
||||
binder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
||||
|
|
@ -999,10 +999,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var propertyMetadata = bindingContext.ModelMetadata.Properties[nameof(model.DateOfBirth)];
|
||||
|
||||
var result = ModelBindingResult.Success("foo", new DateTime(2001, 1, 1));
|
||||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
// Act
|
||||
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
||||
binder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
||||
|
||||
// Assert
|
||||
Assert.True(bindingContext.ModelState.IsValid);
|
||||
|
|
@ -1026,10 +1026,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var propertyMetadata = bindingContext.ModelMetadata.Properties[nameof(model.DateOfDeath)];
|
||||
|
||||
var result = ModelBindingResult.Success("foo", new DateTime(1800, 1, 1));
|
||||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
// Act
|
||||
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
||||
binder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Date of death can't be before date of birth." + Environment.NewLine
|
||||
|
|
@ -1051,10 +1051,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var propertyMetadata = bindingContext.ModelMetadata.Properties[nameof(model.NameNoAttribute)];
|
||||
|
||||
var result = ModelBindingResult.Success("foo.NameNoAttribute", model: null);
|
||||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||
|
||||
// Act
|
||||
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
||||
binder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
||||
|
||||
// Assert
|
||||
Assert.False(bindingContext.ModelState.IsValid);
|
||||
|
|
@ -1064,6 +1064,30 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
bindingContext.ModelState["foo.NameNoAttribute"].Errors[0].Exception.Message);
|
||||
}
|
||||
|
||||
private static TestableComplexTypeModelBinder CreateBinder(ModelMetadata metadata)
|
||||
{
|
||||
var options = new TestOptionsManager<MvcOptions>();
|
||||
MvcCoreMvcOptionsSetup.ConfigureMvc(options.Value, new TestHttpRequestStreamReaderFactory());
|
||||
|
||||
var lastIndex = options.Value.ModelBinderProviders.Count - 1;
|
||||
Assert.IsType<ComplexTypeModelBinderProvider>(options.Value.ModelBinderProviders[lastIndex]);
|
||||
options.Value.ModelBinderProviders.RemoveAt(lastIndex);
|
||||
options.Value.ModelBinderProviders.Add(new TestableComplexTypeModelBinderProvider());
|
||||
|
||||
var factory = TestModelBinderFactory.Create(options.Value.ModelBinderProviders.ToArray());
|
||||
return (TestableComplexTypeModelBinder)factory.CreateBinder(new ModelBinderFactoryContext()
|
||||
{
|
||||
Metadata = metadata,
|
||||
BindingInfo = new BindingInfo()
|
||||
{
|
||||
BinderModelName = metadata.BinderModelName,
|
||||
BinderType = metadata.BinderType,
|
||||
BindingSource = metadata.BindingSource,
|
||||
PropertyBindingPredicateProvider = metadata.PropertyBindingPredicateProvider,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private static DefaultModelBindingContext CreateContext(ModelMetadata metadata, object model = null)
|
||||
{
|
||||
var valueProvider = new TestValueProvider(new Dictionary<string, object>());
|
||||
|
|
@ -1281,7 +1305,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
|
||||
private class NonValueBinderMetadataAttribute : Attribute, IBindingSourceMetadata
|
||||
{
|
||||
public BindingSource BindingSource { get { return BindingSource.Body; } }
|
||||
public BindingSource BindingSource
|
||||
{
|
||||
get { return new BindingSource("Special", string.Empty, isGreedy: true, isFromRequest: true); }
|
||||
}
|
||||
}
|
||||
|
||||
private class ValueBinderMetadataAttribute : Attribute, IBindingSourceMetadata
|
||||
|
|
@ -1333,10 +1360,35 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public IList<int> SettableList { get; set; } = new List<int> { 3, 9, 0 };
|
||||
}
|
||||
|
||||
// Provides the ability to easily mock + call each of these APIs
|
||||
public class TestableMutableObjectModelBinder : MutableObjectModelBinder
|
||||
private class TestableComplexTypeModelBinderProvider : IModelBinderProvider
|
||||
{
|
||||
public TestableMutableObjectModelBinder()
|
||||
public IModelBinder GetBinder(ModelBinderProviderContext context)
|
||||
{
|
||||
if (context.Metadata.IsComplexType)
|
||||
{
|
||||
var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
|
||||
foreach (var property in context.Metadata.Properties)
|
||||
{
|
||||
propertyBinders.Add(property, context.CreateBinder(property));
|
||||
}
|
||||
|
||||
return new TestableComplexTypeModelBinder(propertyBinders);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Provides the ability to easily mock + call each of these APIs
|
||||
public class TestableComplexTypeModelBinder : ComplexTypeModelBinder
|
||||
{
|
||||
public TestableComplexTypeModelBinder()
|
||||
: this(new Dictionary<ModelMetadata, IModelBinder>())
|
||||
{
|
||||
}
|
||||
|
||||
public TestableComplexTypeModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders)
|
||||
: base(propertyBinders)
|
||||
{
|
||||
Results = new Dictionary<ModelMetadata, ModelBindingResult>();
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
// Copyright (c) .NET Foundation. 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 Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public class DictionaryModelBinderProviderTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(typeof(Person))]
|
||||
[InlineData(typeof(string))]
|
||||
[InlineData(typeof(IEnumerable<KeyValuePair<string, int>>))]
|
||||
[InlineData(typeof(ICollection<string>))]
|
||||
public void Create_ForNonDictionaryType_ReturnsNull(Type modelType)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new DictionaryModelBinderProvider();
|
||||
|
||||
var context = new TestModelBinderProviderContext(modelType);
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(IDictionary<string, int>))]
|
||||
[InlineData(typeof(Dictionary<string, int>))]
|
||||
public void Create_ForDictionaryType_ReturnsBinder(Type modelType)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new DictionaryModelBinderProvider();
|
||||
|
||||
var context = new TestModelBinderProviderContext(modelType);
|
||||
context.OnCreatingBinder(m =>
|
||||
{
|
||||
if (m.ModelType == typeof(KeyValuePair<string, int>) ||
|
||||
m.ModelType == typeof(int) ||
|
||||
m.ModelType == typeof(string))
|
||||
{
|
||||
return Mock.Of<IModelBinder>();
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.False(true, "Not the right model type");
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<DictionaryModelBinder<string, int>>(result);
|
||||
}
|
||||
|
||||
private class Person
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public int Age { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,10 +10,9 @@ using Microsoft.AspNetCore.Http.Internal;
|
|||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public class DictionaryModelBinderTest
|
||||
{
|
||||
|
|
@ -23,25 +22,28 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
public async Task BindModel_Succeeds(bool isReadOnly)
|
||||
{
|
||||
// Arrange
|
||||
var values = new Dictionary<string, KeyValuePair<int, string>>()
|
||||
var values = new Dictionary<string, string>()
|
||||
{
|
||||
{ "someName[0]", new KeyValuePair<int, string>(42, "forty-two") },
|
||||
{ "someName[1]", new KeyValuePair<int, string>(84, "eighty-four") },
|
||||
{ "someName[0].Key", "42" },
|
||||
{ "someName[0].Value", "forty-two" },
|
||||
{ "someName[1].Key", "84" },
|
||||
{ "someName[1].Value", "eighty-four" },
|
||||
};
|
||||
|
||||
// Value Provider
|
||||
|
||||
var bindingContext = GetModelBindingContext(isReadOnly, values);
|
||||
var modelState = bindingContext.ModelState;
|
||||
var binder = new DictionaryModelBinder<int, string>();
|
||||
bindingContext.ValueProvider = CreateEnumerableValueProvider("{0}", values);
|
||||
|
||||
var binder = new DictionaryModelBinder<int, string>(new SimpleTypeModelBinder(), new SimpleTypeModelBinder());
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelResultAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.NotEqual(default(ModelBindingResult), result);
|
||||
Assert.True(result.IsModelSet);
|
||||
var dictionary = Assert.IsAssignableFrom<IDictionary<int, string>>(result.Model);
|
||||
Assert.True(modelState.IsValid);
|
||||
|
||||
var dictionary = Assert.IsAssignableFrom<IDictionary<int, string>>(result.Model);
|
||||
Assert.NotNull(dictionary);
|
||||
Assert.Equal(2, dictionary.Count);
|
||||
Assert.Equal("forty-two", dictionary[42]);
|
||||
|
|
@ -57,27 +59,29 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
public async Task BindModel_WithExistingModel_Succeeds(bool isReadOnly)
|
||||
{
|
||||
// Arrange
|
||||
var values = new Dictionary<string, KeyValuePair<int, string>>()
|
||||
var values = new Dictionary<string, string>()
|
||||
{
|
||||
{ "someName[0]", new KeyValuePair<int, string>(42, "forty-two") },
|
||||
{ "someName[1]", new KeyValuePair<int, string>(84, "eighty-four") },
|
||||
{ "someName[0].Key", "42" },
|
||||
{ "someName[0].Value", "forty-two" },
|
||||
{ "someName[1].Key", "84" },
|
||||
{ "someName[1].Value", "eighty-four" },
|
||||
};
|
||||
|
||||
var bindingContext = GetModelBindingContext(isReadOnly, values);
|
||||
var modelState = bindingContext.ModelState;
|
||||
bindingContext.ValueProvider = CreateEnumerableValueProvider("{0}", values);
|
||||
|
||||
var dictionary = new Dictionary<int, string>();
|
||||
bindingContext.Model = dictionary;
|
||||
var binder = new DictionaryModelBinder<int, string>();
|
||||
|
||||
var binder = new DictionaryModelBinder<int, string>(new SimpleTypeModelBinder(), new SimpleTypeModelBinder());
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelResultAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.NotEqual(default(ModelBindingResult), result);
|
||||
Assert.True(result.IsModelSet);
|
||||
Assert.Same(dictionary, result.Model);
|
||||
Assert.True(modelState.IsValid);
|
||||
|
||||
Assert.Same(dictionary, result.Model);
|
||||
Assert.NotNull(dictionary);
|
||||
Assert.Equal(2, dictionary.Count);
|
||||
Assert.Equal("forty-two", dictionary[42]);
|
||||
|
|
@ -123,10 +127,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
IDictionary<string, string> dictionary)
|
||||
{
|
||||
// Arrange
|
||||
var binder = new DictionaryModelBinder<string, string>();
|
||||
var binder = new DictionaryModelBinder<string, string>(new SimpleTypeModelBinder(), new SimpleTypeModelBinder());
|
||||
|
||||
var context = CreateContext();
|
||||
context.ModelName = modelName;
|
||||
context.OperationBindingContext.ModelBinder = CreateCompositeBinder();
|
||||
context.OperationBindingContext.ValueProvider = CreateEnumerableValueProvider(keyFormat, dictionary);
|
||||
context.ValueProvider = context.OperationBindingContext.ValueProvider;
|
||||
context.FieldName = modelName;
|
||||
|
|
@ -140,7 +144,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
var result = await binder.BindModelResultAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotEqual(default(ModelBindingResult), result);
|
||||
Assert.True(result.IsModelSet);
|
||||
Assert.Equal(modelName, result.Key);
|
||||
|
||||
|
|
@ -160,10 +163,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
{ "three", "three" },
|
||||
};
|
||||
|
||||
var binder = new DictionaryModelBinder<string, string>();
|
||||
var binder = new DictionaryModelBinder<string, string>(new SimpleTypeModelBinder(), new SimpleTypeModelBinder());
|
||||
|
||||
var context = CreateContext();
|
||||
context.ModelName = "prefix";
|
||||
context.OperationBindingContext.ModelBinder = CreateCompositeBinder();
|
||||
context.OperationBindingContext.ValueProvider = CreateTestValueProvider("prefix[{0}]", dictionary);
|
||||
context.ValueProvider = context.OperationBindingContext.ValueProvider;
|
||||
context.FieldName = context.ModelName;
|
||||
|
|
@ -210,10 +213,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
{
|
||||
// Arrange
|
||||
var stringDictionary = dictionary.ToDictionary(kvp => kvp.Key.ToString(), kvp => kvp.Value.ToString());
|
||||
var binder = new DictionaryModelBinder<long, int>();
|
||||
|
||||
var binder = new DictionaryModelBinder<long, int>(new SimpleTypeModelBinder(), new SimpleTypeModelBinder());
|
||||
|
||||
var context = CreateContext();
|
||||
context.ModelName = "prefix";
|
||||
context.OperationBindingContext.ModelBinder = CreateCompositeBinder();
|
||||
context.OperationBindingContext.ValueProvider =
|
||||
CreateEnumerableValueProvider("prefix[{0}]", stringDictionary);
|
||||
context.ValueProvider = context.OperationBindingContext.ValueProvider;
|
||||
|
|
@ -252,10 +256,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
{ "prefix[27].Id", "98" },
|
||||
{ "prefix[27].Name", "Fred" },
|
||||
};
|
||||
var binder = new DictionaryModelBinder<int, ModelWithProperties>();
|
||||
|
||||
var context = CreateContext();
|
||||
context.ModelName = "prefix";
|
||||
context.OperationBindingContext.ModelBinder = CreateCompositeBinder();
|
||||
context.OperationBindingContext.ValueProvider = CreateEnumerableValueProvider("{0}", stringDictionary);
|
||||
context.ValueProvider = context.OperationBindingContext.ValueProvider;
|
||||
context.FieldName = context.ModelName;
|
||||
|
|
@ -265,6 +268,16 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
typeof(ModelWithDictionaryProperties),
|
||||
nameof(ModelWithDictionaryProperties.DictionaryWithComplexValuesProperty));
|
||||
|
||||
var valueMetadata = metadataProvider.GetMetadataForType(typeof(ModelWithProperties));
|
||||
|
||||
var binder = new DictionaryModelBinder<int, ModelWithProperties>(
|
||||
new SimpleTypeModelBinder(),
|
||||
new ComplexTypeModelBinder(new Dictionary<ModelMetadata, IModelBinder>()
|
||||
{
|
||||
{ valueMetadata.Properties["Id"], new SimpleTypeModelBinder() },
|
||||
{ valueMetadata.Properties["Name"], new SimpleTypeModelBinder() },
|
||||
}));
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelResultAsync(context);
|
||||
|
||||
|
|
@ -298,10 +311,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
{
|
||||
// Arrange
|
||||
var expectedDictionary = new SortedDictionary<string, string>(dictionary);
|
||||
var binder = new DictionaryModelBinder<string, string>();
|
||||
var binder = new DictionaryModelBinder<string, string>(new SimpleTypeModelBinder(), new SimpleTypeModelBinder());
|
||||
|
||||
var context = CreateContext();
|
||||
context.ModelName = modelName;
|
||||
context.OperationBindingContext.ModelBinder = CreateCompositeBinder();
|
||||
|
||||
context.OperationBindingContext.ValueProvider = CreateEnumerableValueProvider(keyFormat, dictionary);
|
||||
context.ValueProvider = context.OperationBindingContext.ValueProvider;
|
||||
context.FieldName = context.ModelName;
|
||||
|
|
@ -327,7 +341,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
public async Task DictionaryModelBinder_CreatesEmptyCollection_IfIsTopLevelObject()
|
||||
{
|
||||
// Arrange
|
||||
var binder = new DictionaryModelBinder<string, string>();
|
||||
var binder = new DictionaryModelBinder<string, string>(new SimpleTypeModelBinder(), new SimpleTypeModelBinder());
|
||||
|
||||
var context = CreateContext();
|
||||
context.IsTopLevelObject = true;
|
||||
|
|
@ -357,7 +371,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
public async Task DictionaryModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(string prefix)
|
||||
{
|
||||
// Arrange
|
||||
var binder = new DictionaryModelBinder<string, string>();
|
||||
var binder = new DictionaryModelBinder<int, int>(new SimpleTypeModelBinder(), new SimpleTypeModelBinder());
|
||||
|
||||
var context = CreateContext();
|
||||
context.ModelName = ModelNames.CreatePropertyModelName(prefix, "ListProperty");
|
||||
|
|
@ -399,7 +413,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
public void CanCreateInstance_ReturnsExpectedValue(Type modelType, bool expectedResult)
|
||||
{
|
||||
// Arrange
|
||||
var binder = new DictionaryModelBinder<int, int>();
|
||||
var binder = new DictionaryModelBinder<int, int>(new SimpleTypeModelBinder(), new SimpleTypeModelBinder());
|
||||
|
||||
// Act
|
||||
var result = binder.CanCreateInstance(modelType);
|
||||
|
|
@ -427,17 +441,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
return modelBindingContext;
|
||||
}
|
||||
|
||||
private static IModelBinder CreateCompositeBinder()
|
||||
{
|
||||
var binders = new IModelBinder[]
|
||||
{
|
||||
new SimpleTypeModelBinder(),
|
||||
new MutableObjectModelBinder(),
|
||||
};
|
||||
|
||||
return new CompositeModelBinder(binders);
|
||||
}
|
||||
|
||||
private static IValueProvider CreateEnumerableValueProvider(
|
||||
string keyFormat,
|
||||
IDictionary<string, string> dictionary)
|
||||
|
|
@ -468,7 +471,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
|
||||
private static DefaultModelBindingContext GetModelBindingContext(
|
||||
bool isReadOnly,
|
||||
IDictionary<string, KeyValuePair<int, string>> values)
|
||||
IDictionary<string, string> values = null)
|
||||
{
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
metadataProvider
|
||||
|
|
@ -478,15 +481,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
typeof(ModelWithIDictionaryProperty),
|
||||
nameof(ModelWithIDictionaryProperty.DictionaryProperty));
|
||||
|
||||
var binder = new StubModelBinder(mbc =>
|
||||
{
|
||||
KeyValuePair<int, string> value;
|
||||
if (values.TryGetValue(mbc.ModelName, out value))
|
||||
{
|
||||
mbc.Result = ModelBindingResult.Success(mbc.ModelName, value);
|
||||
}
|
||||
});
|
||||
|
||||
var valueProvider = new SimpleValueProvider();
|
||||
foreach (var kvp in values)
|
||||
{
|
||||
|
|
@ -500,7 +494,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
ModelState = new ModelStateDictionary(),
|
||||
OperationBindingContext = new OperationBindingContext
|
||||
{
|
||||
ModelBinder = binder.Object,
|
||||
MetadataProvider = metadataProvider,
|
||||
ValueProvider = valueProvider,
|
||||
},
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright (c) .NET Foundation. 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 Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public class FormCollectionModelBinderProviderTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(typeof(FormCollection))]
|
||||
[InlineData(typeof(TestClass))]
|
||||
[InlineData(typeof(IList<int>))]
|
||||
[InlineData(typeof(int[]))]
|
||||
public void Create_ForNonFormCollectionTypes_ReturnsNull(Type modelType)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new FormCollectionModelBinderProvider();
|
||||
var context = new TestModelBinderProviderContext(modelType);
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_ForFormCollectionToken_ReturnsBinder()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new FormCollectionModelBinderProvider();
|
||||
var context = new TestModelBinderProviderContext(typeof(IFormCollection));
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<FormCollectionModelBinder>(result);
|
||||
}
|
||||
|
||||
private class TestClass
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ using Microsoft.Extensions.Primitives;
|
|||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public class FormCollectionModelBinderTest
|
||||
{
|
||||
|
|
@ -47,47 +47,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
Assert.Equal("value2", form["field2"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FormCollectionModelBinder_InvalidType_BindFails()
|
||||
{
|
||||
// Arrange
|
||||
var formCollection = new FormCollection(new Dictionary<string, StringValues>
|
||||
{
|
||||
{ "field1", "value1" },
|
||||
{ "field2", "value2" }
|
||||
});
|
||||
var httpContext = GetMockHttpContext(formCollection);
|
||||
var bindingContext = GetBindingContext(typeof(string), httpContext);
|
||||
var binder = new FormCollectionModelBinder();
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelResultAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(default(ModelBindingResult), result);
|
||||
}
|
||||
|
||||
// We only support IFormCollection here. Using the concrete type won't work.
|
||||
[Fact]
|
||||
public async Task FormCollectionModelBinder_FormCollectionConcreteType_BindFails()
|
||||
{
|
||||
// Arrange
|
||||
var formCollection = new FormCollection(new Dictionary<string, StringValues>
|
||||
{
|
||||
{ "field1", "value1" },
|
||||
{ "field2", new string[] { "value2" } }
|
||||
});
|
||||
var httpContext = GetMockHttpContext(formCollection);
|
||||
var bindingContext = GetBindingContext(typeof(FormCollection), httpContext);
|
||||
var binder = new FormCollectionModelBinder();
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelResultAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(default(ModelBindingResult), result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FormCollectionModelBinder_NoForm_BindSuccessful_ReturnsEmptyFormCollection()
|
||||
{
|
||||
|
|
@ -127,7 +86,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
{
|
||||
HttpContext = httpContext,
|
||||
},
|
||||
ModelBinder = new FormCollectionModelBinder(),
|
||||
MetadataProvider = metadataProvider,
|
||||
},
|
||||
ValidationState = new ValidationStateDictionary(),
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
// Copyright (c) .NET Foundation. 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.Collections.ObjectModel;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public class FormFileModelBinderProviderTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(typeof(object))]
|
||||
[InlineData(typeof(IFormCollection))]
|
||||
[InlineData(typeof(TestClass))]
|
||||
[InlineData(typeof(IList<int>))]
|
||||
public void Create_ForUnsupportedTypes_ReturnsNull(Type modelType)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new FormFileModelBinderProvider();
|
||||
var context = new TestModelBinderProviderContext(modelType);
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(IFormFile))]
|
||||
[InlineData(typeof(IFormFile[]))]
|
||||
[InlineData(typeof(IFormFileCollection))]
|
||||
[InlineData(typeof(IEnumerable<IFormFile>))]
|
||||
[InlineData(typeof(Collection<IFormFile>))]
|
||||
public void Create_ForSupportedTypes_ReturnsBinder(Type modelType)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new FormFileModelBinderProvider();
|
||||
var context = new TestModelBinderProviderContext(modelType);
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<FormFileModelBinder>(result);
|
||||
}
|
||||
|
||||
private class TestClass
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
|||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public class FormFileModelBinderTest
|
||||
{
|
||||
|
|
@ -108,24 +108,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
Assert.Equal("file1.txt", file.FileName);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(string))]
|
||||
[InlineData(typeof(IEnumerable<string>))]
|
||||
public async Task FormFileModelBinder_ReturnsNothing_ForUnsupportedDestinationTypes(Type destinationType)
|
||||
{
|
||||
// Arrange
|
||||
var formFiles = GetTwoFiles();
|
||||
var httpContext = GetMockHttpContext(GetMockFormCollection(formFiles));
|
||||
var bindingContext = GetBindingContext(destinationType, httpContext);
|
||||
var binder = new FormFileModelBinder();
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelResultAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(default(ModelBindingResult), result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FormFileModelBinder_ReturnsFailedResult_WhenNoFilePosted()
|
||||
{
|
||||
|
|
@ -303,7 +285,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
{
|
||||
HttpContext = httpContext,
|
||||
},
|
||||
ModelBinder = new FormFileModelBinder(),
|
||||
MetadataProvider = metadataProvider,
|
||||
},
|
||||
ValidationState = new ValidationStateDictionary(),
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
// Copyright (c) .NET Foundation. 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.Collections.ObjectModel;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public class HeaderModelBinderProviderTest
|
||||
{
|
||||
public static TheoryData<BindingSource> NonHeaderBindingSources
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<BindingSource>()
|
||||
{
|
||||
BindingSource.Body,
|
||||
BindingSource.Form,
|
||||
null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(NonHeaderBindingSources))]
|
||||
public void Create_WhenBindingSourceIsNotFromHeader_ReturnsNull(BindingSource source)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new HeaderModelBinderProvider();
|
||||
|
||||
var context = new TestModelBinderProviderContext(typeof(string));
|
||||
context.BindingInfo.BindingSource = source;
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_WhenBindingSourceIsFromHeader_ReturnsBinder()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new HeaderModelBinderProvider();
|
||||
|
||||
var context = new TestModelBinderProviderContext(typeof(string));
|
||||
context.BindingInfo.BindingSource = BindingSource.Header;
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<HeaderModelBinder>(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(string))]
|
||||
[InlineData(typeof(IEnumerable<string>))]
|
||||
[InlineData(typeof(string[]))]
|
||||
[InlineData(typeof(Collection<string>))]
|
||||
public void Create_WhenModelTypeIsSupportedType_ReturnsBinder(Type modelType)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new HeaderModelBinderProvider();
|
||||
|
||||
var context = new TestModelBinderProviderContext(modelType);
|
||||
context.BindingInfo.BindingSource = BindingSource.Header;
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<HeaderModelBinder>(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(Dictionary<int, string>))]
|
||||
[InlineData(typeof(Collection<int>))]
|
||||
[InlineData(typeof(Person))]
|
||||
public void Create_WhenModelTypeIsUnsupportedType_ReturnsNull(Type modelType)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new HeaderModelBinderProvider();
|
||||
|
||||
var context = new TestModelBinderProviderContext(modelType);
|
||||
context.BindingInfo.BindingSource = BindingSource.Header;
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
private class Person
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public int Age { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public class HeaderModelBinderTests
|
||||
{
|
||||
|
|
@ -101,50 +101,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
Assert.Equal(headerValue.Split(','), result.Model as IEnumerable<string>);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HeaderBinder_ReturnsNothing_ForNullBindingSource()
|
||||
{
|
||||
// Arrange
|
||||
var type = typeof(string);
|
||||
var header = "User-Agent";
|
||||
var headerValue = "UnitTest";
|
||||
|
||||
var binder = new HeaderModelBinder();
|
||||
var modelBindingContext = GetBindingContext(type);
|
||||
modelBindingContext.BindingSource = null;
|
||||
|
||||
modelBindingContext.FieldName = header;
|
||||
modelBindingContext.OperationBindingContext.HttpContext.Request.Headers.Add(header, new[] { headerValue });
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelResultAsync(modelBindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(default(ModelBindingResult), result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HeaderBinder_ReturnsNothing_ForNonHeaderBindingSource()
|
||||
{
|
||||
// Arrange
|
||||
var type = typeof(string);
|
||||
var header = "User-Agent";
|
||||
var headerValue = "UnitTest";
|
||||
|
||||
var binder = new HeaderModelBinder();
|
||||
var modelBindingContext = GetBindingContext(type);
|
||||
modelBindingContext.BindingSource = BindingSource.Body;
|
||||
|
||||
modelBindingContext.FieldName = header;
|
||||
modelBindingContext.OperationBindingContext.HttpContext.Request.Headers.Add(header, new[] { headerValue });
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelResultAsync(modelBindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(default(ModelBindingResult), result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HeaderBinder_ReturnsFailedResult_ForReadOnlyDestination()
|
||||
{
|
||||
|
|
@ -227,7 +183,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
{
|
||||
HttpContext = new DefaultHttpContext(),
|
||||
},
|
||||
ModelBinder = new HeaderModelBinder(),
|
||||
MetadataProvider = metadataProvider,
|
||||
},
|
||||
BinderModelName = modelMetadata.BinderModelName,
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright (c) .NET Foundation. 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 Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public class KeyValuePairModelBinderProviderTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(typeof(object))]
|
||||
[InlineData(typeof(Person))]
|
||||
[InlineData(typeof(KeyValuePair<string, int>?))]
|
||||
[InlineData(typeof(KeyValuePair<string, int>[]))]
|
||||
public void Create_ForNonKeyValuePair_ReturnsNull(Type modelType)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new KeyValuePairModelBinderProvider();
|
||||
|
||||
var context = new TestModelBinderProviderContext(modelType);
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_ForKeyValuePair_ReturnsBinder()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new KeyValuePairModelBinderProvider();
|
||||
|
||||
var context = new TestModelBinderProviderContext(typeof(KeyValuePair<string, int>));
|
||||
context.OnCreatingBinder(m =>
|
||||
{
|
||||
if (m.ModelType == typeof(string) || m.ModelType == typeof(int))
|
||||
{
|
||||
return Mock.Of<IModelBinder>();
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.False(true, "Not the right model type");
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<KeyValuePairModelBinder<string, int>>(result);
|
||||
}
|
||||
|
||||
private class Person
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public int Age { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,11 +7,9 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.DataAnnotations;
|
||||
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public class KeyValuePairModelBinderTest
|
||||
{
|
||||
|
|
@ -22,8 +20,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
var valueProvider = new SimpleValueProvider();
|
||||
|
||||
// Create string binder to create the value but not the key.
|
||||
var bindingContext = GetBindingContext(valueProvider, CreateStringBinder());
|
||||
var binder = new KeyValuePairModelBinder<int, string>();
|
||||
var bindingContext = GetBindingContext(valueProvider, typeof(KeyValuePair<int, string>));
|
||||
var binder = new KeyValuePairModelBinder<int, string>(CreateIntBinder(false), CreateStringBinder());
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelResultAsync(bindingContext);
|
||||
|
|
@ -44,9 +42,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
var valueProvider = new SimpleValueProvider();
|
||||
|
||||
// Create int binder to create the value but not the key.
|
||||
var bindingContext = GetBindingContext(valueProvider, CreateIntBinder());
|
||||
var binder = new KeyValuePairModelBinder<int, string>();
|
||||
|
||||
var bindingContext = GetBindingContext(valueProvider, typeof(KeyValuePair<int, string>));
|
||||
var binder = new KeyValuePairModelBinder<int, string>(CreateIntBinder(), CreateStringBinder(false));
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelResultAsync(bindingContext);
|
||||
|
||||
|
|
@ -67,11 +65,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
var valueProvider = new SimpleValueProvider();
|
||||
|
||||
// Create int binder to create the value but not the key.
|
||||
var bindingContext = GetBindingContext(valueProvider);
|
||||
var mockBinder = new StubModelBinder();
|
||||
|
||||
bindingContext.OperationBindingContext.ModelBinder = mockBinder;
|
||||
var binder = new KeyValuePairModelBinder<int, string>();
|
||||
var bindingContext = GetBindingContext(valueProvider, typeof(KeyValuePair<int, string>));
|
||||
var binder = new KeyValuePairModelBinder<int, string>(CreateIntBinder(false), CreateStringBinder(false));
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelResultAsync(bindingContext);
|
||||
|
|
@ -86,11 +81,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
public async Task BindModel_SubBindingSucceeds()
|
||||
{
|
||||
// Arrange
|
||||
var innerBinder = new CompositeModelBinder(new[] { CreateStringBinder(), CreateIntBinder() });
|
||||
var valueProvider = new SimpleValueProvider();
|
||||
var bindingContext = GetBindingContext(valueProvider, innerBinder);
|
||||
|
||||
var binder = new KeyValuePairModelBinder<int, string>();
|
||||
var bindingContext = GetBindingContext(valueProvider, typeof(KeyValuePair<int, string>));
|
||||
var binder = new KeyValuePairModelBinder<int, string>(CreateIntBinder(), CreateStringBinder());
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelResultAsync(bindingContext);
|
||||
|
|
@ -124,12 +118,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
Assert.Equal("someName.key", context.ModelName);
|
||||
return innerResult;
|
||||
});
|
||||
var bindingContext = GetBindingContext(new SimpleValueProvider(), innerBinder);
|
||||
|
||||
var binder = new KeyValuePairModelBinder<int, string>();
|
||||
var valueProvider = new SimpleValueProvider();
|
||||
|
||||
var bindingContext = GetBindingContext(valueProvider, typeof(KeyValuePair<int, string>));
|
||||
var binder = new KeyValuePairModelBinder<int, string>(innerBinder, innerBinder);
|
||||
|
||||
// Act
|
||||
var result = await binder.TryBindStrongModel<int>(bindingContext, "key");
|
||||
var result = await binder.TryBindStrongModel<int>(bindingContext, innerBinder, "key");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(innerResult.Value, result);
|
||||
Assert.Empty(bindingContext.ModelState);
|
||||
|
|
@ -139,7 +136,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
public async Task KeyValuePairModelBinder_CreatesEmptyCollection_IfIsTopLevelObject()
|
||||
{
|
||||
// Arrange
|
||||
var binder = new KeyValuePairModelBinder<string, string>();
|
||||
var binder = new KeyValuePairModelBinder<string, string>(new SimpleTypeModelBinder(), new SimpleTypeModelBinder());
|
||||
|
||||
var context = CreateContext();
|
||||
context.IsTopLevelObject = true;
|
||||
|
|
@ -170,7 +167,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
public async Task KeyValuePairModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(string prefix)
|
||||
{
|
||||
// Arrange
|
||||
var binder = new KeyValuePairModelBinder<string, string>();
|
||||
var binder = new KeyValuePairModelBinder<string, string>(new SimpleTypeModelBinder(), new SimpleTypeModelBinder());
|
||||
|
||||
var context = CreateContext();
|
||||
context.ModelName = ModelNames.CreatePropertyModelName(prefix, "KeyValuePairProperty");
|
||||
|
|
@ -200,7 +197,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
HttpContext = new DefaultHttpContext(),
|
||||
},
|
||||
MetadataProvider = new TestModelMetadataProvider(),
|
||||
ModelBinder = new SimpleTypeModelBinder(),
|
||||
},
|
||||
ModelState = new ModelStateDictionary(),
|
||||
};
|
||||
|
|
@ -210,20 +206,17 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
|
||||
private static DefaultModelBindingContext GetBindingContext(
|
||||
IValueProvider valueProvider,
|
||||
IModelBinder innerBinder = null,
|
||||
Type keyValuePairType = null)
|
||||
Type keyValuePairType)
|
||||
{
|
||||
var metataProvider = new EmptyModelMetadataProvider();
|
||||
var bindingContext = new DefaultModelBindingContext
|
||||
{
|
||||
ModelMetadata = metataProvider.GetMetadataForType(
|
||||
keyValuePairType ?? typeof(KeyValuePair<int, string>)),
|
||||
ModelMetadata = metataProvider.GetMetadataForType(keyValuePairType),
|
||||
ModelName = "someName",
|
||||
ModelState = new ModelStateDictionary(),
|
||||
ValueProvider = valueProvider,
|
||||
OperationBindingContext = new OperationBindingContext
|
||||
{
|
||||
ModelBinder = innerBinder ?? CreateIntBinder(),
|
||||
MetadataProvider = metataProvider,
|
||||
ValidatorProvider = new DataAnnotationsModelValidatorProvider(
|
||||
new ValidationAttributeAdapterProvider(),
|
||||
|
|
@ -234,11 +227,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
return bindingContext;
|
||||
}
|
||||
|
||||
private static IModelBinder CreateIntBinder()
|
||||
private static IModelBinder CreateIntBinder(bool success = true)
|
||||
{
|
||||
var mockIntBinder = new StubModelBinder(mbc =>
|
||||
{
|
||||
if (mbc.ModelType == typeof(int))
|
||||
if (mbc.ModelType == typeof(int) && success)
|
||||
{
|
||||
var model = 42;
|
||||
return ModelBindingResult.Success(mbc.ModelName, model);
|
||||
|
|
@ -248,11 +241,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
return mockIntBinder;
|
||||
}
|
||||
|
||||
private static IModelBinder CreateStringBinder()
|
||||
private static IModelBinder CreateStringBinder(bool success = true)
|
||||
{
|
||||
return new StubModelBinder(mbc =>
|
||||
{
|
||||
if (mbc.ModelType == typeof(string))
|
||||
if (mbc.ModelType == typeof(string) && success)
|
||||
{
|
||||
var model = "some-value";
|
||||
return ModelBindingResult.Success(mbc.ModelName, model);
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public class ServicesModelBinderProviderTest
|
||||
{
|
||||
public static TheoryData<BindingSource> NonServicesBindingSources
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<BindingSource>()
|
||||
{
|
||||
BindingSource.Header,
|
||||
BindingSource.Form,
|
||||
null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(NonServicesBindingSources))]
|
||||
public void Create_WhenBindingSourceIsNotFromServices_ReturnsNull(BindingSource source)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new ServicesModelBinderProvider();
|
||||
|
||||
var context = new TestModelBinderProviderContext(typeof(IPersonService));
|
||||
context.BindingInfo.BindingSource = source;
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_WhenBindingSourceIsFromServices_ReturnsBinder()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new ServicesModelBinderProvider();
|
||||
|
||||
var context = new TestModelBinderProviderContext(typeof(IPersonService));
|
||||
context.BindingInfo.BindingSource = BindingSource.Services;
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<ServicesModelBinder>(result);
|
||||
}
|
||||
|
||||
private class IPersonService
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public class ServicesModelBinderTest
|
||||
{
|
||||
|
|
@ -36,40 +36,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
Assert.Null(entry.Metadata);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ServiceModelBinder_ReturnsNothing_ForNullBindingSource()
|
||||
{
|
||||
// Arrange
|
||||
var type = typeof(IService);
|
||||
|
||||
var binder = new ServicesModelBinder();
|
||||
var modelBindingContext = GetBindingContext(type);
|
||||
modelBindingContext.BindingSource = null;
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelResultAsync(modelBindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(default(ModelBindingResult), result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ServiceModelBinder_ReturnsNothing_ForNonServiceBindingSource()
|
||||
{
|
||||
// Arrange
|
||||
var type = typeof(IService);
|
||||
|
||||
var binder = new ServicesModelBinder();
|
||||
var modelBindingContext = GetBindingContext(type);
|
||||
modelBindingContext.BindingSource = BindingSource.Body;
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelResultAsync(modelBindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(default(ModelBindingResult), result);
|
||||
}
|
||||
|
||||
private static DefaultModelBindingContext GetBindingContext(Type modelType)
|
||||
{
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
|
|
@ -95,7 +61,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
RequestServices = services.BuildServiceProvider(),
|
||||
}
|
||||
},
|
||||
ModelBinder = new HeaderModelBinder(),
|
||||
MetadataProvider = metadataProvider,
|
||||
},
|
||||
BinderModelName = modelMetadata.BinderModelName,
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright (c) .NET Foundation. 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 Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public class SimpleTypeModelBinderProviderTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(typeof(object))]
|
||||
[InlineData(typeof(Calendar))]
|
||||
[InlineData(typeof(TestClass))]
|
||||
[InlineData(typeof(List<int>))]
|
||||
public void Create_ForCollectionOrComplexTypes_ReturnsNull(Type modelType)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new SimpleTypeModelBinderProvider();
|
||||
var context = new TestModelBinderProviderContext(modelType);
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(int))]
|
||||
[InlineData(typeof(string))]
|
||||
[InlineData(typeof(DateTime))]
|
||||
[InlineData(typeof(DateTime?))]
|
||||
public void Create_ForSimpleTypes_ReturnsBinder(Type modelType)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new SimpleTypeModelBinderProvider();
|
||||
var context = new TestModelBinderProviderContext(modelType);
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<SimpleTypeModelBinder>(result);
|
||||
}
|
||||
|
||||
private class TestClass
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,32 +7,10 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNetCore.Testing;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
public class SimpleTypeModelBinderTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(typeof(object))]
|
||||
[InlineData(typeof(Calendar))]
|
||||
[InlineData(typeof(TestClass))]
|
||||
public async Task BindModel_ReturnsNothing_IfTypeCannotBeConverted(Type destinationType)
|
||||
{
|
||||
// Arrange
|
||||
var bindingContext = GetBindingContext(destinationType);
|
||||
bindingContext.ValueProvider = new SimpleValueProvider
|
||||
{
|
||||
{ "theModelName", "some-value" }
|
||||
};
|
||||
|
||||
var binder = new SimpleTypeModelBinder();
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelResultAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(default(ModelBindingResult), result);
|
||||
}
|
||||
|
||||
public static TheoryData<Type> ConvertableTypeData
|
||||
{
|
||||
get
|
||||
|
|
@ -134,7 +112,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModel_NullValueProviderResult_ReturnsNull()
|
||||
public async Task BindModel_EmptyValueProviderResult_ReturnsFailed()
|
||||
{
|
||||
// Arrange
|
||||
var bindingContext = GetBindingContext(typeof(int));
|
||||
|
|
@ -144,7 +122,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
var result = await binder.BindModelResultAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(default(ModelBindingResult), result);
|
||||
Assert.Equal(ModelBindingResult.Failed("theModelName"), result);
|
||||
Assert.Empty(bindingContext.ModelState);
|
||||
}
|
||||
|
||||
|
|
@ -1,532 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||
{
|
||||
public class CompositeModelBinderTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task BindModel_SuccessfulBind_ReturnsModel()
|
||||
{
|
||||
// Arrange
|
||||
var bindingContext = new DefaultModelBindingContext
|
||||
{
|
||||
FallbackToEmptyPrefix = true,
|
||||
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(int)),
|
||||
ModelName = "someName",
|
||||
ModelState = new ModelStateDictionary(),
|
||||
OperationBindingContext = new OperationBindingContext(),
|
||||
ValueProvider = new SimpleValueProvider
|
||||
{
|
||||
{ "someName", "dummyValue" }
|
||||
},
|
||||
ValidationState = new ValidationStateDictionary(),
|
||||
FieldName = "someName",
|
||||
};
|
||||
|
||||
var mockIntBinder = new StubModelBinder(context =>
|
||||
{
|
||||
Assert.Same(bindingContext.ModelMetadata, context.ModelMetadata);
|
||||
Assert.Equal("someName", context.ModelName);
|
||||
Assert.Same(bindingContext.ValueProvider, context.ValueProvider);
|
||||
|
||||
context.Result = ModelBindingResult.Success("someName", 42);
|
||||
});
|
||||
var shimBinder = CreateCompositeBinder(mockIntBinder);
|
||||
|
||||
// Act
|
||||
var result = await shimBinder.BindModelResultAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.NotEqual(default(ModelBindingResult), result);
|
||||
Assert.True(result.IsModelSet);
|
||||
Assert.Equal(42, result.Model);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModel_SuccessfulBind_SetsValidationStateAtTopLevel()
|
||||
{
|
||||
// Arrange
|
||||
var bindingContext = new DefaultModelBindingContext
|
||||
{
|
||||
FallbackToEmptyPrefix = true,
|
||||
IsTopLevelObject = true,
|
||||
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(int)),
|
||||
ModelName = "someName",
|
||||
ModelState = new ModelStateDictionary(),
|
||||
OperationBindingContext = new OperationBindingContext(),
|
||||
ValueProvider = new SimpleValueProvider
|
||||
{
|
||||
{ "someName", "dummyValue" }
|
||||
},
|
||||
ValidationState = new ValidationStateDictionary(),
|
||||
FieldName = "someName",
|
||||
};
|
||||
|
||||
var mockIntBinder = new StubModelBinder(context =>
|
||||
{
|
||||
Assert.Same(bindingContext.ModelMetadata, context.ModelMetadata);
|
||||
Assert.Equal("someName", context.ModelName);
|
||||
Assert.Same(bindingContext.ValueProvider, context.ValueProvider);
|
||||
|
||||
context.Result = ModelBindingResult.Success("someName", 42);
|
||||
});
|
||||
var shimBinder = CreateCompositeBinder(mockIntBinder);
|
||||
|
||||
// Act
|
||||
var result = await shimBinder.BindModelResultAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.NotEqual(default(ModelBindingResult), result);
|
||||
Assert.True(result.IsModelSet);
|
||||
Assert.Equal(42, result.Model);
|
||||
|
||||
Assert.Contains(result.Model, bindingContext.ValidationState.Keys);
|
||||
var entry = bindingContext.ValidationState[result.Model];
|
||||
Assert.Equal("someName", entry.Key);
|
||||
Assert.Same(bindingContext.ModelMetadata, entry.Metadata);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModel_SuccessfulBind_DoesNotSetValidationState_WhenNotTopLevel()
|
||||
{
|
||||
// Arrange
|
||||
var bindingContext = new DefaultModelBindingContext
|
||||
{
|
||||
FallbackToEmptyPrefix = true,
|
||||
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(int)),
|
||||
ModelName = "someName",
|
||||
ModelState = new ModelStateDictionary(),
|
||||
OperationBindingContext = new OperationBindingContext(),
|
||||
ValueProvider = new SimpleValueProvider
|
||||
{
|
||||
{ "someName", "dummyValue" }
|
||||
},
|
||||
ValidationState = new ValidationStateDictionary(),
|
||||
FieldName = "someName",
|
||||
};
|
||||
|
||||
var mockIntBinder = new StubModelBinder(context =>
|
||||
{
|
||||
Assert.Same(bindingContext.ModelMetadata, context.ModelMetadata);
|
||||
Assert.Equal("someName", context.ModelName);
|
||||
Assert.Same(bindingContext.ValueProvider, context.ValueProvider);
|
||||
|
||||
context.Result = ModelBindingResult.Success("someName", 42);
|
||||
});
|
||||
var shimBinder = CreateCompositeBinder(mockIntBinder);
|
||||
|
||||
// Act
|
||||
var result = await shimBinder.BindModelResultAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.NotEqual(default(ModelBindingResult), result);
|
||||
Assert.True(result.IsModelSet);
|
||||
Assert.Equal(42, result.Model);
|
||||
|
||||
Assert.Empty(bindingContext.ValidationState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModel_SuccessfulBind_ComplexTypeFallback_ReturnsModel()
|
||||
{
|
||||
// Arrange
|
||||
var expectedModel = new List<int> { 1, 2, 3, 4, 5 };
|
||||
|
||||
var bindingContext = new DefaultModelBindingContext
|
||||
{
|
||||
FallbackToEmptyPrefix = true,
|
||||
IsTopLevelObject = true,
|
||||
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(List<int>)),
|
||||
ModelName = "someName",
|
||||
ModelState = new ModelStateDictionary(),
|
||||
OperationBindingContext = new OperationBindingContext(),
|
||||
ValueProvider = new SimpleValueProvider
|
||||
{
|
||||
{ "someOtherName", "dummyValue" }
|
||||
},
|
||||
ValidationState = new ValidationStateDictionary(),
|
||||
FieldName = "someName",
|
||||
};
|
||||
|
||||
var mockIntBinder = new StubModelBinder(mbc =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(mbc.ModelName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Assert.Same(bindingContext.ModelMetadata, mbc.ModelMetadata);
|
||||
Assert.Equal("", mbc.ModelName);
|
||||
Assert.Same(bindingContext.ValueProvider, mbc.ValueProvider);
|
||||
|
||||
mbc.Result = ModelBindingResult.Success(string.Empty, expectedModel);
|
||||
});
|
||||
|
||||
var shimBinder = CreateCompositeBinder(mockIntBinder);
|
||||
|
||||
// Act
|
||||
var result = await shimBinder.BindModelResultAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.NotEqual(default(ModelBindingResult), result);
|
||||
Assert.True(result.IsModelSet);
|
||||
Assert.Equal(string.Empty, result.Key);
|
||||
Assert.Equal(expectedModel, result.Model);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ModelBinder_ReturnsNothing_IfBinderMatchesButDoesNotSetModel()
|
||||
{
|
||||
// Arrange
|
||||
var bindingContext = new DefaultModelBindingContext
|
||||
{
|
||||
FallbackToEmptyPrefix = true,
|
||||
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(List<int>)),
|
||||
ModelName = "someName",
|
||||
ModelState = new ModelStateDictionary(),
|
||||
OperationBindingContext = new OperationBindingContext(),
|
||||
ValueProvider = new SimpleValueProvider
|
||||
{
|
||||
{ "someOtherName", "dummyValue" }
|
||||
},
|
||||
FieldName = "someName",
|
||||
};
|
||||
|
||||
var mockIntBinder = new StubModelBinder(context =>
|
||||
{
|
||||
context.Result = ModelBindingResult.Failed("someName");
|
||||
});
|
||||
|
||||
var composite = CreateCompositeBinder(mockIntBinder);
|
||||
|
||||
// Act
|
||||
var result = await composite.BindModelResultAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(default(ModelBindingResult), result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ModelBinder_DoesNotFallBackToEmpty_IfFallbackToEmptyPrefixFalse()
|
||||
{
|
||||
// Arrange
|
||||
var bindingContext = new DefaultModelBindingContext
|
||||
{
|
||||
FallbackToEmptyPrefix = false,
|
||||
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(List<int>)),
|
||||
ModelName = "someName",
|
||||
ModelState = new ModelStateDictionary(),
|
||||
OperationBindingContext = new OperationBindingContext(),
|
||||
ValueProvider = new SimpleValueProvider
|
||||
{
|
||||
{ "someOtherName", "dummyValue" }
|
||||
},
|
||||
FieldName = "someName",
|
||||
};
|
||||
|
||||
var mockIntBinder = new StubModelBinder(context =>
|
||||
{
|
||||
Assert.Equal("someName", context.ModelName);
|
||||
context.Result = ModelBindingResult.Failed("someName");
|
||||
});
|
||||
|
||||
var composite = CreateCompositeBinder(mockIntBinder);
|
||||
|
||||
// Act & Assert
|
||||
var result = await composite.BindModelResultAsync(bindingContext);
|
||||
Assert.Equal(1, mockIntBinder.BindModelCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ModelBinder_DoesNotFallBackToEmpty_IfErrorsAreAdded()
|
||||
{
|
||||
// Arrange
|
||||
var bindingContext = new DefaultModelBindingContext
|
||||
{
|
||||
FallbackToEmptyPrefix = false,
|
||||
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(List<int>)),
|
||||
ModelName = "someName",
|
||||
ModelState = new ModelStateDictionary(),
|
||||
OperationBindingContext = new OperationBindingContext(),
|
||||
ValueProvider = new SimpleValueProvider
|
||||
{
|
||||
{ "someOtherName", "dummyValue" }
|
||||
},
|
||||
FieldName = "someName",
|
||||
};
|
||||
|
||||
var mockIntBinder = new StubModelBinder(context =>
|
||||
{
|
||||
Assert.Equal("someName", context.ModelName);
|
||||
context.ModelState.AddModelError(context.ModelName, "this is an error message");
|
||||
context.Result = ModelBindingResult.Failed("someName");
|
||||
});
|
||||
|
||||
var composite = CreateCompositeBinder(mockIntBinder);
|
||||
|
||||
// Act & Assert
|
||||
var result = await composite.BindModelResultAsync(bindingContext);
|
||||
Assert.Equal(1, mockIntBinder.BindModelCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ModelBinder_ReturnsNonEmptyResult_SetsNullValue_SetsModelStateKey()
|
||||
{
|
||||
// Arrange
|
||||
var bindingContext = new DefaultModelBindingContext
|
||||
{
|
||||
FallbackToEmptyPrefix = true,
|
||||
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(List<int>)),
|
||||
ModelName = "someName",
|
||||
ModelState = new ModelStateDictionary(),
|
||||
OperationBindingContext = new OperationBindingContext(),
|
||||
ValueProvider = new SimpleValueProvider
|
||||
{
|
||||
{ "someOtherName", "dummyValue" }
|
||||
},
|
||||
FieldName = "someName",
|
||||
};
|
||||
|
||||
var mockIntBinder = new StubModelBinder(context =>
|
||||
{
|
||||
context.Result = ModelBindingResult.Success("someName", model: null);
|
||||
});
|
||||
|
||||
var composite = CreateCompositeBinder(mockIntBinder);
|
||||
|
||||
// Act
|
||||
var result = await composite.BindModelResultAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.NotEqual(default(ModelBindingResult), result);
|
||||
Assert.True(result.IsModelSet);
|
||||
Assert.Equal("someName", result.Key);
|
||||
Assert.Null(result.Model);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModel_UnsuccessfulBind_SimpleTypeNoFallback_ReturnsNothing()
|
||||
{
|
||||
// Arrange
|
||||
var shimBinder = CreateCompositeBinder(new StubModelBinder());
|
||||
|
||||
var bindingContext = new DefaultModelBindingContext
|
||||
{
|
||||
FallbackToEmptyPrefix = true,
|
||||
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(int)),
|
||||
ModelState = new ModelStateDictionary(),
|
||||
OperationBindingContext = new OperationBindingContext(),
|
||||
ValueProvider = new SimpleValueProvider(),
|
||||
FieldName = "test-field",
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await shimBinder.BindModelResultAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(default(ModelBindingResult), result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModel_WithDefaultBinders_BindsSimpleType()
|
||||
{
|
||||
// Arrange
|
||||
var binder = CreateBinderWithDefaults();
|
||||
|
||||
var valueProvider = new SimpleValueProvider
|
||||
{
|
||||
{ "firstName", "firstName-value"},
|
||||
{ "lastName", "lastName-value"}
|
||||
};
|
||||
var bindingContext = CreateBindingContext(binder, valueProvider, typeof(SimplePropertiesModel));
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelResultAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.NotEqual(default(ModelBindingResult), result);
|
||||
var model = Assert.IsType<SimplePropertiesModel>(result.Model);
|
||||
Assert.Equal("firstName-value", model.FirstName);
|
||||
Assert.Equal("lastName-value", model.LastName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModel_WithDefaultBinders_BindsComplexType()
|
||||
{
|
||||
// Arrange
|
||||
var binder = CreateBinderWithDefaults();
|
||||
|
||||
var valueProvider = new SimpleValueProvider
|
||||
{
|
||||
{ "firstName", "firstName-value"},
|
||||
{ "lastName", "lastName-value"},
|
||||
{ "friends[0].firstName", "first-friend"},
|
||||
{ "friends[0].age", "40"},
|
||||
{ "friends[0].friends[0].firstname", "nested friend"},
|
||||
{ "friends[1].firstName", "some other"},
|
||||
{ "friends[1].lastName", "name"},
|
||||
{ "resume", "4+mFeTp3tPF=" }
|
||||
};
|
||||
|
||||
var bindingContext = CreateBindingContext(binder, valueProvider, typeof(Person));
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelResultAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.NotEqual(default(ModelBindingResult), result);
|
||||
var model = Assert.IsType<Person>(result.Model);
|
||||
Assert.Equal("firstName-value", model.FirstName);
|
||||
Assert.Equal("lastName-value", model.LastName);
|
||||
Assert.Equal(2, model.Friends.Count);
|
||||
Assert.Equal("first-friend", model.Friends[0].FirstName);
|
||||
Assert.Equal(40, model.Friends[0].Age);
|
||||
var nestedFriend = Assert.Single(model.Friends[0].Friends);
|
||||
Assert.Equal("nested friend", nestedFriend.FirstName);
|
||||
Assert.Equal("some other", model.Friends[1].FirstName);
|
||||
Assert.Equal("name", model.Friends[1].LastName);
|
||||
Assert.Equal(new byte[] { 227, 233, 133, 121, 58, 119, 180, 241 }, model.Resume);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModel_DoesNotAddAValidationNode_IfModelIsNotSet()
|
||||
{
|
||||
// Arrange
|
||||
var valueProvider = new SimpleValueProvider();
|
||||
var mockBinder = new StubModelBinder(ModelBindingResult.Failed("someName"));
|
||||
|
||||
var binder = CreateCompositeBinder(mockBinder);
|
||||
var bindingContext = CreateBindingContext(binder, valueProvider, typeof(SimplePropertiesModel));
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelResultAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(default(ModelBindingResult), result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModel_DoesNotAddAValidationNode_IfModelBindingResultIsNothing()
|
||||
{
|
||||
// Arrange
|
||||
var mockBinder = new StubModelBinder();
|
||||
var binder = CreateCompositeBinder(mockBinder);
|
||||
var valueProvider = new SimpleValueProvider();
|
||||
var bindingContext = CreateBindingContext(binder, valueProvider, typeof(SimplePropertiesModel));
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelResultAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(default(ModelBindingResult), result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModel_UsesTheValidationNodeOnModelBindingResult_IfPresent()
|
||||
{
|
||||
// Arrange
|
||||
var valueProvider = new SimpleValueProvider();
|
||||
|
||||
var mockBinder = new StubModelBinder(ModelBindingResult.Success("someName", 42));
|
||||
|
||||
var binder = CreateCompositeBinder(mockBinder);
|
||||
var bindingContext = CreateBindingContext(binder, valueProvider, typeof(SimplePropertiesModel));
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelResultAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.NotEqual(default(ModelBindingResult), result);
|
||||
Assert.True(result.IsModelSet);
|
||||
}
|
||||
|
||||
private static DefaultModelBindingContext CreateBindingContext(
|
||||
IModelBinder binder,
|
||||
IValueProvider valueProvider,
|
||||
Type type)
|
||||
{
|
||||
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||
var bindingContext = new DefaultModelBindingContext
|
||||
{
|
||||
FallbackToEmptyPrefix = true,
|
||||
IsTopLevelObject = true,
|
||||
ModelMetadata = metadataProvider.GetMetadataForType(type),
|
||||
ModelName = "parameter",
|
||||
FieldName = "parameter",
|
||||
ModelState = new ModelStateDictionary(),
|
||||
ValueProvider = valueProvider,
|
||||
OperationBindingContext = new OperationBindingContext
|
||||
{
|
||||
MetadataProvider = metadataProvider,
|
||||
ModelBinder = binder,
|
||||
},
|
||||
ValidationState = new ValidationStateDictionary(),
|
||||
};
|
||||
return bindingContext;
|
||||
}
|
||||
|
||||
private static CompositeModelBinder CreateBinderWithDefaults()
|
||||
{
|
||||
var binders = new IModelBinder[]
|
||||
{
|
||||
new ByteArrayModelBinder(),
|
||||
new GenericModelBinder(),
|
||||
new SimpleTypeModelBinder(),
|
||||
new MutableObjectModelBinder()
|
||||
};
|
||||
|
||||
var binder = new CompositeModelBinder(binders);
|
||||
return binder;
|
||||
}
|
||||
|
||||
private static CompositeModelBinder CreateCompositeBinder(IModelBinder mockIntBinder)
|
||||
{
|
||||
var shimBinder = new CompositeModelBinder(new[] { mockIntBinder });
|
||||
return shimBinder;
|
||||
}
|
||||
|
||||
private class SimplePropertiesModel
|
||||
{
|
||||
public string FirstName { get; set; }
|
||||
public string LastName { get; set; }
|
||||
}
|
||||
|
||||
private sealed class Person
|
||||
{
|
||||
public string FirstName { get; set; }
|
||||
|
||||
public string LastName { get; set; }
|
||||
|
||||
public int Age { get; set; }
|
||||
|
||||
public List<Person> Friends { get; set; }
|
||||
|
||||
public byte[] Resume { get; set; }
|
||||
}
|
||||
|
||||
private class User : IValidatableObject
|
||||
{
|
||||
public string Password { get; set; }
|
||||
|
||||
[Compare("Password")]
|
||||
public string ConfirmPassword { get; set; }
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
if (Password == "password")
|
||||
{
|
||||
yield return new ValidationResult("Password does not meet complexity requirements.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,9 +2,13 @@
|
|||
// 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.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Test;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
|
|
@ -37,7 +41,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
|
||||
// Act
|
||||
var originalBinderModelName = bindingContext.BinderModelName;
|
||||
var originalBinderType = bindingContext.BinderType;
|
||||
var originalBindingSource = bindingContext.BindingSource;
|
||||
var originalModelState = bindingContext.ModelState;
|
||||
var originalOperationBindingContext = bindingContext.OperationBindingContext;
|
||||
|
|
@ -51,9 +54,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
|
||||
// Assert
|
||||
Assert.Same(newModelMetadata.BinderModelName, bindingContext.BinderModelName);
|
||||
Assert.Same(newModelMetadata.BinderType, bindingContext.BinderType);
|
||||
Assert.Same(newModelMetadata.BindingSource, bindingContext.BindingSource);
|
||||
Assert.False(bindingContext.FallbackToEmptyPrefix);
|
||||
Assert.Equal("fieldName", bindingContext.FieldName);
|
||||
Assert.False(bindingContext.IsTopLevelObject);
|
||||
Assert.Null(bindingContext.Model);
|
||||
|
|
@ -66,6 +67,98 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
disposable.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateBindingContext_FiltersValueProviders_ForValueProviderSource()
|
||||
{
|
||||
// Arrange
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
|
||||
var original = CreateDefaultValueProvider();
|
||||
var operationBindingContext = new OperationBindingContext()
|
||||
{
|
||||
ActionContext = new ActionContext(),
|
||||
ValueProvider = original,
|
||||
};
|
||||
|
||||
// Act
|
||||
var context = DefaultModelBindingContext.CreateBindingContext(
|
||||
operationBindingContext,
|
||||
metadataProvider.GetMetadataForType(typeof(object)),
|
||||
new BindingInfo() { BindingSource = BindingSource.Query },
|
||||
"model");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
Assert.IsType<CompositeValueProvider>(context.ValueProvider),
|
||||
vp => Assert.Same(original[1], vp));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnterNestedScope_FiltersValueProviders_ForValueProviderSource()
|
||||
{
|
||||
// Arrange
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
metadataProvider
|
||||
.ForProperty(typeof(string), nameof(string.Length))
|
||||
.BindingDetails(b => b.BindingSource = BindingSource.Query);
|
||||
|
||||
var original = CreateDefaultValueProvider();
|
||||
var operationBindingContext = new OperationBindingContext()
|
||||
{
|
||||
ActionContext = new ActionContext(),
|
||||
ValueProvider = original,
|
||||
};
|
||||
|
||||
var context = DefaultModelBindingContext.CreateBindingContext(
|
||||
operationBindingContext,
|
||||
metadataProvider.GetMetadataForType(typeof(string)),
|
||||
new BindingInfo(),
|
||||
"model");
|
||||
|
||||
var propertyMetadata = metadataProvider.GetMetadataForProperty(typeof(string), nameof(string.Length));
|
||||
|
||||
// Act
|
||||
context.EnterNestedScope(propertyMetadata, "Length", "Length", model: null);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
Assert.IsType<CompositeValueProvider>(context.ValueProvider),
|
||||
vp => Assert.Same(original[1], vp));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnterNestedScope_FiltersValueProviders_BasedOnTopLevelValueProviders()
|
||||
{
|
||||
// Arrange
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
metadataProvider
|
||||
.ForProperty(typeof(string), nameof(string.Length))
|
||||
.BindingDetails(b => b.BindingSource = BindingSource.Form);
|
||||
|
||||
var original = CreateDefaultValueProvider();
|
||||
var operationBindingContext = new OperationBindingContext()
|
||||
{
|
||||
ActionContext = new ActionContext(),
|
||||
ValueProvider = original,
|
||||
};
|
||||
|
||||
var context = DefaultModelBindingContext.CreateBindingContext(
|
||||
operationBindingContext,
|
||||
metadataProvider.GetMetadataForType(typeof(string)),
|
||||
new BindingInfo() { BindingSource = BindingSource.Query },
|
||||
"model");
|
||||
|
||||
var propertyMetadata = metadataProvider.GetMetadataForProperty(typeof(string), nameof(string.Length));
|
||||
|
||||
// Act
|
||||
context.EnterNestedScope(propertyMetadata, "Length", "Length", model: null);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
Assert.IsType<CompositeValueProvider>(context.ValueProvider),
|
||||
vp => Assert.Same(original[2], vp));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModelTypeAreFedFromModelMetadata()
|
||||
{
|
||||
|
|
@ -79,6 +172,21 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
Assert.Equal(typeof(int), bindingContext.ModelType);
|
||||
}
|
||||
|
||||
private static CompositeValueProvider CreateDefaultValueProvider()
|
||||
{
|
||||
var result = new CompositeValueProvider();
|
||||
result.Add(new RouteValueProvider(BindingSource.Path, new RouteValueDictionary()));
|
||||
result.Add(new QueryStringValueProvider(
|
||||
BindingSource.Query,
|
||||
new QueryCollection(),
|
||||
CultureInfo.InvariantCulture));
|
||||
result.Add(new FormValueProvider(
|
||||
BindingSource.Form,
|
||||
new FormCollection(new Dictionary<string, StringValues>()),
|
||||
CultureInfo.CurrentCulture));
|
||||
return result;
|
||||
}
|
||||
|
||||
private class TestModelBinder : IModelBinder
|
||||
{
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
|
|
|
|||
|
|
@ -0,0 +1,228 @@
|
|||
// Copyright (c) .NET Foundation. 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.AspNetCore.Mvc.ModelBinding.Internal;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
{
|
||||
public class ModelBinderFactoryTest
|
||||
{
|
||||
// No providers => can't create a binder
|
||||
[Fact]
|
||||
public void CreateBinder_Throws_WhenBinderNotCreated()
|
||||
{
|
||||
// Arrange
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
var options = new TestOptionsManager<MvcOptions>();
|
||||
var factory = new ModelBinderFactory(metadataProvider, options);
|
||||
|
||||
var context = new ModelBinderFactoryContext()
|
||||
{
|
||||
Metadata = metadataProvider.GetMetadataForType(typeof(string)),
|
||||
};
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => factory.CreateBinder(context));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
$"Could not create a model binder for model object of type '{typeof(string).FullName}'.",
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateBinder_CreatesNoOpBinder_WhenPropertyDoesntHaveABinder()
|
||||
{
|
||||
// Arrange
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
|
||||
// There isn't a provider that can handle WidgetId.
|
||||
var options = new TestOptionsManager<MvcOptions>();
|
||||
options.Value.ModelBinderProviders.Add(new TestModelBinderProvider(c =>
|
||||
{
|
||||
if (c.Metadata.ModelType == typeof(Widget))
|
||||
{
|
||||
Assert.NotNull(c.CreateBinder(c.Metadata.Properties[nameof(Widget.Id)]));
|
||||
return Mock.Of<IModelBinder>();
|
||||
}
|
||||
|
||||
return null;
|
||||
}));
|
||||
|
||||
var factory = new ModelBinderFactory(metadataProvider, options);
|
||||
|
||||
var context = new ModelBinderFactoryContext()
|
||||
{
|
||||
Metadata = metadataProvider.GetMetadataForType(typeof(Widget)),
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = factory.CreateBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateBinder_NestedProperties()
|
||||
{
|
||||
// Arrange
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
|
||||
var options = new TestOptionsManager<MvcOptions>();
|
||||
options.Value.ModelBinderProviders.Add(new TestModelBinderProvider(c =>
|
||||
{
|
||||
if (c.Metadata.ModelType == typeof(Widget))
|
||||
{
|
||||
Assert.NotNull(c.CreateBinder(c.Metadata.Properties[nameof(Widget.Id)]));
|
||||
return Mock.Of<IModelBinder>();
|
||||
}
|
||||
else if (c.Metadata.ModelType == typeof(WidgetId))
|
||||
{
|
||||
return Mock.Of<IModelBinder>();
|
||||
}
|
||||
|
||||
return null;
|
||||
}));
|
||||
|
||||
var factory = new ModelBinderFactory(metadataProvider, options);
|
||||
|
||||
var context = new ModelBinderFactoryContext()
|
||||
{
|
||||
Metadata = metadataProvider.GetMetadataForType(typeof(Widget)),
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = factory.CreateBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateBinder_BreaksCycles()
|
||||
{
|
||||
// Arrange
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
|
||||
var callCount = 0;
|
||||
|
||||
var options = new TestOptionsManager<MvcOptions>();
|
||||
options.Value.ModelBinderProviders.Add(new TestModelBinderProvider(c =>
|
||||
{
|
||||
var currentCallCount = ++callCount;
|
||||
Assert.Equal(typeof(Employee), c.Metadata.ModelType);
|
||||
var binder = c.CreateBinder(c.Metadata.Properties[nameof(Employee.Manager)]);
|
||||
|
||||
if (currentCallCount == 2)
|
||||
{
|
||||
Assert.IsType<PlaceholderBinder>(binder);
|
||||
}
|
||||
|
||||
return Mock.Of<IModelBinder>();
|
||||
}));
|
||||
|
||||
var factory = new ModelBinderFactory(metadataProvider, options);
|
||||
|
||||
var context = new ModelBinderFactoryContext()
|
||||
{
|
||||
Metadata = metadataProvider.GetMetadataForType(typeof(Employee)),
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = factory.CreateBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateBinder_DoesNotCache_WhenTokenIsNull()
|
||||
{
|
||||
// Arrange
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
|
||||
var options = new TestOptionsManager<MvcOptions>();
|
||||
options.Value.ModelBinderProviders.Add(new TestModelBinderProvider(c =>
|
||||
{
|
||||
Assert.Equal(typeof(Employee), c.Metadata.ModelType);
|
||||
return Mock.Of<IModelBinder>();
|
||||
}));
|
||||
|
||||
var factory = new ModelBinderFactory(metadataProvider, options);
|
||||
|
||||
var context = new ModelBinderFactoryContext()
|
||||
{
|
||||
Metadata = metadataProvider.GetMetadataForType(typeof(Employee)),
|
||||
};
|
||||
|
||||
// Act
|
||||
var result1 = factory.CreateBinder(context);
|
||||
var result2 = factory.CreateBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotSame(result1, result2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateBinder_Caches_WhenTokenIsNotNull()
|
||||
{
|
||||
// Arrange
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
|
||||
var options = new TestOptionsManager<MvcOptions>();
|
||||
options.Value.ModelBinderProviders.Add(new TestModelBinderProvider(c =>
|
||||
{
|
||||
Assert.Equal(typeof(Employee), c.Metadata.ModelType);
|
||||
return Mock.Of<IModelBinder>();
|
||||
}));
|
||||
|
||||
var factory = new ModelBinderFactory(metadataProvider, options);
|
||||
|
||||
var context = new ModelBinderFactoryContext()
|
||||
{
|
||||
Metadata = metadataProvider.GetMetadataForType(typeof(Employee)),
|
||||
CacheToken = new object(),
|
||||
};
|
||||
|
||||
// Act
|
||||
var result1 = factory.CreateBinder(context);
|
||||
var result2 = factory.CreateBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.Same(result1, result2);
|
||||
}
|
||||
|
||||
private class Widget
|
||||
{
|
||||
public WidgetId Id { get; set; }
|
||||
}
|
||||
|
||||
private class WidgetId
|
||||
{
|
||||
}
|
||||
|
||||
private class Employee
|
||||
{
|
||||
public Employee Manager { get; set; }
|
||||
}
|
||||
|
||||
private class TestModelBinderProvider : IModelBinderProvider
|
||||
{
|
||||
private readonly Func<ModelBinderProviderContext, IModelBinder> _factory;
|
||||
|
||||
public TestModelBinderProvider(Func<ModelBinderProviderContext, IModelBinder> factory)
|
||||
{
|
||||
_factory = factory;
|
||||
}
|
||||
|
||||
public IModelBinder GetBinder(ModelBinderProviderContext context)
|
||||
{
|
||||
return _factory(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Mvc.DataAnnotations;
|
|||
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Test;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using Moq;
|
||||
|
|
@ -49,7 +50,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
string.Empty,
|
||||
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
||||
metadataProvider,
|
||||
GetCompositeBinder(binder),
|
||||
GetModelBinderFactory(binder),
|
||||
Mock.Of<IValueProvider>(),
|
||||
new List<IInputFormatter>(),
|
||||
new Mock<IObjectModelValidator>(MockBehavior.Strict).Object,
|
||||
|
|
@ -66,10 +67,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
// Arrange
|
||||
// Mono issue - https://github.com/aspnet/External/issues/19
|
||||
var expectedMessage = PlatformNormalizer.NormalizeContent("The MyProperty field is required.");
|
||||
var binders = new IModelBinder[]
|
||||
var binderProviders = new IModelBinderProvider[]
|
||||
{
|
||||
new SimpleTypeModelBinder(),
|
||||
new MutableObjectModelBinder()
|
||||
new SimpleTypeModelBinderProvider(),
|
||||
new ComplexTypeModelBinderProvider(),
|
||||
};
|
||||
|
||||
var validator = new DataAnnotationsModelValidatorProvider(
|
||||
|
|
@ -94,7 +95,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
"",
|
||||
actionContext,
|
||||
modelMetadataProvider,
|
||||
GetCompositeBinder(binders),
|
||||
GetModelBinderFactory(binderProviders),
|
||||
valueProvider,
|
||||
new List<IInputFormatter>(),
|
||||
new DefaultObjectValidator(modelMetadataProvider, new ValidatorCache()),
|
||||
|
|
@ -110,10 +111,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public async Task TryUpdateModel_ReturnsTrue_IfModelBindsAndValidatesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
var binders = new IModelBinder[]
|
||||
var binderProviders = new IModelBinderProvider[]
|
||||
{
|
||||
new SimpleTypeModelBinder(),
|
||||
new MutableObjectModelBinder()
|
||||
new SimpleTypeModelBinderProvider(),
|
||||
new ComplexTypeModelBinderProvider(),
|
||||
};
|
||||
|
||||
var validator = new DataAnnotationsModelValidatorProvider(
|
||||
|
|
@ -136,7 +137,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
"",
|
||||
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
||||
metadataProvider,
|
||||
GetCompositeBinder(binders),
|
||||
GetModelBinderFactory(binderProviders),
|
||||
valueProvider,
|
||||
new List<IInputFormatter>(),
|
||||
new DefaultObjectValidator(metadataProvider, new ValidatorCache()),
|
||||
|
|
@ -164,7 +165,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
string.Empty,
|
||||
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
||||
metadataProvider,
|
||||
GetCompositeBinder(binder),
|
||||
GetModelBinderFactory(binder),
|
||||
Mock.Of<IValueProvider>(),
|
||||
new List<IInputFormatter>(),
|
||||
new Mock<IObjectModelValidator>(MockBehavior.Strict).Object,
|
||||
|
|
@ -182,10 +183,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public async Task TryUpdateModel_UsingIncludePredicateOverload_ReturnsTrue_ModelBindsAndValidatesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
var binders = new IModelBinder[]
|
||||
var binderProviders = new IModelBinderProvider[]
|
||||
{
|
||||
new SimpleTypeModelBinder(),
|
||||
new MutableObjectModelBinder()
|
||||
new SimpleTypeModelBinderProvider(),
|
||||
new ComplexTypeModelBinderProvider(),
|
||||
};
|
||||
|
||||
var validator = new DataAnnotationsModelValidatorProvider(
|
||||
|
|
@ -220,7 +221,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
"",
|
||||
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
||||
metadataProvider,
|
||||
GetCompositeBinder(binders),
|
||||
GetModelBinderFactory(binderProviders),
|
||||
valueProvider,
|
||||
new List<IInputFormatter>(),
|
||||
new DefaultObjectValidator(metadataProvider, new ValidatorCache()),
|
||||
|
|
@ -250,7 +251,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
string.Empty,
|
||||
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
||||
metadataProvider,
|
||||
GetCompositeBinder(binder),
|
||||
GetModelBinderFactory(binder),
|
||||
Mock.Of<IValueProvider>(),
|
||||
new List<IInputFormatter>(),
|
||||
new Mock<IObjectModelValidator>(MockBehavior.Strict).Object,
|
||||
|
|
@ -268,10 +269,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public async Task TryUpdateModel_UsingIncludeExpressionOverload_ReturnsTrue_ModelBindsAndValidatesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
var binders = new IModelBinder[]
|
||||
var binderProviders = new IModelBinderProvider[]
|
||||
{
|
||||
new SimpleTypeModelBinder(),
|
||||
new MutableObjectModelBinder()
|
||||
new SimpleTypeModelBinderProvider(),
|
||||
new ComplexTypeModelBinderProvider(),
|
||||
};
|
||||
|
||||
var validator = new DataAnnotationsModelValidatorProvider(
|
||||
|
|
@ -302,7 +303,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
"",
|
||||
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
||||
TestModelMetadataProvider.CreateDefaultProvider(),
|
||||
GetCompositeBinder(binders),
|
||||
GetModelBinderFactory(binderProviders),
|
||||
valueProvider,
|
||||
new List<IInputFormatter>(),
|
||||
new DefaultObjectValidator(metadataProvider, new ValidatorCache()),
|
||||
|
|
@ -321,10 +322,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public async Task TryUpdateModel_UsingDefaultIncludeOverload_IncludesAllProperties()
|
||||
{
|
||||
// Arrange
|
||||
var binders = new IModelBinder[]
|
||||
var binderProviders = new IModelBinderProvider[]
|
||||
{
|
||||
new SimpleTypeModelBinder(),
|
||||
new MutableObjectModelBinder()
|
||||
new SimpleTypeModelBinderProvider(),
|
||||
new ComplexTypeModelBinderProvider(),
|
||||
};
|
||||
|
||||
var validator = new DataAnnotationsModelValidatorProvider(
|
||||
|
|
@ -355,7 +356,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
"",
|
||||
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
||||
metadataProvider,
|
||||
GetCompositeBinder(binders),
|
||||
GetModelBinderFactory(binderProviders),
|
||||
valueProvider,
|
||||
new List<IInputFormatter>(),
|
||||
new DefaultObjectValidator(metadataProvider, new ValidatorCache()),
|
||||
|
|
@ -506,7 +507,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
prefix: "",
|
||||
actionContext: new ActionContext() { HttpContext = new DefaultHttpContext() },
|
||||
metadataProvider: metadataProvider,
|
||||
modelBinder: GetCompositeBinder(binder),
|
||||
modelBinderFactory: GetModelBinderFactory(binder),
|
||||
valueProvider: Mock.Of<IValueProvider>(),
|
||||
inputFormatters: new List<IInputFormatter>(),
|
||||
objectModelValidator: new Mock<IObjectModelValidator>(MockBehavior.Strict).Object,
|
||||
|
|
@ -524,10 +525,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public async Task TryUpdateModelNonGeneric_PredicateOverload_ReturnsTrue_ModelBindsAndValidatesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
var binders = new IModelBinder[]
|
||||
var binderProviders = new IModelBinderProvider[]
|
||||
{
|
||||
new SimpleTypeModelBinder(),
|
||||
new MutableObjectModelBinder()
|
||||
new SimpleTypeModelBinderProvider(),
|
||||
new ComplexTypeModelBinderProvider(),
|
||||
};
|
||||
|
||||
var validator = new DataAnnotationsModelValidatorProvider(
|
||||
|
|
@ -563,7 +564,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
"",
|
||||
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
||||
metadataProvider,
|
||||
GetCompositeBinder(binders),
|
||||
GetModelBinderFactory(binderProviders),
|
||||
valueProvider,
|
||||
new List<IInputFormatter>(),
|
||||
new DefaultObjectValidator(metadataProvider, new ValidatorCache()),
|
||||
|
|
@ -595,7 +596,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
prefix: "",
|
||||
actionContext: new ActionContext() { HttpContext = new DefaultHttpContext() },
|
||||
metadataProvider: metadataProvider,
|
||||
modelBinder: GetCompositeBinder(binder.Object),
|
||||
modelBinderFactory: GetModelBinderFactory(binder.Object),
|
||||
valueProvider: Mock.Of<IValueProvider>(),
|
||||
inputFormatters: new List<IInputFormatter>(),
|
||||
objectModelValidator: new Mock<IObjectModelValidator>(MockBehavior.Strict).Object,
|
||||
|
|
@ -610,10 +611,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public async Task TryUpdateModelNonGeneric_ModelTypeOverload_ReturnsTrue_IfModelBindsAndValidatesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
var binders = new IModelBinder[]
|
||||
var binderProviders = new IModelBinderProvider[]
|
||||
{
|
||||
new SimpleTypeModelBinder(),
|
||||
new MutableObjectModelBinder()
|
||||
new SimpleTypeModelBinderProvider(),
|
||||
new ComplexTypeModelBinderProvider(),
|
||||
};
|
||||
|
||||
var validator = new DataAnnotationsModelValidatorProvider(
|
||||
|
|
@ -637,7 +638,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
"",
|
||||
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
||||
TestModelMetadataProvider.CreateDefaultProvider(),
|
||||
GetCompositeBinder(binders),
|
||||
GetModelBinderFactory(binderProviders),
|
||||
valueProvider,
|
||||
new List<IInputFormatter>(),
|
||||
new DefaultObjectValidator(metadataProvider, new ValidatorCache()),
|
||||
|
|
@ -666,7 +667,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
"",
|
||||
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
||||
metadataProvider,
|
||||
GetCompositeBinder(binder.Object),
|
||||
GetModelBinderFactory(binder.Object),
|
||||
Mock.Of<IValueProvider>(),
|
||||
new List<IInputFormatter>(),
|
||||
new DefaultObjectValidator(metadataProvider, new ValidatorCache()),
|
||||
|
|
@ -816,9 +817,19 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
}
|
||||
}
|
||||
|
||||
private static IModelBinder GetCompositeBinder(params IModelBinder[] binders)
|
||||
public static ModelBinderFactory GetModelBinderFactory(IModelBinder binder)
|
||||
{
|
||||
return new CompositeModelBinder(binders);
|
||||
var binderProvider = new Mock<IModelBinderProvider>();
|
||||
binderProvider
|
||||
.Setup(p => p.GetBinder(It.IsAny<ModelBinderProviderContext>()))
|
||||
.Returns(binder);
|
||||
|
||||
return TestModelBinderFactory.Create(binderProvider.Object);
|
||||
}
|
||||
|
||||
private static ModelBinderFactory GetModelBinderFactory(params IModelBinderProvider[] providers)
|
||||
{
|
||||
return TestModelBinderFactory.CreateDefault(providers);
|
||||
}
|
||||
|
||||
public class User
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
{
|
||||
public sealed class SimpleValueProvider : Dictionary<string, object>, IValueProvider
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ using System.Diagnostics;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
{
|
||||
public class StubModelBinder : IModelBinder
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright (c) .NET Foundation. 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
{
|
||||
public class TestModelBinderProviderContext : ModelBinderProviderContext
|
||||
{
|
||||
// Has to be internal because TestModelMetadataProvider is 'shared' code.
|
||||
internal static readonly TestModelMetadataProvider CachedMetadataProvider = new TestModelMetadataProvider();
|
||||
|
||||
private readonly List<Func<ModelMetadata, IModelBinder>> _binderCreators =
|
||||
new List<Func<ModelMetadata, IModelBinder>>();
|
||||
|
||||
public TestModelBinderProviderContext(Type modelType)
|
||||
{
|
||||
Metadata = CachedMetadataProvider.GetMetadataForType(modelType);
|
||||
MetadataProvider = CachedMetadataProvider;
|
||||
BindingInfo = new BindingInfo()
|
||||
{
|
||||
BinderModelName = Metadata.BinderModelName,
|
||||
BinderType = Metadata.BinderType,
|
||||
BindingSource = Metadata.BindingSource,
|
||||
PropertyBindingPredicateProvider = Metadata.PropertyBindingPredicateProvider,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
public TestModelBinderProviderContext(ModelMetadata metadata, BindingInfo bindingInfo)
|
||||
{
|
||||
Metadata = metadata;
|
||||
BindingInfo = bindingInfo;
|
||||
|
||||
MetadataProvider = CachedMetadataProvider;
|
||||
}
|
||||
|
||||
public override BindingInfo BindingInfo { get; }
|
||||
|
||||
public override ModelMetadata Metadata { get; }
|
||||
|
||||
public override IModelMetadataProvider MetadataProvider { get; }
|
||||
|
||||
public override IModelBinder CreateBinder(ModelMetadata metadata)
|
||||
{
|
||||
foreach (var creator in _binderCreators)
|
||||
{
|
||||
var result = creator(metadata);
|
||||
if (result != null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void OnCreatingBinder(Func<ModelMetadata, IModelBinder> binderCreator)
|
||||
{
|
||||
_binderCreators.Add(binderCreator);
|
||||
}
|
||||
|
||||
public void OnCreatingBinder(ModelMetadata metadata, Func<IModelBinder> binderCreator)
|
||||
{
|
||||
_binderCreators.Add((m) => m.Equals(metadata) ? binderCreator() : null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -73,7 +73,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, operationContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(null, modelBindingResult);
|
||||
Assert.False(modelBindingResult.Value.IsModelSet);
|
||||
|
||||
// ModelState (not set unless inner binder sets it)
|
||||
Assert.True(modelState.IsValid);
|
||||
|
|
@ -110,7 +110,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
// Assert
|
||||
|
||||
// ModelBindingResult
|
||||
Assert.Equal(null, modelBindingResult);
|
||||
Assert.False(modelBindingResult.Value.IsModelSet);
|
||||
|
||||
// ModelState
|
||||
Assert.True(modelState.IsValid);
|
||||
|
|
@ -146,7 +146,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
// Assert
|
||||
|
||||
// ModelBindingResult
|
||||
Assert.Equal(null, modelBindingResult);
|
||||
Assert.False(modelBindingResult.Value.IsModelSet);
|
||||
|
||||
// ModelState
|
||||
Assert.True(modelState.IsValid);
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
// Assert
|
||||
|
||||
// ModelBindingResult
|
||||
Assert.Equal(default(ModelBindingResult), modelBindingResult);
|
||||
Assert.False(modelBindingResult.IsModelSet);
|
||||
|
||||
// ModelState
|
||||
Assert.True(modelState.IsValid);
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue