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 System;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
using Microsoft.Extensions.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A key type which identifies a <see cref="ModelMetadata"/>.
|
/// A key type which identifies a <see cref="ModelMetadata"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public struct ModelMetadataIdentity
|
public struct ModelMetadataIdentity : IEquatable<ModelMetadataIdentity>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a <see cref="ModelMetadataIdentity"/> for the provided model <see cref="Type"/>.
|
/// 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.
|
/// the current instance represents a type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; private set; }
|
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>
|
/// </summary>
|
||||||
public abstract string BinderModelName { get; set; }
|
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>
|
/// <summary>
|
||||||
/// Gets or sets a value which represents the <see cref="ModelBinding.BindingSource"/> associated with the
|
/// Gets or sets a value which represents the <see cref="ModelBinding.BindingSource"/> associated with the
|
||||||
/// <see cref="Model"/>.
|
/// <see cref="Model"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract BindingSource BindingSource { get; set; }
|
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>
|
/// <summary>
|
||||||
/// Gets or sets the name of the current field being bound.
|
/// Gets or sets the name of the current field being bound.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
/// A metadata representation of a model type, property or parameter.
|
/// A metadata representation of a model type, property or parameter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DebuggerDisplay("{DebuggerToString(),nq}")]
|
[DebuggerDisplay("{DebuggerToString(),nq}")]
|
||||||
public abstract class ModelMetadata
|
public abstract class ModelMetadata : IEquatable<ModelMetadata>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The default value of <see cref="ModelMetadata.Order"/>.
|
/// The default value of <see cref="ModelMetadata.Order"/>.
|
||||||
|
|
@ -378,6 +378,36 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
return DisplayName ?? PropertyName ?? ModelType.Name;
|
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()
|
private void InitializeTypeInformation()
|
||||||
{
|
{
|
||||||
Debug.Assert(ModelType != null);
|
Debug.Assert(ModelType != null);
|
||||||
|
|
|
||||||
|
|
@ -35,11 +35,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IValueProvider ValueProvider { get; set; }
|
public IValueProvider ValueProvider { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the <see cref="IModelBinder"/> associated with this context.
|
|
||||||
/// </summary>
|
|
||||||
public IModelBinder ModelBinder { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the <see cref="IModelMetadataProvider"/> associated with this context.
|
/// Gets or sets the <see cref="IModelMetadataProvider"/> associated with this context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
{
|
{
|
||||||
private ControllerContext _controllerContext;
|
private ControllerContext _controllerContext;
|
||||||
private IModelMetadataProvider _metadataProvider;
|
private IModelMetadataProvider _metadataProvider;
|
||||||
|
private IModelBinderFactory _modelBinderFactory;
|
||||||
private IObjectModelValidator _objectValidator;
|
private IObjectModelValidator _objectValidator;
|
||||||
private IUrlHelper _url;
|
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>
|
/// <summary>
|
||||||
/// Gets or sets the <see cref="IUrlHelper"/>.
|
/// Gets or sets the <see cref="IUrlHelper"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -1139,7 +1165,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
prefix,
|
prefix,
|
||||||
ControllerContext,
|
ControllerContext,
|
||||||
MetadataProvider,
|
MetadataProvider,
|
||||||
new CompositeModelBinder(ControllerContext.ModelBinders),
|
ModelBinderFactory,
|
||||||
valueProvider,
|
valueProvider,
|
||||||
ControllerContext.InputFormatters,
|
ControllerContext.InputFormatters,
|
||||||
ObjectValidator,
|
ObjectValidator,
|
||||||
|
|
@ -1179,7 +1205,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
prefix,
|
prefix,
|
||||||
ControllerContext,
|
ControllerContext,
|
||||||
MetadataProvider,
|
MetadataProvider,
|
||||||
new CompositeModelBinder(ControllerContext.ModelBinders),
|
ModelBinderFactory,
|
||||||
new CompositeValueProvider(ControllerContext.ValueProviders),
|
new CompositeValueProvider(ControllerContext.ValueProviders),
|
||||||
ControllerContext.InputFormatters,
|
ControllerContext.InputFormatters,
|
||||||
ObjectValidator,
|
ObjectValidator,
|
||||||
|
|
@ -1219,7 +1245,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
prefix,
|
prefix,
|
||||||
ControllerContext,
|
ControllerContext,
|
||||||
MetadataProvider,
|
MetadataProvider,
|
||||||
new CompositeModelBinder(ControllerContext.ModelBinders),
|
ModelBinderFactory,
|
||||||
new CompositeValueProvider(ControllerContext.ValueProviders),
|
new CompositeValueProvider(ControllerContext.ValueProviders),
|
||||||
ControllerContext.InputFormatters,
|
ControllerContext.InputFormatters,
|
||||||
ObjectValidator,
|
ObjectValidator,
|
||||||
|
|
@ -1267,7 +1293,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
prefix,
|
prefix,
|
||||||
ControllerContext,
|
ControllerContext,
|
||||||
MetadataProvider,
|
MetadataProvider,
|
||||||
new CompositeModelBinder(ControllerContext.ModelBinders),
|
ModelBinderFactory,
|
||||||
valueProvider,
|
valueProvider,
|
||||||
ControllerContext.InputFormatters,
|
ControllerContext.InputFormatters,
|
||||||
ObjectValidator,
|
ObjectValidator,
|
||||||
|
|
@ -1314,7 +1340,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
prefix,
|
prefix,
|
||||||
ControllerContext,
|
ControllerContext,
|
||||||
MetadataProvider,
|
MetadataProvider,
|
||||||
new CompositeModelBinder(ControllerContext.ModelBinders),
|
ModelBinderFactory,
|
||||||
valueProvider,
|
valueProvider,
|
||||||
ControllerContext.InputFormatters,
|
ControllerContext.InputFormatters,
|
||||||
ObjectValidator,
|
ObjectValidator,
|
||||||
|
|
@ -1353,7 +1379,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
prefix,
|
prefix,
|
||||||
ControllerContext,
|
ControllerContext,
|
||||||
MetadataProvider,
|
MetadataProvider,
|
||||||
new CompositeModelBinder(ControllerContext.ModelBinders),
|
ModelBinderFactory,
|
||||||
new CompositeValueProvider(ControllerContext.ValueProviders),
|
new CompositeValueProvider(ControllerContext.ValueProviders),
|
||||||
ControllerContext.InputFormatters,
|
ControllerContext.InputFormatters,
|
||||||
ObjectValidator,
|
ObjectValidator,
|
||||||
|
|
@ -1405,7 +1431,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
prefix,
|
prefix,
|
||||||
ControllerContext,
|
ControllerContext,
|
||||||
MetadataProvider,
|
MetadataProvider,
|
||||||
new CompositeModelBinder(ControllerContext.ModelBinders),
|
ModelBinderFactory,
|
||||||
valueProvider,
|
valueProvider,
|
||||||
ControllerContext.InputFormatters,
|
ControllerContext.InputFormatters,
|
||||||
ObjectValidator,
|
ObjectValidator,
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
public class ControllerContext : ActionContext
|
public class ControllerContext : ActionContext
|
||||||
{
|
{
|
||||||
private FormatterCollection<IInputFormatter> _inputFormatters;
|
private FormatterCollection<IInputFormatter> _inputFormatters;
|
||||||
private IList<IModelBinder> _modelBinders;
|
|
||||||
private IList<IModelValidatorProvider> _validatorProviders;
|
private IList<IModelValidatorProvider> _validatorProviders;
|
||||||
private IList<IValueProvider> _valueProviders;
|
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>
|
/// <summary>
|
||||||
/// Gets or sets the list of <see cref="IModelValidatorProvider"/> instances for the current request.
|
/// Gets or sets the list of <see cref="IModelValidatorProvider"/> instances for the current request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
var options = serviceProvider.GetRequiredService<IOptions<MvcOptions>>().Value;
|
var options = serviceProvider.GetRequiredService<IOptions<MvcOptions>>().Value;
|
||||||
return new DefaultCompositeMetadataDetailsProvider(options.ModelMetadataDetailsProviders);
|
return new DefaultCompositeMetadataDetailsProvider(options.ModelMetadataDetailsProviders);
|
||||||
}));
|
}));
|
||||||
|
services.TryAddSingleton<IModelBinderFactory, ModelBinderFactory>();
|
||||||
services.TryAddSingleton<IObjectModelValidator, DefaultObjectValidator>();
|
services.TryAddSingleton<IObjectModelValidator, DefaultObjectValidator>();
|
||||||
services.TryAddSingleton<ValidatorCache>();
|
services.TryAddSingleton<ValidatorCache>();
|
||||||
services.TryAddSingleton<ClientValidatorCache>();
|
services.TryAddSingleton<ClientValidatorCache>();
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ControllerActionDescriptor descriptor,
|
ControllerActionDescriptor descriptor,
|
||||||
IReadOnlyList<IInputFormatter> inputFormatters,
|
IReadOnlyList<IInputFormatter> inputFormatters,
|
||||||
IControllerActionArgumentBinder argumentBinder,
|
IControllerActionArgumentBinder argumentBinder,
|
||||||
IReadOnlyList<IModelBinder> modelBinders,
|
|
||||||
IReadOnlyList<IModelValidatorProvider> modelValidatorProviders,
|
IReadOnlyList<IModelValidatorProvider> modelValidatorProviders,
|
||||||
IReadOnlyList<IValueProviderFactory> valueProviderFactories,
|
IReadOnlyList<IValueProviderFactory> valueProviderFactories,
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
|
|
@ -42,7 +41,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
actionContext,
|
actionContext,
|
||||||
controllerActionInvokerCache,
|
controllerActionInvokerCache,
|
||||||
inputFormatters,
|
inputFormatters,
|
||||||
modelBinders,
|
|
||||||
modelValidatorProviders,
|
modelValidatorProviders,
|
||||||
valueProviderFactories,
|
valueProviderFactories,
|
||||||
logger,
|
logger,
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
private readonly IControllerFactory _controllerFactory;
|
private readonly IControllerFactory _controllerFactory;
|
||||||
private readonly ControllerActionInvokerCache _controllerActionInvokerCache;
|
private readonly ControllerActionInvokerCache _controllerActionInvokerCache;
|
||||||
private readonly IReadOnlyList<IInputFormatter> _inputFormatters;
|
private readonly IReadOnlyList<IInputFormatter> _inputFormatters;
|
||||||
private readonly IReadOnlyList<IModelBinder> _modelBinders;
|
|
||||||
private readonly IReadOnlyList<IModelValidatorProvider> _modelValidatorProviders;
|
private readonly IReadOnlyList<IModelValidatorProvider> _modelValidatorProviders;
|
||||||
private readonly IReadOnlyList<IValueProviderFactory> _valueProviderFactories;
|
private readonly IReadOnlyList<IValueProviderFactory> _valueProviderFactories;
|
||||||
private readonly int _maxModelValidationErrors;
|
private readonly int _maxModelValidationErrors;
|
||||||
|
|
@ -40,7 +39,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
_controllerActionInvokerCache = controllerActionInvokerCache;
|
_controllerActionInvokerCache = controllerActionInvokerCache;
|
||||||
_argumentBinder = argumentBinder;
|
_argumentBinder = argumentBinder;
|
||||||
_inputFormatters = optionsAccessor.Value.InputFormatters.ToArray();
|
_inputFormatters = optionsAccessor.Value.InputFormatters.ToArray();
|
||||||
_modelBinders = optionsAccessor.Value.ModelBinders.ToArray();
|
|
||||||
_modelValidatorProviders = optionsAccessor.Value.ModelValidatorProviders.ToArray();
|
_modelValidatorProviders = optionsAccessor.Value.ModelValidatorProviders.ToArray();
|
||||||
_valueProviderFactories = optionsAccessor.Value.ValueProviderFactories.ToArray();
|
_valueProviderFactories = optionsAccessor.Value.ValueProviderFactories.ToArray();
|
||||||
_maxModelValidationErrors = optionsAccessor.Value.MaxModelValidationErrors;
|
_maxModelValidationErrors = optionsAccessor.Value.MaxModelValidationErrors;
|
||||||
|
|
@ -72,7 +70,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
actionDescriptor,
|
actionDescriptor,
|
||||||
_inputFormatters,
|
_inputFormatters,
|
||||||
_argumentBinder,
|
_argumentBinder,
|
||||||
_modelBinders,
|
|
||||||
_modelValidatorProviders,
|
_modelValidatorProviders,
|
||||||
_valueProviderFactories,
|
_valueProviderFactories,
|
||||||
_logger,
|
_logger,
|
||||||
|
|
|
||||||
|
|
@ -24,14 +24,17 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
typeof(ControllerArgumentBinder).GetTypeInfo().GetDeclaredMethod(
|
typeof(ControllerArgumentBinder).GetTypeInfo().GetDeclaredMethod(
|
||||||
nameof(CallPropertyAddRange));
|
nameof(CallPropertyAddRange));
|
||||||
|
|
||||||
|
private readonly IModelBinderFactory _modelBinderFactory;
|
||||||
private readonly IModelMetadataProvider _modelMetadataProvider;
|
private readonly IModelMetadataProvider _modelMetadataProvider;
|
||||||
private readonly IObjectModelValidator _validator;
|
private readonly IObjectModelValidator _validator;
|
||||||
|
|
||||||
public ControllerArgumentBinder(
|
public ControllerArgumentBinder(
|
||||||
IModelMetadataProvider modelMetadataProvider,
|
IModelMetadataProvider modelMetadataProvider,
|
||||||
|
IModelBinderFactory modelBinderFactory,
|
||||||
IObjectModelValidator validator)
|
IObjectModelValidator validator)
|
||||||
{
|
{
|
||||||
_modelMetadataProvider = modelMetadataProvider;
|
_modelMetadataProvider = modelMetadataProvider;
|
||||||
|
_modelBinderFactory = modelBinderFactory;
|
||||||
_validator = validator;
|
_validator = validator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,7 +121,31 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
parameter.BindingInfo,
|
parameter.BindingInfo,
|
||||||
parameter.Name);
|
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;
|
var modelBindingResult = modelBindingContext.Result;
|
||||||
if (modelBindingResult != null && modelBindingResult.Value.IsModelSet)
|
if (modelBindingResult != null && modelBindingResult.Value.IsModelSet)
|
||||||
{
|
{
|
||||||
|
|
@ -241,7 +268,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
{
|
{
|
||||||
ActionContext = context,
|
ActionContext = context,
|
||||||
InputFormatters = context.InputFormatters,
|
InputFormatters = context.InputFormatters,
|
||||||
ModelBinder = new CompositeModelBinder(context.ModelBinders),
|
|
||||||
ValidatorProvider = new CompositeModelValidatorProvider(context.ValidatorProviders),
|
ValidatorProvider = new CompositeModelValidatorProvider(context.ValidatorProviders),
|
||||||
MetadataProvider = _modelMetadataProvider,
|
MetadataProvider = _modelMetadataProvider,
|
||||||
ValueProvider = new CompositeValueProvider(context.ValueProviders),
|
ValueProvider = new CompositeValueProvider(context.ValueProviders),
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DefaultModelBindingContext : ModelBindingContext
|
public class DefaultModelBindingContext : ModelBindingContext
|
||||||
{
|
{
|
||||||
|
private OperationBindingContext _operationBindingContext;
|
||||||
|
|
||||||
private State _state;
|
private State _state;
|
||||||
private readonly Stack<State> _stack = new Stack<State>();
|
private readonly Stack<State> _stack = new Stack<State>();
|
||||||
|
|
||||||
|
|
@ -57,17 +59,19 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
var propertyPredicateProvider =
|
var propertyPredicateProvider =
|
||||||
bindingInfo?.PropertyBindingPredicateProvider ?? metadata.PropertyBindingPredicateProvider;
|
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()
|
return new DefaultModelBindingContext()
|
||||||
{
|
{
|
||||||
BinderModelName = binderModelName,
|
BinderModelName = binderModelName,
|
||||||
BindingSource = bindingInfo?.BindingSource ?? metadata.BindingSource,
|
BindingSource = bindingSource,
|
||||||
BinderType = bindingInfo?.BinderType ?? metadata.BinderType,
|
|
||||||
PropertyFilter = propertyPredicateProvider?.PropertyFilter,
|
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.
|
// Because this is the top-level context, FieldName and ModelName should be the same.
|
||||||
FieldName = binderModelName ?? modelName,
|
FieldName = binderModelName ?? modelName,
|
||||||
ModelName = binderModelName ?? modelName,
|
ModelName = binderModelName ?? modelName,
|
||||||
|
|
@ -76,7 +80,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
ModelMetadata = metadata,
|
ModelMetadata = metadata,
|
||||||
ModelState = operationBindingContext.ActionContext.ModelState,
|
ModelState = operationBindingContext.ActionContext.ModelState,
|
||||||
OperationBindingContext = operationBindingContext,
|
OperationBindingContext = operationBindingContext,
|
||||||
ValueProvider = operationBindingContext.ValueProvider,
|
ValueProvider = valueProvider,
|
||||||
|
|
||||||
ValidationState = new ValidationStateDictionary(),
|
ValidationState = new ValidationStateDictionary(),
|
||||||
};
|
};
|
||||||
|
|
@ -106,16 +110,21 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
|
|
||||||
var scope = EnterNestedScope();
|
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;
|
Model = model;
|
||||||
ModelMetadata = modelMetadata;
|
ModelMetadata = modelMetadata;
|
||||||
ModelName = modelName;
|
ModelName = modelName;
|
||||||
FieldName = fieldName;
|
FieldName = fieldName;
|
||||||
BinderModelName = modelMetadata.BinderModelName;
|
BinderModelName = modelMetadata.BinderModelName;
|
||||||
BinderType = modelMetadata.BinderType;
|
|
||||||
BindingSource = modelMetadata.BindingSource;
|
BindingSource = modelMetadata.BindingSource;
|
||||||
PropertyFilter = modelMetadata.PropertyBindingPredicateProvider?.PropertyFilter;
|
PropertyFilter = modelMetadata.PropertyBindingPredicateProvider?.PropertyFilter;
|
||||||
|
|
||||||
FallbackToEmptyPrefix = false;
|
|
||||||
IsTopLevelObject = false;
|
IsTopLevelObject = false;
|
||||||
|
|
||||||
return scope;
|
return scope;
|
||||||
|
|
@ -140,14 +149,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override OperationBindingContext OperationBindingContext
|
public override OperationBindingContext OperationBindingContext
|
||||||
{
|
{
|
||||||
get { return _state.OperationBindingContext; }
|
get { return _operationBindingContext; }
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value == null)
|
if (value == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(value));
|
throw new ArgumentNullException(nameof(value));
|
||||||
}
|
}
|
||||||
_state.OperationBindingContext = value;
|
_operationBindingContext = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -231,20 +240,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
set { _state.BindingSource = value; }
|
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 />
|
/// <inheritdoc />
|
||||||
public override bool IsTopLevelObject
|
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
|
private struct State
|
||||||
{
|
{
|
||||||
public OperationBindingContext OperationBindingContext;
|
|
||||||
public string FieldName;
|
public string FieldName;
|
||||||
public object Model;
|
public object Model;
|
||||||
public ModelMetadata ModelMetadata;
|
public ModelMetadata ModelMetadata;
|
||||||
|
|
@ -313,8 +323,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
|
|
||||||
public string BinderModelName;
|
public string BinderModelName;
|
||||||
public BindingSource BindingSource;
|
public BindingSource BindingSource;
|
||||||
public Type BinderType;
|
|
||||||
public bool FallbackToEmptyPrefix;
|
|
||||||
public bool IsTopLevelObject;
|
public bool IsTopLevelObject;
|
||||||
|
|
||||||
public ModelBindingResult? Result;
|
public ModelBindingResult? Result;
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
{
|
{
|
||||||
private readonly ControllerActionInvokerCache _controllerActionInvokerCache;
|
private readonly ControllerActionInvokerCache _controllerActionInvokerCache;
|
||||||
private readonly IReadOnlyList<IInputFormatter> _inputFormatters;
|
private readonly IReadOnlyList<IInputFormatter> _inputFormatters;
|
||||||
private readonly IReadOnlyList<IModelBinder> _modelBinders;
|
|
||||||
private readonly IReadOnlyList<IModelValidatorProvider> _modelValidatorProviders;
|
private readonly IReadOnlyList<IModelValidatorProvider> _modelValidatorProviders;
|
||||||
private readonly IReadOnlyList<IValueProviderFactory> _valueProviderFactories;
|
private readonly IReadOnlyList<IValueProviderFactory> _valueProviderFactories;
|
||||||
private readonly DiagnosticSource _diagnosticSource;
|
private readonly DiagnosticSource _diagnosticSource;
|
||||||
|
|
@ -47,7 +46,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ActionContext actionContext,
|
ActionContext actionContext,
|
||||||
ControllerActionInvokerCache controllerActionInvokerCache,
|
ControllerActionInvokerCache controllerActionInvokerCache,
|
||||||
IReadOnlyList<IInputFormatter> inputFormatters,
|
IReadOnlyList<IInputFormatter> inputFormatters,
|
||||||
IReadOnlyList<IModelBinder> modelBinders,
|
|
||||||
IReadOnlyList<IModelValidatorProvider> modelValidatorProviders,
|
IReadOnlyList<IModelValidatorProvider> modelValidatorProviders,
|
||||||
IReadOnlyList<IValueProviderFactory> valueProviderFactories,
|
IReadOnlyList<IValueProviderFactory> valueProviderFactories,
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
|
|
@ -69,11 +67,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
throw new ArgumentNullException(nameof(inputFormatters));
|
throw new ArgumentNullException(nameof(inputFormatters));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modelBinders == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(modelBinders));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (modelValidatorProviders == null)
|
if (modelValidatorProviders == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(modelValidatorProviders));
|
throw new ArgumentNullException(nameof(modelValidatorProviders));
|
||||||
|
|
@ -98,7 +91,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
_controllerActionInvokerCache = controllerActionInvokerCache;
|
_controllerActionInvokerCache = controllerActionInvokerCache;
|
||||||
_inputFormatters = inputFormatters;
|
_inputFormatters = inputFormatters;
|
||||||
_modelBinders = modelBinders;
|
|
||||||
_modelValidatorProviders = modelValidatorProviders;
|
_modelValidatorProviders = modelValidatorProviders;
|
||||||
_valueProviderFactories = valueProviderFactories;
|
_valueProviderFactories = valueProviderFactories;
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
|
|
@ -356,7 +348,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
// binding.
|
// binding.
|
||||||
Context.InputFormatters = new FormatterCollection<IInputFormatter>(
|
Context.InputFormatters = new FormatterCollection<IInputFormatter>(
|
||||||
new CopyOnWriteList<IInputFormatter>(_inputFormatters));
|
new CopyOnWriteList<IInputFormatter>(_inputFormatters));
|
||||||
Context.ModelBinders = new CopyOnWriteList<IModelBinder>(_modelBinders);
|
|
||||||
Context.ValidatorProviders = new CopyOnWriteList<IModelValidatorProvider>(_modelValidatorProviders);
|
Context.ValidatorProviders = new CopyOnWriteList<IModelValidatorProvider>(_modelValidatorProviders);
|
||||||
|
|
||||||
var valueProviders = new List<IValueProvider>();
|
var valueProviders = new List<IValueProvider>();
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,8 @@ using System.Threading;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc.Core;
|
using Microsoft.AspNetCore.Mvc.Core;
|
||||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
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.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
|
@ -37,17 +36,20 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
messageProvider.ValueMustBeANumberAccessor = Resources.FormatHtmlGeneration_ValueMustBeNumber;
|
messageProvider.ValueMustBeANumberAccessor = Resources.FormatHtmlGeneration_ValueMustBeNumber;
|
||||||
|
|
||||||
// Set up ModelBinding
|
// Set up ModelBinding
|
||||||
options.ModelBinders.Add(new BinderTypeBasedModelBinder());
|
options.ModelBinderProviders.Add(new BinderTypeModelBinderProvider());
|
||||||
options.ModelBinders.Add(new ServicesModelBinder());
|
options.ModelBinderProviders.Add(new ServicesModelBinderProvider());
|
||||||
options.ModelBinders.Add(new BodyModelBinder(readerFactory));
|
options.ModelBinderProviders.Add(new BodyModelBinderProvider(readerFactory));
|
||||||
options.ModelBinders.Add(new HeaderModelBinder());
|
options.ModelBinderProviders.Add(new HeaderModelBinderProvider());
|
||||||
options.ModelBinders.Add(new SimpleTypeModelBinder());
|
options.ModelBinderProviders.Add(new SimpleTypeModelBinderProvider());
|
||||||
options.ModelBinders.Add(new CancellationTokenModelBinder());
|
options.ModelBinderProviders.Add(new CancellationTokenModelBinderProvider());
|
||||||
options.ModelBinders.Add(new ByteArrayModelBinder());
|
options.ModelBinderProviders.Add(new ByteArrayModelBinderProvider());
|
||||||
options.ModelBinders.Add(new FormFileModelBinder());
|
options.ModelBinderProviders.Add(new FormFileModelBinderProvider());
|
||||||
options.ModelBinders.Add(new FormCollectionModelBinder());
|
options.ModelBinderProviders.Add(new FormCollectionModelBinderProvider());
|
||||||
options.ModelBinders.Add(new GenericModelBinder());
|
options.ModelBinderProviders.Add(new KeyValuePairModelBinderProvider());
|
||||||
options.ModelBinders.Add(new MutableObjectModelBinder());
|
options.ModelBinderProviders.Add(new DictionaryModelBinderProvider());
|
||||||
|
options.ModelBinderProviders.Add(new ArrayModelBinderProvider());
|
||||||
|
options.ModelBinderProviders.Add(new CollectionModelBinderProvider());
|
||||||
|
options.ModelBinderProviders.Add(new ComplexTypeModelBinderProvider());
|
||||||
|
|
||||||
// Set up filters
|
// Set up filters
|
||||||
options.Filters.Add(new UnsupportedContentTypeFilter());
|
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.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <see cref="IModelBinder"/> implementation for binding array values.
|
/// <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>
|
/// <typeparam name="TElement">Type of elements in the array.</typeparam>
|
||||||
public class ArrayModelBinder<TElement> : CollectionModelBinder<TElement>
|
public class ArrayModelBinder<TElement> : CollectionModelBinder<TElement>
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
public override Task BindModelAsync(ModelBindingContext bindingContext)
|
/// 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 />
|
/// <inheritdoc />
|
||||||
|
|
@ -62,8 +55,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(target));
|
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
|
// 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.Formatters;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An <see cref="IModelBinder"/> which binds models from the request body using an <see cref="IInputFormatter"/>
|
/// 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 />
|
/// <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>.
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
if (bindingContext == null)
|
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 System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ModelBinder to bind Byte Arrays.
|
/// ModelBinder to bind byte Arrays.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ByteArrayModelBinder : IModelBinder
|
public class ByteArrayModelBinder : IModelBinder
|
||||||
{
|
{
|
||||||
|
|
@ -20,16 +20,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
throw new ArgumentNullException(nameof(bindingContext));
|
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.
|
// Check for missing data case 1: There was no <input ... /> element containing this data.
|
||||||
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
|
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
|
||||||
if (valueProviderResult == ValueProviderResult.None)
|
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.ModelBinding.Validation;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <see cref="IModelBinder"/> implementation to bind models of type <see cref="CancellationToken"/>.
|
/// <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));
|
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.
|
||||||
// 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;
|
||||||
// DO NOT simplify this code by removing the cast.
|
bindingContext.ValidationState.Add(model, new ValidationStateEntry() { SuppressValidation = true });
|
||||||
var model = (object)bindingContext.OperationBindingContext.HttpContext.RequestAborted;
|
bindingContext.Result = ModelBindingResult.Success(bindingContext.ModelName, model);
|
||||||
bindingContext.ValidationState.Add(model, new ValidationStateEntry() { SuppressValidation = true });
|
|
||||||
bindingContext.Result = ModelBindingResult.Success(bindingContext.ModelName, model);
|
|
||||||
}
|
|
||||||
|
|
||||||
return TaskCache.CompletedTask;
|
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.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <see cref="IModelBinder"/> implementation for binding collection values.
|
/// <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>
|
/// <typeparam name="TElement">Type of elements in the collection.</typeparam>
|
||||||
public class CollectionModelBinder<TElement> : ICollectionModelBinder
|
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 />
|
/// <inheritdoc />
|
||||||
public virtual async Task BindModelAsync(ModelBindingContext bindingContext)
|
public virtual async Task BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
|
|
@ -28,8 +47,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
throw new ArgumentNullException(nameof(bindingContext));
|
throw new ArgumentNullException(nameof(bindingContext));
|
||||||
}
|
}
|
||||||
|
|
||||||
ModelBindingHelper.ValidateBindingContext(bindingContext);
|
|
||||||
|
|
||||||
var model = bindingContext.Model;
|
var model = bindingContext.Model;
|
||||||
if (!bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName))
|
if (!bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName))
|
||||||
{
|
{
|
||||||
|
|
@ -163,7 +180,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
modelName: bindingContext.ModelName,
|
modelName: bindingContext.ModelName,
|
||||||
model: null))
|
model: null))
|
||||||
{
|
{
|
||||||
await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(bindingContext);
|
await ElementBinder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
if (bindingContext.Result != null && bindingContext.Result.Value.IsModelSet)
|
if (bindingContext.Result != null && bindingContext.Result.Value.IsModelSet)
|
||||||
{
|
{
|
||||||
|
|
@ -224,7 +241,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
modelName: fullChildName,
|
modelName: fullChildName,
|
||||||
model: null))
|
model: null))
|
||||||
{
|
{
|
||||||
await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(bindingContext);
|
await ElementBinder.BindModelAsync(bindingContext);
|
||||||
result = bindingContext.Result;
|
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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <see cref="IModelBinder"/> implementation for binding complex values.
|
/// <see cref="IModelBinder"/> implementation for binding complex types.
|
||||||
/// </summary>
|
/// </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 />
|
/// <inheritdoc />
|
||||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
|
|
@ -21,12 +40,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
throw new ArgumentNullException(nameof(bindingContext));
|
throw new ArgumentNullException(nameof(bindingContext));
|
||||||
}
|
}
|
||||||
|
|
||||||
ModelBindingHelper.ValidateBindingContext(bindingContext);
|
|
||||||
if (!CanBindType(bindingContext.ModelMetadata))
|
|
||||||
{
|
|
||||||
return TaskCache.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!CanCreateModel(bindingContext))
|
if (!CanCreateModel(bindingContext))
|
||||||
{
|
{
|
||||||
return TaskCache.CompletedTask;
|
return TaskCache.CompletedTask;
|
||||||
|
|
@ -135,7 +148,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
/// </returns>
|
/// </returns>
|
||||||
protected virtual Task BindProperty(ModelBindingContext bindingContext)
|
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)
|
internal bool CanCreateModel(ModelBindingContext bindingContext)
|
||||||
|
|
@ -284,22 +298,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
return false;
|
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 for tests
|
||||||
internal static bool CanUpdatePropertyInternal(ModelMetadata propertyMetadata)
|
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.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <see cref="IModelBinder"/> implementation for binding dictionary values.
|
/// <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>
|
/// <typeparam name="TValue">Type of values in the dictionary.</typeparam>
|
||||||
public class DictionaryModelBinder<TKey, TValue> : CollectionModelBinder<KeyValuePair<TKey, TValue>>
|
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 />
|
/// <inheritdoc />
|
||||||
public override async Task BindModelAsync(ModelBindingContext bindingContext)
|
public override async Task BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
|
|
@ -66,8 +84,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider;
|
var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider;
|
||||||
var valueMetadata = metadataProvider.GetMetadataForType(typeof(TValue));
|
var valueMetadata = metadataProvider.GetMetadataForType(typeof(TValue));
|
||||||
|
|
||||||
var modelBinder = bindingContext.OperationBindingContext.ModelBinder;
|
|
||||||
|
|
||||||
var keyMappings = new Dictionary<string, TKey>(StringComparer.Ordinal);
|
var keyMappings = new Dictionary<string, TKey>(StringComparer.Ordinal);
|
||||||
foreach (var kvp in keys)
|
foreach (var kvp in keys)
|
||||||
{
|
{
|
||||||
|
|
@ -81,7 +97,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
modelName: kvp.Value,
|
modelName: kvp.Value,
|
||||||
model: null))
|
model: null))
|
||||||
{
|
{
|
||||||
await modelBinder.BindModelAsync(bindingContext);
|
await _valueBinder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
var valueResult = bindingContext.Result;
|
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.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <see cref="IModelBinder"/> implementation to bind form values to <see cref="IFormCollection"/>.
|
/// <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
|
public class FormCollectionModelBinder : IModelBinder
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
public async Task BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
if (bindingContext == null)
|
if (bindingContext == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(bindingContext));
|
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;
|
object model;
|
||||||
var request = bindingContext.OperationBindingContext.HttpContext.Request;
|
var request = bindingContext.OperationBindingContext.HttpContext.Request;
|
||||||
if (request.HasFormContentType)
|
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
|
#endif
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <see cref="IModelBinder"/> implementation to bind posted files to <see cref="IFormFile"/>.
|
/// <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
|
public class FormFileModelBinder : IModelBinder
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
public async Task BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
if (bindingContext == null)
|
if (bindingContext == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(bindingContext));
|
throw new ArgumentNullException(nameof(bindingContext));
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method is optimized to use cached tasks when possible and avoid allocating
|
var createFileCollection =
|
||||||
// using Task.FromResult or async state machines.
|
bindingContext.ModelType == typeof(IFormFileCollection) &&
|
||||||
|
|
||||||
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) &&
|
|
||||||
!bindingContext.ModelMetadata.IsReadOnly;
|
!bindingContext.ModelMetadata.IsReadOnly;
|
||||||
if (!createFileCollection && !ModelBindingHelper.CanGetCompatibleCollection<IFormFile>(bindingContext))
|
if (!createFileCollection && !ModelBindingHelper.CanGetCompatibleCollection<IFormFile>(bindingContext))
|
||||||
{
|
{
|
||||||
// Silently fail and stop other model binders running if unable to create an instance or use the
|
// Silently fail and stop other model binders running if unable to create an instance or use the
|
||||||
// current instance.
|
// current instance.
|
||||||
bindingContext.Result = ModelBindingResult.Failed(bindingContext.ModelName);
|
bindingContext.Result = ModelBindingResult.Failed(bindingContext.ModelName);
|
||||||
return TaskCache.CompletedTask;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ICollection<IFormFile> postedFiles;
|
ICollection<IFormFile> postedFiles;
|
||||||
|
|
@ -59,13 +49,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
postedFiles = ModelBindingHelper.GetCompatibleCollection<IFormFile>(bindingContext);
|
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).
|
// 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
|
// 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.
|
// 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.Http;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An <see cref="IModelBinder"/> which binds models from the request headers when a model
|
/// 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));
|
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;
|
var request = bindingContext.OperationBindingContext.HttpContext.Request;
|
||||||
|
|
||||||
// Property name can be null if the model metadata represents a type (rather than a property or parameter).
|
// Property name can be null if the model metadata represents a type (rather than a property or parameter).
|
||||||
var headerName = bindingContext.FieldName;
|
var headerName = bindingContext.FieldName;
|
||||||
|
|
||||||
object model;
|
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;
|
||||||
var value = request.Headers[headerName];
|
}
|
||||||
model = (string)value;
|
else if (ModelBindingHelper.CanGetCompatibleCollection<string>(bindingContext))
|
||||||
}
|
{
|
||||||
else
|
var values = request.Headers.GetCommaSeparatedValues(headerName);
|
||||||
{
|
model = GetCompatibleCollection(bindingContext, values);
|
||||||
var values = request.Headers.GetCommaSeparatedValues(headerName);
|
|
||||||
model = GetCompatibleCollection(bindingContext, values);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
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.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
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)
|
public async Task BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
if (bindingContext == null)
|
if (bindingContext == null)
|
||||||
|
|
@ -16,14 +46,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
throw new ArgumentNullException(nameof(bindingContext));
|
throw new ArgumentNullException(nameof(bindingContext));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bindingContext.ModelType != typeof(KeyValuePair<TKey, TValue>))
|
var keyResult = await TryBindStrongModel<TKey>(bindingContext, _keyBinder, "Key");
|
||||||
{
|
var valueResult = await TryBindStrongModel<TValue>(bindingContext, _valueBinder, "Value");
|
||||||
// This binder does not apply.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var keyResult = await TryBindStrongModel<TKey>(bindingContext, "Key");
|
|
||||||
var valueResult = await TryBindStrongModel<TValue>(bindingContext, "Value");
|
|
||||||
|
|
||||||
if (keyResult.IsModelSet && valueResult.IsModelSet)
|
if (keyResult.IsModelSet && valueResult.IsModelSet)
|
||||||
{
|
{
|
||||||
|
|
@ -70,6 +94,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
|
|
||||||
internal async Task<ModelBindingResult> TryBindStrongModel<TModel>(
|
internal async Task<ModelBindingResult> TryBindStrongModel<TModel>(
|
||||||
ModelBindingContext bindingContext,
|
ModelBindingContext bindingContext,
|
||||||
|
IModelBinder binder,
|
||||||
string propertyName)
|
string propertyName)
|
||||||
{
|
{
|
||||||
var propertyModelMetadata =
|
var propertyModelMetadata =
|
||||||
|
|
@ -83,9 +108,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
modelName: propertyModelName,
|
modelName: propertyModelName,
|
||||||
model: null))
|
model: null))
|
||||||
{
|
{
|
||||||
|
await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(
|
|
||||||
bindingContext);
|
|
||||||
var result = bindingContext.Result;
|
var result = bindingContext.Result;
|
||||||
if (result != null && result.Value.IsModelSet)
|
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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An <see cref="IModelBinder"/> which binds models from the request services when a model
|
/// 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));
|
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 requestServices = bindingContext.OperationBindingContext.HttpContext.RequestServices;
|
||||||
var model = requestServices.GetRequiredService(bindingContext.ModelType);
|
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 System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
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
|
public class SimpleTypeModelBinder : IModelBinder
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
if (bindingContext == null)
|
if (bindingContext == null)
|
||||||
|
|
@ -16,20 +20,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
throw new ArgumentNullException(nameof(bindingContext));
|
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);
|
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
|
||||||
if (valueProviderResult == ValueProviderResult.None)
|
if (valueProviderResult == ValueProviderResult.None)
|
||||||
{
|
{
|
||||||
// no entry
|
// no entry
|
||||||
|
bindingContext.Result = ModelBindingResult.Failed(bindingContext.ModelName);
|
||||||
return TaskCache.CompletedTask;
|
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
|
public static class ModelBindingHelper
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the specified <paramref name="model"/> instance using the specified <paramref name="modelBinder"/>
|
/// Updates the specified <paramref name="model"/> instance using the specified
|
||||||
/// and the specified <paramref name="valueProvider"/> and executes validation using the specified
|
/// <paramref name="modelBinderFactory"/> and the specified <paramref name="valueProvider"/> and executes
|
||||||
/// <paramref name="validatorProvider"/>.
|
/// validation using the specified <paramref name="validatorProvider"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TModel">The type of the model object.</typeparam>
|
/// <typeparam name="TModel">The type of the model object.</typeparam>
|
||||||
/// <param name="model">The model instance to update and validate.</param>
|
/// <param name="model">The model instance to update and validate.</param>
|
||||||
|
|
@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <param name="actionContext">The <see cref="ActionContext"/> for the current executing request.</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="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="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
|
||||||
/// <param name="inputFormatters">
|
/// <param name="inputFormatters">
|
||||||
/// The set of <see cref="IInputFormatter"/> instances for deserializing the body.
|
/// The set of <see cref="IInputFormatter"/> instances for deserializing the body.
|
||||||
|
|
@ -45,7 +45,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
string prefix,
|
string prefix,
|
||||||
ActionContext actionContext,
|
ActionContext actionContext,
|
||||||
IModelMetadataProvider metadataProvider,
|
IModelMetadataProvider metadataProvider,
|
||||||
IModelBinder modelBinder,
|
IModelBinderFactory modelBinderFactory,
|
||||||
IValueProvider valueProvider,
|
IValueProvider valueProvider,
|
||||||
IList<IInputFormatter> inputFormatters,
|
IList<IInputFormatter> inputFormatters,
|
||||||
IObjectModelValidator objectModelValidator,
|
IObjectModelValidator objectModelValidator,
|
||||||
|
|
@ -72,9 +72,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
throw new ArgumentNullException(nameof(metadataProvider));
|
throw new ArgumentNullException(nameof(metadataProvider));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modelBinder == null)
|
if (modelBinderFactory == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(modelBinder));
|
throw new ArgumentNullException(nameof(modelBinderFactory));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (valueProvider == null)
|
if (valueProvider == null)
|
||||||
|
|
@ -103,7 +103,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
prefix,
|
prefix,
|
||||||
actionContext,
|
actionContext,
|
||||||
metadataProvider,
|
metadataProvider,
|
||||||
modelBinder,
|
modelBinderFactory,
|
||||||
valueProvider,
|
valueProvider,
|
||||||
inputFormatters,
|
inputFormatters,
|
||||||
objectModelValidator,
|
objectModelValidator,
|
||||||
|
|
@ -112,7 +112,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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
|
/// and the specified <paramref name="valueProvider"/> and executes validation using the specified
|
||||||
/// <paramref name="validatorProvider"/>.
|
/// <paramref name="validatorProvider"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -122,7 +122,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <param name="actionContext">The <see cref="ActionContext"/> for the current executing request.</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="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="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
|
||||||
/// <param name="inputFormatters">
|
/// <param name="inputFormatters">
|
||||||
/// The set of <see cref="IInputFormatter"/> instances for deserializing the body.
|
/// The set of <see cref="IInputFormatter"/> instances for deserializing the body.
|
||||||
|
|
@ -140,7 +140,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
string prefix,
|
string prefix,
|
||||||
ActionContext actionContext,
|
ActionContext actionContext,
|
||||||
IModelMetadataProvider metadataProvider,
|
IModelMetadataProvider metadataProvider,
|
||||||
IModelBinder modelBinder,
|
IModelBinderFactory modelBinderFactory,
|
||||||
IValueProvider valueProvider,
|
IValueProvider valueProvider,
|
||||||
IList<IInputFormatter> inputFormatters,
|
IList<IInputFormatter> inputFormatters,
|
||||||
IObjectModelValidator objectModelValidator,
|
IObjectModelValidator objectModelValidator,
|
||||||
|
|
@ -168,9 +168,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
throw new ArgumentNullException(nameof(metadataProvider));
|
throw new ArgumentNullException(nameof(metadataProvider));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modelBinder == null)
|
if (modelBinderFactory == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(modelBinder));
|
throw new ArgumentNullException(nameof(modelBinderFactory));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (valueProvider == null)
|
if (valueProvider == null)
|
||||||
|
|
@ -206,7 +206,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
prefix,
|
prefix,
|
||||||
actionContext,
|
actionContext,
|
||||||
metadataProvider,
|
metadataProvider,
|
||||||
modelBinder,
|
modelBinderFactory,
|
||||||
valueProvider,
|
valueProvider,
|
||||||
inputFormatters,
|
inputFormatters,
|
||||||
objectModelValidator,
|
objectModelValidator,
|
||||||
|
|
@ -215,7 +215,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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
|
/// and the specified <paramref name="valueProvider"/> and executes validation using the specified
|
||||||
/// <paramref name="validatorProvider"/>.
|
/// <paramref name="validatorProvider"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -225,7 +225,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <param name="actionContext">The <see cref="ActionContext"/> for the current executing request.</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="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="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
|
||||||
/// <param name="inputFormatters">
|
/// <param name="inputFormatters">
|
||||||
/// The set of <see cref="IInputFormatter"/> instances for deserializing the body.
|
/// The set of <see cref="IInputFormatter"/> instances for deserializing the body.
|
||||||
|
|
@ -242,7 +242,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
string prefix,
|
string prefix,
|
||||||
ActionContext actionContext,
|
ActionContext actionContext,
|
||||||
IModelMetadataProvider metadataProvider,
|
IModelMetadataProvider metadataProvider,
|
||||||
IModelBinder modelBinder,
|
IModelBinderFactory modelBinderFactory,
|
||||||
IValueProvider valueProvider,
|
IValueProvider valueProvider,
|
||||||
IList<IInputFormatter> inputFormatters,
|
IList<IInputFormatter> inputFormatters,
|
||||||
IObjectModelValidator objectModelValidator,
|
IObjectModelValidator objectModelValidator,
|
||||||
|
|
@ -270,9 +270,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
throw new ArgumentNullException(nameof(metadataProvider));
|
throw new ArgumentNullException(nameof(metadataProvider));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modelBinder == null)
|
if (modelBinderFactory == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(modelBinder));
|
throw new ArgumentNullException(nameof(modelBinderFactory));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (valueProvider == null)
|
if (valueProvider == null)
|
||||||
|
|
@ -306,7 +306,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
prefix,
|
prefix,
|
||||||
actionContext,
|
actionContext,
|
||||||
metadataProvider,
|
metadataProvider,
|
||||||
modelBinder,
|
modelBinderFactory,
|
||||||
valueProvider,
|
valueProvider,
|
||||||
inputFormatters,
|
inputFormatters,
|
||||||
objectModelValidator,
|
objectModelValidator,
|
||||||
|
|
@ -315,7 +315,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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
|
/// and the specified <paramref name="valueProvider"/> and executes validation using the specified
|
||||||
/// <paramref name="validatorProvider"/>.
|
/// <paramref name="validatorProvider"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -325,7 +325,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <param name="actionContext">The <see cref="ActionContext"/> for the current executing request.</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="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="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
|
||||||
/// <param name="inputFormatters">
|
/// <param name="inputFormatters">
|
||||||
/// The set of <see cref="IInputFormatter"/> instances for deserializing the body.
|
/// The set of <see cref="IInputFormatter"/> instances for deserializing the body.
|
||||||
|
|
@ -341,7 +341,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
string prefix,
|
string prefix,
|
||||||
ActionContext actionContext,
|
ActionContext actionContext,
|
||||||
IModelMetadataProvider metadataProvider,
|
IModelMetadataProvider metadataProvider,
|
||||||
IModelBinder modelBinder,
|
IModelBinderFactory modelBinderFactory,
|
||||||
IValueProvider valueProvider,
|
IValueProvider valueProvider,
|
||||||
IList<IInputFormatter> inputFormatters,
|
IList<IInputFormatter> inputFormatters,
|
||||||
IObjectModelValidator objectModelValidator,
|
IObjectModelValidator objectModelValidator,
|
||||||
|
|
@ -372,9 +372,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
throw new ArgumentNullException(nameof(metadataProvider));
|
throw new ArgumentNullException(nameof(metadataProvider));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modelBinder == null)
|
if (modelBinderFactory == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(modelBinder));
|
throw new ArgumentNullException(nameof(modelBinderFactory));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (valueProvider == null)
|
if (valueProvider == null)
|
||||||
|
|
@ -404,7 +404,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
prefix,
|
prefix,
|
||||||
actionContext,
|
actionContext,
|
||||||
metadataProvider,
|
metadataProvider,
|
||||||
modelBinder,
|
modelBinderFactory,
|
||||||
valueProvider,
|
valueProvider,
|
||||||
inputFormatters,
|
inputFormatters,
|
||||||
objectModelValidator,
|
objectModelValidator,
|
||||||
|
|
@ -413,7 +413,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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
|
/// and the specified <paramref name="valueProvider"/> and executes validation using the specified
|
||||||
/// <paramref name="validatorProvider"/>.
|
/// <paramref name="validatorProvider"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -423,7 +423,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <param name="actionContext">The <see cref="ActionContext"/> for the current executing request.</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="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="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
|
||||||
/// <param name="inputFormatters">
|
/// <param name="inputFormatters">
|
||||||
/// The set of <see cref="IInputFormatter"/> instances for deserializing the body.
|
/// The set of <see cref="IInputFormatter"/> instances for deserializing the body.
|
||||||
|
|
@ -441,7 +441,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
string prefix,
|
string prefix,
|
||||||
ActionContext actionContext,
|
ActionContext actionContext,
|
||||||
IModelMetadataProvider metadataProvider,
|
IModelMetadataProvider metadataProvider,
|
||||||
IModelBinder modelBinder,
|
IModelBinderFactory modelBinderFactory,
|
||||||
IValueProvider valueProvider,
|
IValueProvider valueProvider,
|
||||||
IList<IInputFormatter> inputFormatters,
|
IList<IInputFormatter> inputFormatters,
|
||||||
IObjectModelValidator objectModelValidator,
|
IObjectModelValidator objectModelValidator,
|
||||||
|
|
@ -473,9 +473,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
throw new ArgumentNullException(nameof(metadataProvider));
|
throw new ArgumentNullException(nameof(metadataProvider));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modelBinder == null)
|
if (modelBinderFactory == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(modelBinder));
|
throw new ArgumentNullException(nameof(modelBinderFactory));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (valueProvider == null)
|
if (valueProvider == null)
|
||||||
|
|
@ -517,7 +517,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
var operationBindingContext = new OperationBindingContext
|
var operationBindingContext = new OperationBindingContext
|
||||||
{
|
{
|
||||||
InputFormatters = inputFormatters,
|
InputFormatters = inputFormatters,
|
||||||
ModelBinder = modelBinder,
|
|
||||||
ValidatorProvider = validatorProvider,
|
ValidatorProvider = validatorProvider,
|
||||||
MetadataProvider = metadataProvider,
|
MetadataProvider = metadataProvider,
|
||||||
ActionContext = actionContext,
|
ActionContext = actionContext,
|
||||||
|
|
@ -532,7 +531,25 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
modelBindingContext.Model = model;
|
modelBindingContext.Model = model;
|
||||||
modelBindingContext.PropertyFilter = predicate;
|
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;
|
var modelBindingResult = modelBindingContext.Result;
|
||||||
if (modelBindingResult != null && modelBindingResult.Value.IsModelSet)
|
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)
|
internal static TModel CastOrDefault<TModel>(object model)
|
||||||
{
|
{
|
||||||
return (model is TModel) ? (TModel)model : default(TModel);
|
return (model is TModel) ? (TModel)model : default(TModel);
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
FormatterMappings = new FormatterMappings();
|
FormatterMappings = new FormatterMappings();
|
||||||
InputFormatters = new FormatterCollection<IInputFormatter>();
|
InputFormatters = new FormatterCollection<IInputFormatter>();
|
||||||
OutputFormatters = new FormatterCollection<IOutputFormatter>();
|
OutputFormatters = new FormatterCollection<IOutputFormatter>();
|
||||||
ModelBinders = new List<IModelBinder>();
|
ModelBinderProviders = new List<IModelBinderProvider>();
|
||||||
ModelBindingMessageProvider = new ModelBindingMessageProvider();
|
ModelBindingMessageProvider = new ModelBindingMessageProvider();
|
||||||
ModelMetadataDetailsProviders = new List<IMetadataDetailsProvider>();
|
ModelMetadataDetailsProviders = new List<IMetadataDetailsProvider>();
|
||||||
ModelValidatorProviders = new List<IModelValidatorProvider>();
|
ModelValidatorProviders = new List<IModelValidatorProvider>();
|
||||||
|
|
@ -81,9 +81,9 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public IList<IModelBinder> ModelBinders { get; }
|
public IList<IModelBinderProvider> ModelBinderProviders { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the default <see cref="IModelBindingMessageProvider"/>. Changes here are copied to the
|
/// 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"));
|
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)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
||||||
Version 2.0
|
Version 2.0
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
The primary goals of this format is to allow a simple XML format
|
||||||
that is mostly human readable. The generation and parsing of the
|
that is mostly human readable. The generation and parsing of the
|
||||||
various data types are done through the TypeConverter classes
|
various data types are done through the TypeConverter classes
|
||||||
associated with the data types.
|
associated with the data types.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
... ado.net/XML headers & schema ...
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
<resheader name="version">2.0</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>
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
<comment>This is a comment</comment>
|
<comment>This is a comment</comment>
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
There are any number of "resheader" rows that contain simple
|
||||||
name/value pairs.
|
name/value pairs.
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
Each data row contains a name, and value. The row also contains a
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
text/value conversion through the TypeConverter architecture.
|
text/value conversion through the TypeConverter architecture.
|
||||||
Classes that don't support this are serialized and stored with the
|
Classes that don't support this are serialized and stored with the
|
||||||
mimetype set.
|
mimetype set.
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
The mimetype is used for serialized objects, and tells the
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
read any of the formats listed below.
|
read any of the formats listed below.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
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
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
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
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
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
|
: using a System.ComponentModel.TypeConverter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
-->
|
-->
|
||||||
|
|
@ -343,4 +343,7 @@
|
||||||
<data name="MustSpecifyAtLeastOneAuthenticationScheme" xml:space="preserve">
|
<data name="MustSpecifyAtLeastOneAuthenticationScheme" xml:space="preserve">
|
||||||
<value>At least one authentication scheme must be specified.</value>
|
<value>At least one authentication scheme must be specified.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="CouldNotCreateIModelBinder" xml:space="preserve">
|
||||||
|
<value>Could not create a model binder for model object of type '{0}'.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -17,12 +17,9 @@ namespace Microsoft.AspNetCore.Mvc.WebApiCompatShim
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
if (bindingContext.ModelType == typeof(HttpRequestMessage))
|
var model = bindingContext.OperationBindingContext.HttpContext.GetHttpRequestMessage();
|
||||||
{
|
bindingContext.ValidationState.Add(model, new ValidationStateEntry() { SuppressValidation = true });
|
||||||
var model = bindingContext.OperationBindingContext.HttpContext.GetHttpRequestMessage();
|
bindingContext.Result = ModelBindingResult.Success(bindingContext.ModelName, model);
|
||||||
bindingContext.ValidationState.Add(model, new ValidationStateEntry() { SuppressValidation = true });
|
|
||||||
bindingContext.Result = ModelBindingResult.Success(bindingContext.ModelName, model);
|
|
||||||
}
|
|
||||||
|
|
||||||
return TaskCache.CompletedTask;
|
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());
|
options.Filters.Add(new HttpResponseExceptionActionFilter());
|
||||||
|
|
||||||
// Add a model binder to be able to bind HttpRequestMessage
|
// 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
|
// Add a formatter to write out an HttpResponseMessage to the response
|
||||||
options.OutputFormatters.Insert(0, new HttpResponseMessageOutputFormatter());
|
options.OutputFormatters.Insert(0, new HttpResponseMessageOutputFormatter());
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.Routing.Constraints;
|
using Microsoft.AspNetCore.Routing.Constraints;
|
||||||
|
|
|
||||||
|
|
@ -1638,7 +1638,6 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
|
||||||
var controllerContext = new ControllerContext()
|
var controllerContext = new ControllerContext()
|
||||||
{
|
{
|
||||||
HttpContext = httpContext,
|
HttpContext = httpContext,
|
||||||
ModelBinders = new[] { binder, },
|
|
||||||
ValueProviders = new[] { valueProvider, },
|
ValueProviders = new[] { valueProvider, },
|
||||||
ValidatorProviders = new[]
|
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()
|
var controller = new TestableController()
|
||||||
{
|
{
|
||||||
ControllerContext = controllerContext,
|
ControllerContext = controllerContext,
|
||||||
MetadataProvider = metadataProvider,
|
MetadataProvider = metadataProvider,
|
||||||
|
ModelBinderFactory = binderFactory.Object,
|
||||||
ObjectValidator = new DefaultObjectValidator(metadataProvider, new ValidatorCache()),
|
ObjectValidator = new DefaultObjectValidator(metadataProvider, new ValidatorCache()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2057,7 +2057,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
actionDescriptor,
|
actionDescriptor,
|
||||||
new IInputFormatter[0],
|
new IInputFormatter[0],
|
||||||
actionArgumentsBinder.Object,
|
actionArgumentsBinder.Object,
|
||||||
new IModelBinder[0],
|
|
||||||
new IModelValidatorProvider[0],
|
new IModelValidatorProvider[0],
|
||||||
new IValueProviderFactory[0],
|
new IValueProviderFactory[0],
|
||||||
new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(),
|
new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(),
|
||||||
|
|
@ -2090,9 +2089,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
FilterDescriptors = new List<FilterDescriptor>()
|
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>();
|
var context = new Mock<HttpContext>();
|
||||||
context.SetupGet(c => c.Items)
|
context.SetupGet(c => c.Items)
|
||||||
.Returns(new Dictionary<object, object>());
|
.Returns(new Dictionary<object, object>());
|
||||||
|
|
@ -2115,8 +2111,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
new IInputFormatter[0],
|
new IInputFormatter[0],
|
||||||
new ControllerArgumentBinder(
|
new ControllerArgumentBinder(
|
||||||
metadataProvider,
|
metadataProvider,
|
||||||
|
TestModelBinderFactory.CreateDefault(metadataProvider),
|
||||||
new DefaultObjectValidator(metadataProvider, new ValidatorCache())),
|
new DefaultObjectValidator(metadataProvider, new ValidatorCache())),
|
||||||
new IModelBinder[] { binder.Object },
|
|
||||||
new IModelValidatorProvider[0],
|
new IModelValidatorProvider[0],
|
||||||
new IValueProviderFactory[0],
|
new IValueProviderFactory[0],
|
||||||
new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(),
|
new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(),
|
||||||
|
|
@ -2241,7 +2237,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ControllerActionDescriptor descriptor,
|
ControllerActionDescriptor descriptor,
|
||||||
IReadOnlyList<IInputFormatter> inputFormatters,
|
IReadOnlyList<IInputFormatter> inputFormatters,
|
||||||
IControllerActionArgumentBinder controllerActionArgumentBinder,
|
IControllerActionArgumentBinder controllerActionArgumentBinder,
|
||||||
IReadOnlyList<IModelBinder> modelBinders,
|
|
||||||
IReadOnlyList<IModelValidatorProvider> modelValidatorProviders,
|
IReadOnlyList<IModelValidatorProvider> modelValidatorProviders,
|
||||||
IReadOnlyList<IValueProviderFactory> valueProviderFactories,
|
IReadOnlyList<IValueProviderFactory> valueProviderFactories,
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
|
|
@ -2254,7 +2249,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
descriptor,
|
descriptor,
|
||||||
inputFormatters,
|
inputFormatters,
|
||||||
controllerActionArgumentBinder,
|
controllerActionArgumentBinder,
|
||||||
modelBinders,
|
|
||||||
modelValidatorProviders,
|
modelValidatorProviders,
|
||||||
valueProviderFactories,
|
valueProviderFactories,
|
||||||
logger,
|
logger,
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ using Microsoft.AspNetCore.Http.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Test;
|
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Moq;
|
using Moq;
|
||||||
|
|
@ -36,17 +35,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
binder
|
binder
|
||||||
.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
|
.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
|
||||||
.Returns(TaskCache.CompletedTask);
|
.Returns(TaskCache.CompletedTask);
|
||||||
|
var factory = GetModelBinderFactory(binder.Object);
|
||||||
|
var argumentBinder = GetArgumentBinder(factory);
|
||||||
|
|
||||||
var controllerContext = GetControllerContext(actionDescriptor);
|
var controllerContext = GetControllerContext(actionDescriptor);
|
||||||
controllerContext.ModelBinders.Add(binder.Object);
|
|
||||||
controllerContext.ValueProviders.Add(new SimpleValueProvider());
|
|
||||||
|
|
||||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
|
||||||
var argumentBinder = GetArgumentBinder();
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await argumentBinder
|
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, new TestController());
|
||||||
.BindActionArgumentsAsync(controllerContext, new TestController());
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Empty(result);
|
Assert.Empty(result);
|
||||||
|
|
@ -69,17 +64,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
binder
|
binder
|
||||||
.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
|
.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
|
||||||
.Returns(TaskCache.CompletedTask);
|
.Returns(TaskCache.CompletedTask);
|
||||||
|
var factory = GetModelBinderFactory(binder.Object);
|
||||||
|
var argumentBinder = GetArgumentBinder(factory);
|
||||||
|
|
||||||
var controllerContext = GetControllerContext(actionDescriptor);
|
var controllerContext = GetControllerContext(actionDescriptor);
|
||||||
controllerContext.ModelBinders.Add(binder.Object);
|
|
||||||
controllerContext.ValueProviders.Add(new SimpleValueProvider());
|
|
||||||
|
|
||||||
var argumentBinder = GetArgumentBinder();
|
|
||||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await argumentBinder
|
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, new TestController());
|
||||||
.BindActionArgumentsAsync(controllerContext, new TestController());
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Empty(result);
|
Assert.Empty(result);
|
||||||
|
|
@ -111,16 +102,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
context.Result = ModelBindingResult.Success(string.Empty, value);
|
context.Result = ModelBindingResult.Success(string.Empty, value);
|
||||||
})
|
})
|
||||||
.Returns(TaskCache.CompletedTask);
|
.Returns(TaskCache.CompletedTask);
|
||||||
|
var factory = GetModelBinderFactory(binder.Object);
|
||||||
|
var argumentBinder = GetArgumentBinder(factory);
|
||||||
|
|
||||||
var controllerContext = GetControllerContext(actionDescriptor);
|
var controllerContext = GetControllerContext(actionDescriptor);
|
||||||
controllerContext.ModelBinders.Add(binder.Object);
|
|
||||||
controllerContext.ValueProviders.Add(new SimpleValueProvider());
|
|
||||||
|
|
||||||
var argumentBinder = GetArgumentBinder();
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await argumentBinder
|
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, new TestController());
|
||||||
.BindActionArgumentsAsync(controllerContext, new TestController());
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(1, result.Count);
|
Assert.Equal(1, result.Count);
|
||||||
|
|
@ -139,7 +127,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ParameterType = typeof(object),
|
ParameterType = typeof(object),
|
||||||
});
|
});
|
||||||
|
|
||||||
var controllerContext = GetControllerContext(actionDescriptor, "Hello");
|
var controllerContext = GetControllerContext(actionDescriptor);
|
||||||
|
|
||||||
|
var factory = GetModelBinderFactory("Hello");
|
||||||
|
|
||||||
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
||||||
mockValidator
|
mockValidator
|
||||||
|
|
@ -150,7 +140,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
It.IsAny<string>(),
|
It.IsAny<string>(),
|
||||||
It.IsAny<object>()));
|
It.IsAny<object>()));
|
||||||
|
|
||||||
var argumentBinder = GetArgumentBinder(mockValidator.Object);
|
var argumentBinder = GetArgumentBinder(factory, mockValidator.Object);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, new TestController());
|
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, new TestController());
|
||||||
|
|
@ -180,15 +170,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
BindingInfo = new BindingInfo(),
|
BindingInfo = new BindingInfo(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var controllerContext = GetControllerContext(actionDescriptor);
|
||||||
|
|
||||||
var binder = new Mock<IModelBinder>();
|
var binder = new Mock<IModelBinder>();
|
||||||
binder
|
binder
|
||||||
.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
|
.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
|
||||||
.Returns(TaskCache.CompletedTask);
|
.Returns(TaskCache.CompletedTask);
|
||||||
|
|
||||||
var controllerContext = GetControllerContext(actionDescriptor);
|
|
||||||
controllerContext.ModelBinders.Add(binder.Object);
|
|
||||||
controllerContext.ValueProviders.Add(new SimpleValueProvider());
|
|
||||||
|
|
||||||
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
||||||
mockValidator
|
mockValidator
|
||||||
.Setup(o => o.Validate(
|
.Setup(o => o.Validate(
|
||||||
|
|
@ -198,7 +186,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
It.IsAny<string>(),
|
It.IsAny<string>(),
|
||||||
It.IsAny<object>()));
|
It.IsAny<object>()));
|
||||||
|
|
||||||
var argumentBinder = GetArgumentBinder(mockValidator.Object);
|
var factory = GetModelBinderFactory(binder.Object);
|
||||||
|
var argumentBinder = GetArgumentBinder(factory, mockValidator.Object);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, new TestController());
|
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, new TestController());
|
||||||
|
|
@ -226,7 +215,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ParameterType = typeof(string),
|
ParameterType = typeof(string),
|
||||||
});
|
});
|
||||||
|
|
||||||
var controllerContext = GetControllerContext(actionDescriptor, "Hello");
|
var controllerContext = GetControllerContext(actionDescriptor);
|
||||||
|
|
||||||
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
||||||
mockValidator
|
mockValidator
|
||||||
|
|
@ -237,7 +226,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
It.IsAny<string>(),
|
It.IsAny<string>(),
|
||||||
It.IsAny<object>()));
|
It.IsAny<object>()));
|
||||||
|
|
||||||
var argumentBinder = GetArgumentBinder(mockValidator.Object);
|
var factory = GetModelBinderFactory("Hello");
|
||||||
|
var argumentBinder = GetArgumentBinder(factory, mockValidator.Object);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, new TestController());
|
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, new TestController());
|
||||||
|
|
@ -266,15 +256,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ParameterType = typeof(string),
|
ParameterType = typeof(string),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var controllerContext = GetControllerContext(actionDescriptor);
|
||||||
|
|
||||||
var binder = new Mock<IModelBinder>();
|
var binder = new Mock<IModelBinder>();
|
||||||
binder
|
binder
|
||||||
.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
|
.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
|
||||||
.Returns(TaskCache.CompletedTask);
|
.Returns(TaskCache.CompletedTask);
|
||||||
|
|
||||||
var controllerContext = GetControllerContext(actionDescriptor);
|
|
||||||
controllerContext.ModelBinders.Add(binder.Object);
|
|
||||||
controllerContext.ValueProviders.Add(new SimpleValueProvider());
|
|
||||||
|
|
||||||
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
||||||
mockValidator
|
mockValidator
|
||||||
.Setup(o => o.Validate(
|
.Setup(o => o.Validate(
|
||||||
|
|
@ -284,7 +272,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
It.IsAny<string>(),
|
It.IsAny<string>(),
|
||||||
It.IsAny<object>()));
|
It.IsAny<object>()));
|
||||||
|
|
||||||
var argumentBinder = GetArgumentBinder(mockValidator.Object);
|
var factory = GetModelBinderFactory(binder.Object);
|
||||||
|
var argumentBinder = GetArgumentBinder(factory, mockValidator.Object);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, new TestController());
|
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, new TestController());
|
||||||
|
|
@ -313,8 +302,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ParameterType = typeof(string)
|
ParameterType = typeof(string)
|
||||||
});
|
});
|
||||||
|
|
||||||
var controllerContext = GetControllerContext(actionDescriptor, "Hello");
|
var controllerContext = GetControllerContext(actionDescriptor);
|
||||||
var argumentBinder = GetArgumentBinder();
|
|
||||||
|
var factory = GetModelBinderFactory("Hello");
|
||||||
|
var argumentBinder = GetArgumentBinder(factory);
|
||||||
|
|
||||||
var controller = new TestController();
|
var controller = new TestController();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -339,10 +331,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ParameterType = typeof(ICollection<string>),
|
ParameterType = typeof(ICollection<string>),
|
||||||
});
|
});
|
||||||
|
|
||||||
var expected = new List<string> { "Hello", "World", "!!" };
|
var controllerContext = GetControllerContext(actionDescriptor);
|
||||||
var controllerContext = GetControllerContext(actionDescriptor, expected);
|
|
||||||
|
var expected = new List<string> { "Hello", "World", "!!" };
|
||||||
|
var factory = GetModelBinderFactory(expected);
|
||||||
|
var argumentBinder = GetArgumentBinder(factory);
|
||||||
|
|
||||||
var argumentBinder = GetArgumentBinder();
|
|
||||||
var controller = new TestController();
|
var controller = new TestController();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -369,13 +363,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ParameterType = typeof(int)
|
ParameterType = typeof(int)
|
||||||
});
|
});
|
||||||
|
|
||||||
var binder = new StubModelBinder(ModelBindingResult.Success(string.Empty, model: null));
|
|
||||||
|
|
||||||
var controllerContext = GetControllerContext(actionDescriptor);
|
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();
|
var controller = new TestController();
|
||||||
|
|
||||||
// Some non default value.
|
// Some non default value.
|
||||||
|
|
@ -401,13 +394,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ParameterType = typeof(int?)
|
ParameterType = typeof(int?)
|
||||||
});
|
});
|
||||||
|
|
||||||
var binder = new StubModelBinder(ModelBindingResult.Success(key: string.Empty, model: null));
|
|
||||||
|
|
||||||
var controllerContext = GetControllerContext(actionDescriptor);
|
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();
|
var controller = new TestController();
|
||||||
|
|
||||||
// Some non default value.
|
// Some non default value.
|
||||||
|
|
@ -478,8 +470,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
ParameterType = propertyType,
|
ParameterType = propertyType,
|
||||||
});
|
});
|
||||||
|
|
||||||
var controllerContext = GetControllerContext(actionDescriptor, inputValue);
|
var controllerContext = GetControllerContext(actionDescriptor);
|
||||||
var argumentBinder = GetArgumentBinder();
|
|
||||||
|
var factory = GetModelBinderFactory(inputValue);
|
||||||
|
var argumentBinder = GetArgumentBinder(factory);
|
||||||
|
|
||||||
var controller = new TestController();
|
var controller = new TestController();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -533,8 +528,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
var controllerContext = GetControllerContext(actionDescriptor);
|
var controllerContext = GetControllerContext(actionDescriptor);
|
||||||
var argumentBinder = GetArgumentBinder();
|
|
||||||
var controller = new TestController();
|
|
||||||
|
|
||||||
var binder = new StubModelBinder(bindingContext =>
|
var binder = new StubModelBinder(bindingContext =>
|
||||||
{
|
{
|
||||||
|
|
@ -550,9 +543,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
bindingContext.Result = ModelBindingResult.Failed(bindingContext.ModelName);
|
bindingContext.Result = ModelBindingResult.Failed(bindingContext.ModelName);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
controllerContext.ModelBinders.Add(binder.Object);
|
|
||||||
|
var factory = GetModelBinderFactory(binder);
|
||||||
controllerContext.ValueProviders.Add(new SimpleValueProvider());
|
controllerContext.ValueProviders.Add(new SimpleValueProvider());
|
||||||
|
|
||||||
|
var argumentBinder = GetArgumentBinder(factory);
|
||||||
|
var controller = new TestController();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, controller);
|
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, controller);
|
||||||
|
|
||||||
|
|
@ -574,6 +571,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
RouteData = new RouteData(),
|
RouteData = new RouteData(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
context.ValueProviders.Add(new SimpleValueProvider());
|
||||||
return context;
|
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>();
|
var binder = new Mock<IModelBinder>();
|
||||||
binder.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
|
binder
|
||||||
.Returns<DefaultModelBindingContext>(mbc =>
|
.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
|
||||||
{
|
.Returns<DefaultModelBindingContext>(mbc =>
|
||||||
mbc.Result = ModelBindingResult.Success(string.Empty, model);
|
{
|
||||||
return TaskCache.CompletedTask;
|
mbc.Result = ModelBindingResult.Success(string.Empty, model);
|
||||||
});
|
return TaskCache.CompletedTask;
|
||||||
|
});
|
||||||
|
|
||||||
context.ModelBinders.Add(binder.Object);
|
return GetModelBinderFactory(binder.Object);
|
||||||
context.ValueProviders.Add(new SimpleValueProvider());
|
|
||||||
return context;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
if (validator == null)
|
||||||
{
|
{
|
||||||
validator = CreateMockValidator();
|
validator = CreateMockValidator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (factory == null)
|
||||||
|
{
|
||||||
|
factory = TestModelBinderFactory.CreateDefault();
|
||||||
|
}
|
||||||
|
|
||||||
return new ControllerArgumentBinder(
|
return new ControllerArgumentBinder(
|
||||||
TestModelMetadataProvider.CreateDefaultProvider(),
|
TestModelMetadataProvider.CreateDefaultProvider(),
|
||||||
|
factory,
|
||||||
validator);
|
validator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Mvc.Core;
|
using Microsoft.AspNetCore.Mvc.Core;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||||
using Xunit;
|
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.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http.Internal;
|
using Microsoft.AspNetCore.Http.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
{
|
{
|
||||||
public class ArrayModelBinderTest
|
public class ArrayModelBinderTest
|
||||||
{
|
{
|
||||||
|
|
@ -22,7 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
};
|
};
|
||||||
var bindingContext = GetBindingContext(valueProvider);
|
var bindingContext = GetBindingContext(valueProvider);
|
||||||
var modelState = bindingContext.ModelState;
|
var modelState = bindingContext.ModelState;
|
||||||
var binder = new ArrayModelBinder<int>();
|
var binder = new ArrayModelBinder<int>(new SimpleTypeModelBinder());
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await binder.BindModelResultAsync(bindingContext);
|
var result = await binder.BindModelResultAsync(bindingContext);
|
||||||
|
|
@ -32,14 +31,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
|
|
||||||
var array = Assert.IsType<int[]>(result.Model);
|
var array = Assert.IsType<int[]>(result.Model);
|
||||||
Assert.Equal(new[] { 42, 84 }, array);
|
Assert.Equal(new[] { 42, 84 }, array);
|
||||||
Assert.True(modelState.IsValid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ArrayModelBinder_CreatesEmptyCollection_IfIsTopLevelObject()
|
public async Task ArrayModelBinder_CreatesEmptyCollection_IfIsTopLevelObject()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var binder = new ArrayModelBinder<string>();
|
var binder = new ArrayModelBinder<string>(new SimpleTypeModelBinder());
|
||||||
|
|
||||||
var context = CreateContext();
|
var context = CreateContext();
|
||||||
context.IsTopLevelObject = true;
|
context.IsTopLevelObject = true;
|
||||||
|
|
@ -69,7 +67,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
public async Task ArrayModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(string prefix)
|
public async Task ArrayModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(string prefix)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var binder = new ArrayModelBinder<string>();
|
var binder = new ArrayModelBinder<string>(new SimpleTypeModelBinder());
|
||||||
|
|
||||||
var context = CreateContext();
|
var context = CreateContext();
|
||||||
context.ModelName = ModelNames.CreatePropertyModelName(prefix, "ArrayProperty");
|
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.
|
// Here "fails silently" means the call does not update the array but also does not throw or set an error.
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(ArrayModelData))]
|
[MemberData(nameof(ArrayModelData))]
|
||||||
|
|
@ -139,7 +115,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
var bindingContext = GetBindingContext(valueProvider, isReadOnly: false);
|
var bindingContext = GetBindingContext(valueProvider, isReadOnly: false);
|
||||||
var modelState = bindingContext.ModelState;
|
var modelState = bindingContext.ModelState;
|
||||||
bindingContext.Model = model;
|
bindingContext.Model = model;
|
||||||
var binder = new ArrayModelBinder<int>();
|
|
||||||
|
var binder = new ArrayModelBinder<int>(new SimpleTypeModelBinder());
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await binder.BindModelResultAsync(bindingContext);
|
var result = await binder.BindModelResultAsync(bindingContext);
|
||||||
|
|
@ -148,8 +125,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
Assert.NotEqual(default(ModelBindingResult), result);
|
Assert.NotEqual(default(ModelBindingResult), result);
|
||||||
Assert.True(result.IsModelSet);
|
Assert.True(result.IsModelSet);
|
||||||
Assert.Same(model, result.Model);
|
Assert.Same(model, result.Model);
|
||||||
|
|
||||||
Assert.True(modelState.IsValid);
|
|
||||||
for (var i = 0; i < arrayLength; i++)
|
for (var i = 0; i < arrayLength; i++)
|
||||||
{
|
{
|
||||||
// Array should be unchanged.
|
// Array should be unchanged.
|
||||||
|
|
@ -191,7 +167,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
ValueProvider = valueProvider,
|
ValueProvider = valueProvider,
|
||||||
OperationBindingContext = new OperationBindingContext
|
OperationBindingContext = new OperationBindingContext
|
||||||
{
|
{
|
||||||
ModelBinder = CreateIntBinder(),
|
|
||||||
MetadataProvider = metadataProvider,
|
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 System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http.Internal;
|
using Microsoft.AspNetCore.Http.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
|
using Microsoft.AspNetCore.Testing;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
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]
|
[Fact]
|
||||||
public async Task BindModel_ReturnsFailedResult_EvenIfSelectedBinderReturnsNull()
|
public async Task BindModel_ReturnsFailedResult_EvenIfSelectedBinderReturnsNull()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var bindingContext = GetBindingContext(typeof(Person), binderType: typeof(NullModelBinder));
|
var bindingContext = GetBindingContext(typeof(Person), binderType: typeof(NullModelBinder));
|
||||||
|
|
||||||
var binder = new BinderTypeBasedModelBinder();
|
var binder = new BinderTypeModelBinder(typeof(NullModelBinder));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
||||||
|
|
@ -57,7 +43,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
|
|
||||||
bindingContext.OperationBindingContext.HttpContext.RequestServices = serviceProvider;
|
bindingContext.OperationBindingContext.HttpContext.RequestServices = serviceProvider;
|
||||||
|
|
||||||
var binder = new BinderTypeBasedModelBinder();
|
var binder = new BinderTypeModelBinder(typeof(NotNullModelBinder));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
||||||
|
|
@ -69,21 +55,19 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task BindModel_ForNonModelBinder_Throws()
|
public void BindModel_ForNonModelBinder_Throws()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var bindingContext = GetBindingContext(typeof(Person), binderType: typeof(Person));
|
var bindingContext = GetBindingContext(typeof(Person), binderType: typeof(Person));
|
||||||
var binder = new BinderTypeBasedModelBinder();
|
|
||||||
|
|
||||||
var expected = $"The type '{typeof(Person).FullName}' must implement " +
|
var expected = $"The type '{typeof(Person).FullName}' must implement " +
|
||||||
$"'{typeof(IModelBinder).FullName}' to be used as a model binder.";
|
$"'{typeof(IModelBinder).FullName}' to be used as a model binder.";
|
||||||
|
|
||||||
// Act
|
// Act & Assert
|
||||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
ExceptionAssert.ThrowsArgument(
|
||||||
() => binder.BindModelResultAsync(bindingContext));
|
() => new BinderTypeModelBinder(typeof(Person)),
|
||||||
|
"binderType",
|
||||||
// Assert
|
expected);
|
||||||
Assert.Equal(expected, ex.Message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DefaultModelBindingContext GetBindingContext(Type modelType, Type binderType = null)
|
private static DefaultModelBindingContext GetBindingContext(Type modelType, Type binderType = null)
|
||||||
|
|
@ -108,7 +92,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
ValueProvider = Mock.Of<IValueProvider>(),
|
ValueProvider = Mock.Of<IValueProvider>(),
|
||||||
ModelState = new ModelStateDictionary(),
|
ModelState = new ModelStateDictionary(),
|
||||||
OperationBindingContext = operationBindingContext,
|
OperationBindingContext = operationBindingContext,
|
||||||
BinderType = binderType
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return bindingContext;
|
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 Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
{
|
{
|
||||||
public class BodyModelBinderTests
|
public class BodyModelBinderTests
|
||||||
{
|
{
|
||||||
|
|
@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
new[] { inputFormatter },
|
new[] { inputFormatter },
|
||||||
metadataProvider: provider);
|
metadataProvider: provider);
|
||||||
|
|
||||||
var binder = new BodyModelBinder(new TestHttpRequestStreamReaderFactory());
|
var binder = CreateBinder();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
||||||
|
|
@ -60,7 +60,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
|
|
||||||
var bindingContext = GetBindingContext(typeof(Person), metadataProvider: provider);
|
var bindingContext = GetBindingContext(typeof(Person), metadataProvider: provider);
|
||||||
|
|
||||||
var binder = bindingContext.OperationBindingContext.ModelBinder;
|
var binder = CreateBinder();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
||||||
|
|
@ -88,7 +88,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
var bindingContext = GetBindingContext(typeof(Person), metadataProvider: provider);
|
var bindingContext = GetBindingContext(typeof(Person), metadataProvider: provider);
|
||||||
bindingContext.BinderModelName = "custom";
|
bindingContext.BinderModelName = "custom";
|
||||||
|
|
||||||
var binder = bindingContext.OperationBindingContext.ModelBinder;
|
var binder = CreateBinder();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
||||||
|
|
@ -115,7 +115,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
|
|
||||||
var bindingContext = GetBindingContext(typeof(Person), metadataProvider: provider);
|
var bindingContext = GetBindingContext(typeof(Person), metadataProvider: provider);
|
||||||
|
|
||||||
var binder = bindingContext.OperationBindingContext.ModelBinder;
|
var binder = CreateBinder();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
||||||
|
|
@ -125,44 +125,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
Assert.False(binderResult.IsModelSet);
|
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]
|
[Fact]
|
||||||
public async Task CustomFormatterDeserializationException_AddedToModelState()
|
public async Task CustomFormatterDeserializationException_AddedToModelState()
|
||||||
{
|
{
|
||||||
|
|
@ -180,7 +142,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
httpContext: httpContext,
|
httpContext: httpContext,
|
||||||
metadataProvider: provider);
|
metadataProvider: provider);
|
||||||
|
|
||||||
var binder = bindingContext.OperationBindingContext.ModelBinder;
|
var binder = CreateBinder();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
||||||
|
|
@ -215,7 +177,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
httpContext: httpContext,
|
httpContext: httpContext,
|
||||||
metadataProvider: provider);
|
metadataProvider: provider);
|
||||||
|
|
||||||
var binder = bindingContext.OperationBindingContext.ModelBinder;
|
var binder = CreateBinder();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
||||||
|
|
@ -250,7 +212,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
var provider = new TestModelMetadataProvider();
|
var provider = new TestModelMetadataProvider();
|
||||||
provider.ForType<Person>().BindingDetails(d => d.BindingSource = BindingSource.Body);
|
provider.ForType<Person>().BindingDetails(d => d.BindingSource = BindingSource.Body);
|
||||||
var bindingContext = GetBindingContext(typeof(Person), inputFormatters, metadataProvider: provider);
|
var bindingContext = GetBindingContext(typeof(Person), inputFormatters, metadataProvider: provider);
|
||||||
var binder = bindingContext.OperationBindingContext.ModelBinder;
|
|
||||||
|
var binder = CreateBinder();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
var binderResult = await binder.BindModelResultAsync(bindingContext);
|
||||||
|
|
@ -288,7 +251,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
HttpContext = httpContext,
|
HttpContext = httpContext,
|
||||||
},
|
},
|
||||||
InputFormatters = inputFormatters.ToList(),
|
InputFormatters = inputFormatters.ToList(),
|
||||||
ModelBinder = new BodyModelBinder(new TestHttpRequestStreamReaderFactory()),
|
|
||||||
MetadataProvider = metadataProvider,
|
MetadataProvider = metadataProvider,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -307,6 +269,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
return bindingContext;
|
return bindingContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static BodyModelBinder CreateBinder()
|
||||||
|
{
|
||||||
|
return new BodyModelBinder(new TestHttpRequestStreamReaderFactory());
|
||||||
|
}
|
||||||
|
|
||||||
private class Person
|
private class Person
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
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 System.Threading.Tasks;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
{
|
{
|
||||||
public class ByteArrayModelBinderTests
|
public class ByteArrayModelBinderTests
|
||||||
{
|
{
|
||||||
|
|
@ -106,20 +106,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
Assert.Empty(bindingContext.ModelState); // No submitted data for "foo".
|
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)
|
private static DefaultModelBindingContext GetBindingContext(IValueProvider valueProvider, Type modelType)
|
||||||
{
|
{
|
||||||
var metadataProvider = new EmptyModelMetadataProvider();
|
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 Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
{
|
{
|
||||||
public class CancellationTokenModelBinderTests
|
public class CancellationTokenModelBinderTests
|
||||||
{
|
{
|
||||||
|
|
@ -28,23 +28,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
Assert.Equal(bindingContext.OperationBindingContext.HttpContext.RequestAborted, result.Model);
|
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)
|
private static DefaultModelBindingContext GetBindingContext(Type modelType)
|
||||||
{
|
{
|
||||||
var metadataProvider = new EmptyModelMetadataProvider();
|
var metadataProvider = new EmptyModelMetadataProvider();
|
||||||
|
|
@ -59,7 +42,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
{
|
{
|
||||||
HttpContext = new DefaultHttpContext(),
|
HttpContext = new DefaultHttpContext(),
|
||||||
},
|
},
|
||||||
ModelBinder = new CancellationTokenModelBinder(),
|
|
||||||
MetadataProvider = metadataProvider,
|
MetadataProvider = metadataProvider,
|
||||||
},
|
},
|
||||||
ValidationState = new ValidationStateDictionary(),
|
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 System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http.Internal;
|
using Microsoft.AspNetCore.Http.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Test;
|
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
{
|
{
|
||||||
public class CollectionModelBinderTest
|
public class CollectionModelBinderTest
|
||||||
{
|
{
|
||||||
|
|
@ -26,7 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
{ "someName[baz]", "200" }
|
{ "someName[baz]", "200" }
|
||||||
};
|
};
|
||||||
var bindingContext = GetModelBindingContext(valueProvider);
|
var bindingContext = GetModelBindingContext(valueProvider);
|
||||||
var binder = new CollectionModelBinder<int>();
|
var binder = new CollectionModelBinder<int>(CreateIntBinder());
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var collectionResult = await binder.BindComplexCollectionFromIndexes(
|
var collectionResult = await binder.BindComplexCollectionFromIndexes(
|
||||||
|
|
@ -52,7 +51,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
{ "someName[3]", "400" }
|
{ "someName[3]", "400" }
|
||||||
};
|
};
|
||||||
var bindingContext = GetModelBindingContext(valueProvider);
|
var bindingContext = GetModelBindingContext(valueProvider);
|
||||||
var binder = new CollectionModelBinder<int>();
|
var binder = new CollectionModelBinder<int>(CreateIntBinder());
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var boundCollection = await binder.BindComplexCollectionFromIndexes(bindingContext, indexNames: null);
|
var boundCollection = await binder.BindComplexCollectionFromIndexes(bindingContext, indexNames: null);
|
||||||
|
|
@ -79,7 +78,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
};
|
};
|
||||||
var bindingContext = GetModelBindingContext(valueProvider, isReadOnly);
|
var bindingContext = GetModelBindingContext(valueProvider, isReadOnly);
|
||||||
var modelState = bindingContext.ModelState;
|
var modelState = bindingContext.ModelState;
|
||||||
var binder = new CollectionModelBinder<int>();
|
var binder = new CollectionModelBinder<int>(CreateIntBinder());
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await binder.BindModelResultAsync(bindingContext);
|
var result = await binder.BindModelResultAsync(bindingContext);
|
||||||
|
|
@ -111,7 +110,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
var modelState = bindingContext.ModelState;
|
var modelState = bindingContext.ModelState;
|
||||||
var list = new List<int>();
|
var list = new List<int>();
|
||||||
bindingContext.Model = list;
|
bindingContext.Model = list;
|
||||||
var binder = new CollectionModelBinder<int>();
|
var binder = new CollectionModelBinder<int>(CreateIntBinder());
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await binder.BindModelResultAsync(bindingContext);
|
var result = await binder.BindModelResultAsync(bindingContext);
|
||||||
|
|
@ -138,7 +137,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
};
|
};
|
||||||
var bindingContext = GetModelBindingContext(valueProvider, isReadOnly);
|
var bindingContext = GetModelBindingContext(valueProvider, isReadOnly);
|
||||||
var modelState = bindingContext.ModelState;
|
var modelState = bindingContext.ModelState;
|
||||||
var binder = new CollectionModelBinder<int>();
|
var binder = new CollectionModelBinder<int>(CreateIntBinder());
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await binder.BindModelResultAsync(bindingContext);
|
var result = await binder.BindModelResultAsync(bindingContext);
|
||||||
|
|
@ -165,7 +164,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
var modelState = bindingContext.ModelState;
|
var modelState = bindingContext.ModelState;
|
||||||
var list = new List<int>();
|
var list = new List<int>();
|
||||||
bindingContext.Model = list;
|
bindingContext.Model = list;
|
||||||
var binder = new CollectionModelBinder<int>();
|
var binder = new CollectionModelBinder<int>(CreateIntBinder());
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await binder.BindModelResultAsync(bindingContext);
|
var result = await binder.BindModelResultAsync(bindingContext);
|
||||||
|
|
@ -182,7 +181,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
public async Task BindModelAsync_SimpleCollectionWithNullValue_Succeeds()
|
public async Task BindModelAsync_SimpleCollectionWithNullValue_Succeeds()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var binder = new CollectionModelBinder<int>();
|
var binder = new CollectionModelBinder<int>(CreateIntBinder());
|
||||||
var valueProvider = new SimpleValueProvider
|
var valueProvider = new SimpleValueProvider
|
||||||
{
|
{
|
||||||
{ "someName", null },
|
{ "someName", null },
|
||||||
|
|
@ -206,7 +205,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
public async Task BindSimpleCollection_RawValueIsEmptyCollection_ReturnsEmptyList()
|
public async Task BindSimpleCollection_RawValueIsEmptyCollection_ReturnsEmptyList()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var binder = new CollectionModelBinder<int>();
|
var binder = new CollectionModelBinder<int>(CreateIntBinder());
|
||||||
var context = GetModelBindingContext(new SimpleValueProvider());
|
var context = GetModelBindingContext(new SimpleValueProvider());
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -221,7 +220,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
public async Task CollectionModelBinder_CreatesEmptyCollection_IfIsTopLevelObject()
|
public async Task CollectionModelBinder_CreatesEmptyCollection_IfIsTopLevelObject()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var binder = new CollectionModelBinder<string>();
|
var binder = new CollectionModelBinder<string>(new StubModelBinder(result: null));
|
||||||
|
|
||||||
var context = CreateContext();
|
var context = CreateContext();
|
||||||
context.IsTopLevelObject = true;
|
context.IsTopLevelObject = true;
|
||||||
|
|
@ -251,7 +250,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
public async Task CollectionModelBinder_DoesNotCreateEmptyCollection_IfModelNonNull()
|
public async Task CollectionModelBinder_DoesNotCreateEmptyCollection_IfModelNonNull()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var binder = new CollectionModelBinder<string>();
|
var binder = new CollectionModelBinder<string>(new StubModelBinder(result: null));
|
||||||
|
|
||||||
var context = CreateContext();
|
var context = CreateContext();
|
||||||
context.IsTopLevelObject = true;
|
context.IsTopLevelObject = true;
|
||||||
|
|
@ -285,7 +284,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
public async Task CollectionModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(string prefix)
|
public async Task CollectionModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(string prefix)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var binder = new CollectionModelBinder<string>();
|
var binder = new CollectionModelBinder<string>(new StubModelBinder(result: null));
|
||||||
|
|
||||||
var context = CreateContext();
|
var context = CreateContext();
|
||||||
context.ModelName = ModelNames.CreatePropertyModelName(prefix, "ListProperty");
|
context.ModelName = ModelNames.CreatePropertyModelName(prefix, "ListProperty");
|
||||||
|
|
@ -326,7 +325,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
public void CanCreateInstance_ReturnsExpectedValue(Type modelType, bool expectedResult)
|
public void CanCreateInstance_ReturnsExpectedValue(Type modelType, bool expectedResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var binder = new CollectionModelBinder<int>();
|
var binder = new CollectionModelBinder<int>(CreateIntBinder());
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = binder.CanCreateInstance(modelType);
|
var result = binder.CanCreateInstance(modelType);
|
||||||
|
|
@ -342,13 +341,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
var culture = new CultureInfo("fr-FR");
|
var culture = new CultureInfo("fr-FR");
|
||||||
var bindingContext = GetModelBindingContext(new SimpleValueProvider());
|
var bindingContext = GetModelBindingContext(new SimpleValueProvider());
|
||||||
|
|
||||||
bindingContext.OperationBindingContext.ModelBinder = new StubModelBinder(mbc =>
|
var elementBinder = new StubModelBinder(mbc =>
|
||||||
{
|
{
|
||||||
Assert.Equal("someName", mbc.ModelName);
|
Assert.Equal("someName", mbc.ModelName);
|
||||||
mbc.Result = ModelBindingResult.Success(mbc.ModelName, 42);
|
mbc.Result = ModelBindingResult.Success(mbc.ModelName, 42);
|
||||||
});
|
});
|
||||||
|
|
||||||
var modelBinder = new CollectionModelBinder<int>();
|
var modelBinder = new CollectionModelBinder<int>(elementBinder);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var boundCollection = await modelBinder.BindSimpleCollection(
|
var boundCollection = await modelBinder.BindSimpleCollection(
|
||||||
|
|
@ -379,7 +378,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
ValueProvider = valueProvider,
|
ValueProvider = valueProvider,
|
||||||
OperationBindingContext = new OperationBindingContext
|
OperationBindingContext = new OperationBindingContext
|
||||||
{
|
{
|
||||||
ModelBinder = CreateIntBinder(),
|
|
||||||
MetadataProvider = metadataProvider
|
MetadataProvider = metadataProvider
|
||||||
},
|
},
|
||||||
ValidationState = new ValidationStateDictionary(),
|
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.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Linq;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http.Internal;
|
using Microsoft.AspNetCore.Http.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.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.Mvc.ModelBinding.Validation;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
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();
|
private static readonly IModelMetadataProvider _metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
|
|
||||||
|
|
@ -32,7 +33,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
var bindingContext = CreateContext(GetMetadataForType(typeof(Person)));
|
var bindingContext = CreateContext(GetMetadataForType(typeof(Person)));
|
||||||
bindingContext.IsTopLevelObject = isTopLevelObject;
|
bindingContext.IsTopLevelObject = isTopLevelObject;
|
||||||
|
|
||||||
var binder = new TestableMutableObjectModelBinder();
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var canCreate = binder.CanCreateModel(bindingContext);
|
var canCreate = binder.CanCreateModel(bindingContext);
|
||||||
|
|
@ -49,7 +50,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
var bindingContext = CreateContext(modelMetadata);
|
var bindingContext = CreateContext(modelMetadata);
|
||||||
bindingContext.IsTopLevelObject = false;
|
bindingContext.IsTopLevelObject = false;
|
||||||
|
|
||||||
var binder = new MutableObjectModelBinder();
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var canCreate = binder.CanCreateModel(bindingContext);
|
var canCreate = binder.CanCreateModel(bindingContext);
|
||||||
|
|
@ -64,7 +65,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
var bindingContext = CreateContext(GetMetadataForType(typeof(Document)));
|
var bindingContext = CreateContext(GetMetadataForType(typeof(Document)));
|
||||||
bindingContext.IsTopLevelObject = true;
|
bindingContext.IsTopLevelObject = true;
|
||||||
|
|
||||||
var binder = new MutableObjectModelBinder();
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var canCreate = binder.CanCreateModel(bindingContext);
|
var canCreate = binder.CanCreateModel(bindingContext);
|
||||||
|
|
@ -81,7 +82,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
var bindingContext = CreateContext(GetMetadataForType(typeof(HasAllGreedyProperties)));
|
var bindingContext = CreateContext(GetMetadataForType(typeof(HasAllGreedyProperties)));
|
||||||
bindingContext.IsTopLevelObject = isTopLevelObject;
|
bindingContext.IsTopLevelObject = isTopLevelObject;
|
||||||
|
|
||||||
var binder = new TestableMutableObjectModelBinder();
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var canCreate = binder.CanCreateModel(bindingContext);
|
var canCreate = binder.CanCreateModel(bindingContext);
|
||||||
|
|
@ -109,7 +110,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
bindingContext.ValueProvider = valueProvider.Object;
|
bindingContext.ValueProvider = valueProvider.Object;
|
||||||
bindingContext.OperationBindingContext.ValueProvider = valueProvider.Object;
|
bindingContext.OperationBindingContext.ValueProvider = valueProvider.Object;
|
||||||
|
|
||||||
var binder = new MutableObjectModelBinder();
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var canCreate = binder.CanCreateModel(bindingContext);
|
var canCreate = binder.CanCreateModel(bindingContext);
|
||||||
|
|
@ -126,7 +127,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
var bindingContext = CreateContext(GetMetadataForType(typeof(PersonWithNoProperties)));
|
var bindingContext = CreateContext(GetMetadataForType(typeof(PersonWithNoProperties)));
|
||||||
bindingContext.IsTopLevelObject = false;
|
bindingContext.IsTopLevelObject = false;
|
||||||
|
|
||||||
var binder = new TestableMutableObjectModelBinder();
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var canCreate = binder.CanCreateModel(bindingContext);
|
var canCreate = binder.CanCreateModel(bindingContext);
|
||||||
|
|
@ -142,7 +143,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
var bindingContext = CreateContext(GetMetadataForType(typeof(PersonWithNoProperties)));
|
var bindingContext = CreateContext(GetMetadataForType(typeof(PersonWithNoProperties)));
|
||||||
bindingContext.IsTopLevelObject = true;
|
bindingContext.IsTopLevelObject = true;
|
||||||
|
|
||||||
var binder = new TestableMutableObjectModelBinder();
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var canCreate = binder.CanCreateModel(bindingContext);
|
var canCreate = binder.CanCreateModel(bindingContext);
|
||||||
|
|
@ -172,7 +173,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
bindingContext.ValueProvider = valueProvider.Object;
|
bindingContext.ValueProvider = valueProvider.Object;
|
||||||
bindingContext.OperationBindingContext.ValueProvider = valueProvider.Object;
|
bindingContext.OperationBindingContext.ValueProvider = valueProvider.Object;
|
||||||
|
|
||||||
var binder = new TestableMutableObjectModelBinder();
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var canCreate = binder.CanCreateModel(bindingContext);
|
var canCreate = binder.CanCreateModel(bindingContext);
|
||||||
|
|
@ -207,7 +208,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
bindingContext.ValueProvider = valueProvider.Object;
|
bindingContext.ValueProvider = valueProvider.Object;
|
||||||
bindingContext.OperationBindingContext.ValueProvider = originalValueProvider.Object;
|
bindingContext.OperationBindingContext.ValueProvider = originalValueProvider.Object;
|
||||||
|
|
||||||
var binder = new TestableMutableObjectModelBinder();
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var canCreate = binder.CanCreateModel(bindingContext);
|
var canCreate = binder.CanCreateModel(bindingContext);
|
||||||
|
|
@ -240,7 +241,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
bindingContext.ValueProvider = valueProvider.Object;
|
bindingContext.ValueProvider = valueProvider.Object;
|
||||||
bindingContext.OperationBindingContext.ValueProvider = originalValueProvider.Object;
|
bindingContext.OperationBindingContext.ValueProvider = originalValueProvider.Object;
|
||||||
|
|
||||||
var binder = new TestableMutableObjectModelBinder();
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var canCreate = binder.CanCreateModel(bindingContext);
|
var canCreate = binder.CanCreateModel(bindingContext);
|
||||||
|
|
@ -269,7 +270,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
ValueProvider = mockValueProvider.Object,
|
ValueProvider = mockValueProvider.Object,
|
||||||
OperationBindingContext = new OperationBindingContext
|
OperationBindingContext = new OperationBindingContext
|
||||||
{
|
{
|
||||||
ModelBinder = mockBinder,
|
|
||||||
MetadataProvider = _metadataProvider,
|
MetadataProvider = _metadataProvider,
|
||||||
ValidatorProvider = Mock.Of<IModelValidatorProvider>()
|
ValidatorProvider = Mock.Of<IModelValidatorProvider>()
|
||||||
},
|
},
|
||||||
|
|
@ -278,7 +278,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
|
|
||||||
var model = new Person();
|
var model = new Person();
|
||||||
|
|
||||||
var testableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
|
var testableBinder = new Mock<TestableComplexTypeModelBinder> { CallBase = true };
|
||||||
testableBinder
|
testableBinder
|
||||||
.Setup(o => o.CreateModelPublic(bindingContext))
|
.Setup(o => o.CreateModelPublic(bindingContext))
|
||||||
.Returns(model)
|
.Returns(model)
|
||||||
|
|
@ -311,7 +311,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
var propertyMetadata = GetMetadataForProperty(typeof(MyModelTestingCanUpdateProperty), propertyName);
|
var propertyMetadata = GetMetadataForProperty(typeof(MyModelTestingCanUpdateProperty), propertyName);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(propertyMetadata);
|
var canUpdate = ComplexTypeModelBinder.CanUpdatePropertyInternal(propertyMetadata);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(expected, canUpdate);
|
Assert.Equal(expected, canUpdate);
|
||||||
|
|
@ -331,7 +331,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
var metadata = metadataProvider.GetMetadataForProperty(typeof(CollectionContainer), propertyName);
|
var metadata = metadataProvider.GetMetadataForProperty(typeof(CollectionContainer), propertyName);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(metadata);
|
var canUpdate = ComplexTypeModelBinder.CanUpdatePropertyInternal(metadata);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(expected, canUpdate);
|
Assert.Equal(expected, canUpdate);
|
||||||
|
|
@ -346,10 +346,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
ModelMetadata = GetMetadataForType(typeof(Person))
|
ModelMetadata = GetMetadataForType(typeof(Person))
|
||||||
};
|
};
|
||||||
|
|
||||||
var testableBinder = new TestableMutableObjectModelBinder();
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var model = testableBinder.CreateModelPublic(bindingContext);
|
var model = binder.CreateModelPublic(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.IsType<Person>(model);
|
Assert.IsType<Person>(model);
|
||||||
|
|
@ -362,7 +362,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
var bindingContext = CreateContext(GetMetadataForType(typeof(Person)), new Person());
|
var bindingContext = CreateContext(GetMetadataForType(typeof(Person)), new Person());
|
||||||
var originalModel = bindingContext.Model;
|
var originalModel = bindingContext.Model;
|
||||||
|
|
||||||
var binder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
|
var binder = new Mock<TestableComplexTypeModelBinder>(){ CallBase = true };
|
||||||
binder
|
binder
|
||||||
.Setup(b => b.CreateModelPublic(It.IsAny<ModelBindingContext>()))
|
.Setup(b => b.CreateModelPublic(It.IsAny<ModelBindingContext>()))
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
|
|
@ -381,7 +381,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
// Arrange
|
// Arrange
|
||||||
var bindingContext = CreateContext(GetMetadataForType(typeof(Person)), model: null);
|
var bindingContext = CreateContext(GetMetadataForType(typeof(Person)), model: null);
|
||||||
|
|
||||||
var testableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
|
var testableBinder = new Mock<TestableComplexTypeModelBinder> { CallBase = true };
|
||||||
testableBinder
|
testableBinder
|
||||||
.Setup(o => o.CreateModelPublic(bindingContext))
|
.Setup(o => o.CreateModelPublic(bindingContext))
|
||||||
.Returns(new Person())
|
.Returns(new Person())
|
||||||
|
|
@ -402,10 +402,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
public void CanBindProperty_GetSetProperty(string property)
|
public void CanBindProperty_GetSetProperty(string property)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var binder = new TestableMutableObjectModelBinder();
|
|
||||||
|
|
||||||
var metadata = GetMetadataForProperty(typeof(PersonWithBindExclusion), property);
|
var metadata = GetMetadataForProperty(typeof(PersonWithBindExclusion), property);
|
||||||
var context = new DefaultModelBindingContext()
|
var bindingContext = new DefaultModelBindingContext()
|
||||||
{
|
{
|
||||||
ModelMetadata = GetMetadataForType(typeof(PersonWithBindExclusion)),
|
ModelMetadata = GetMetadataForType(typeof(PersonWithBindExclusion)),
|
||||||
OperationBindingContext = new OperationBindingContext()
|
OperationBindingContext = new OperationBindingContext()
|
||||||
|
|
@ -420,8 +418,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = binder.CanBindPropertyPublic(context, metadata);
|
var result = binder.CanBindPropertyPublic(bindingContext, metadata);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
|
|
@ -432,10 +432,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
public void CanBindProperty_GetOnlyProperty_WithBindNever(string property)
|
public void CanBindProperty_GetOnlyProperty_WithBindNever(string property)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var binder = new TestableMutableObjectModelBinder();
|
|
||||||
|
|
||||||
var metadata = GetMetadataForProperty(typeof(PersonWithBindExclusion), property);
|
var metadata = GetMetadataForProperty(typeof(PersonWithBindExclusion), property);
|
||||||
var context = new DefaultModelBindingContext()
|
var bindingContext = new DefaultModelBindingContext()
|
||||||
{
|
{
|
||||||
ModelMetadata = GetMetadataForType(typeof(PersonWithBindExclusion)),
|
ModelMetadata = GetMetadataForType(typeof(PersonWithBindExclusion)),
|
||||||
OperationBindingContext = new OperationBindingContext()
|
OperationBindingContext = new OperationBindingContext()
|
||||||
|
|
@ -450,8 +448,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = binder.CanBindPropertyPublic(context, metadata);
|
var result = binder.CanBindPropertyPublic(bindingContext, metadata);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(result);
|
Assert.False(result);
|
||||||
|
|
@ -463,10 +463,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
public void CanBindProperty_GetSetProperty_WithBindNever(string property)
|
public void CanBindProperty_GetSetProperty_WithBindNever(string property)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var binder = new TestableMutableObjectModelBinder();
|
|
||||||
|
|
||||||
var metadata = GetMetadataForProperty(typeof(PersonWithBindExclusion), property);
|
var metadata = GetMetadataForProperty(typeof(PersonWithBindExclusion), property);
|
||||||
var context = new DefaultModelBindingContext()
|
var bindingContext = new DefaultModelBindingContext()
|
||||||
{
|
{
|
||||||
ModelMetadata = GetMetadataForType(typeof(PersonWithBindExclusion)),
|
ModelMetadata = GetMetadataForType(typeof(PersonWithBindExclusion)),
|
||||||
OperationBindingContext = new OperationBindingContext()
|
OperationBindingContext = new OperationBindingContext()
|
||||||
|
|
@ -481,8 +479,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = binder.CanBindPropertyPublic(context, metadata);
|
var result = binder.CanBindPropertyPublic(bindingContext, metadata);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(result);
|
Assert.False(result);
|
||||||
|
|
@ -496,10 +496,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
public void CanBindProperty_WithPredicate(string property, bool expected)
|
public void CanBindProperty_WithPredicate(string property, bool expected)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var binder = new TestableMutableObjectModelBinder();
|
|
||||||
|
|
||||||
var metadata = GetMetadataForProperty(typeof(TypeWithExcludedPropertiesUsingBindAttribute), property);
|
var metadata = GetMetadataForProperty(typeof(TypeWithExcludedPropertiesUsingBindAttribute), property);
|
||||||
var context = new DefaultModelBindingContext()
|
var bindingContext = new DefaultModelBindingContext()
|
||||||
{
|
{
|
||||||
ModelMetadata = GetMetadataForType(typeof(TypeWithExcludedPropertiesUsingBindAttribute)),
|
ModelMetadata = GetMetadataForType(typeof(TypeWithExcludedPropertiesUsingBindAttribute)),
|
||||||
OperationBindingContext = new OperationBindingContext()
|
OperationBindingContext = new OperationBindingContext()
|
||||||
|
|
@ -514,8 +512,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = binder.CanBindPropertyPublic(context, metadata);
|
var result = binder.CanBindPropertyPublic(bindingContext, metadata);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(expected, result);
|
Assert.Equal(expected, result);
|
||||||
|
|
@ -529,10 +529,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
public void CanBindProperty_WithBindInclude(string property, bool expected)
|
public void CanBindProperty_WithBindInclude(string property, bool expected)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var binder = new TestableMutableObjectModelBinder();
|
|
||||||
|
|
||||||
var metadata = GetMetadataForProperty(typeof(TypeWithIncludedPropertiesUsingBindAttribute), property);
|
var metadata = GetMetadataForProperty(typeof(TypeWithIncludedPropertiesUsingBindAttribute), property);
|
||||||
var context = new DefaultModelBindingContext()
|
var bindingContext = new DefaultModelBindingContext()
|
||||||
{
|
{
|
||||||
ModelMetadata = GetMetadataForType(typeof(TypeWithIncludedPropertiesUsingBindAttribute)),
|
ModelMetadata = GetMetadataForType(typeof(TypeWithIncludedPropertiesUsingBindAttribute)),
|
||||||
OperationBindingContext = new OperationBindingContext()
|
OperationBindingContext = new OperationBindingContext()
|
||||||
|
|
@ -547,8 +545,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = binder.CanBindPropertyPublic(context, metadata);
|
var result = binder.CanBindPropertyPublic(bindingContext, metadata);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(expected, result);
|
Assert.Equal(expected, result);
|
||||||
|
|
@ -561,10 +561,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
public void CanBindProperty_BindingAttributes_OverridingBehavior(string property, bool expected)
|
public void CanBindProperty_BindingAttributes_OverridingBehavior(string property, bool expected)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var binder = new TestableMutableObjectModelBinder();
|
|
||||||
|
|
||||||
var metadata = GetMetadataForProperty(typeof(ModelWithMixedBindingBehaviors), property);
|
var metadata = GetMetadataForProperty(typeof(ModelWithMixedBindingBehaviors), property);
|
||||||
var context = new DefaultModelBindingContext()
|
var bindingContext = new DefaultModelBindingContext()
|
||||||
{
|
{
|
||||||
ModelMetadata = GetMetadataForType(typeof(ModelWithMixedBindingBehaviors)),
|
ModelMetadata = GetMetadataForType(typeof(ModelWithMixedBindingBehaviors)),
|
||||||
OperationBindingContext = new OperationBindingContext()
|
OperationBindingContext = new OperationBindingContext()
|
||||||
|
|
@ -579,8 +577,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = binder.CanBindPropertyPublic(context, metadata);
|
var result = binder.CanBindPropertyPublic(bindingContext, metadata);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(expected, result);
|
Assert.Equal(expected, result);
|
||||||
|
|
@ -597,13 +597,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
Age = -20
|
Age = -20
|
||||||
};
|
};
|
||||||
|
|
||||||
var binder = new TestableMutableObjectModelBinder();
|
|
||||||
|
|
||||||
var property = GetMetadataForProperty(model.GetType(), nameof(ModelWithBindRequired.Age));
|
var property = GetMetadataForProperty(model.GetType(), nameof(ModelWithBindRequired.Age));
|
||||||
binder.Results[property] = ModelBindingResult.Failed("theModel.Age");
|
|
||||||
|
|
||||||
var bindingContext = CreateContext(GetMetadataForType(model.GetType()), model);
|
var bindingContext = CreateContext(GetMetadataForType(model.GetType()), model);
|
||||||
|
|
||||||
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
binder.Results[property] = ModelBindingResult.Failed("theModel.Age");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await binder.BindModelAsync(bindingContext);
|
await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
|
|
@ -632,13 +632,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
Age = -20
|
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));
|
var property = GetMetadataForProperty(model.GetType(), nameof(ModelWithDataMemberIsRequired.Age));
|
||||||
binder.Results[property] = ModelBindingResult.Failed("theModel.Age");
|
binder.Results[property] = ModelBindingResult.Failed("theModel.Age");
|
||||||
|
|
||||||
var bindingContext = CreateContext(GetMetadataForType(model.GetType()), model);
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await binder.BindModelAsync(bindingContext);
|
await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
|
|
@ -667,15 +667,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
Age = -20
|
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
|
// Attempt to set non-Nullable property to null. BindRequiredAttribute should not be relevant in this
|
||||||
// case because the property did have a result.
|
// case because the property did have a result.
|
||||||
var property = GetMetadataForProperty(model.GetType(), nameof(ModelWithBindRequired.Age));
|
var property = GetMetadataForProperty(model.GetType(), nameof(ModelWithBindRequired.Age));
|
||||||
binder.Results[property] = ModelBindingResult.Success("theModel.Age", model: null);
|
binder.Results[property] = ModelBindingResult.Success("theModel.Age", model: null);
|
||||||
|
|
||||||
var bindingContext = CreateContext(GetMetadataForType(model.GetType()), model);
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await binder.BindModelAsync(bindingContext);
|
await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
|
|
@ -701,7 +701,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
var model = new BindingOptionalProperty();
|
var model = new BindingOptionalProperty();
|
||||||
var bindingContext = CreateContext(GetMetadataForType(model.GetType()), model);
|
var bindingContext = CreateContext(GetMetadataForType(model.GetType()), model);
|
||||||
|
|
||||||
var binder = new TestableMutableObjectModelBinder();
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
var property = GetMetadataForProperty(model.GetType(), nameof(BindingOptionalProperty.ValueTypeRequired));
|
var property = GetMetadataForProperty(model.GetType(), nameof(BindingOptionalProperty.ValueTypeRequired));
|
||||||
binder.Results[property] = ModelBindingResult.Failed("theModel.ValueTypeRequired");
|
binder.Results[property] = ModelBindingResult.Failed("theModel.ValueTypeRequired");
|
||||||
|
|
@ -721,7 +721,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
var model = new NullableValueTypeProperty();
|
var model = new NullableValueTypeProperty();
|
||||||
var bindingContext = CreateContext(GetMetadataForType(model.GetType()), model);
|
var bindingContext = CreateContext(GetMetadataForType(model.GetType()), model);
|
||||||
|
|
||||||
var binder = new TestableMutableObjectModelBinder();
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
var property = GetMetadataForProperty(model.GetType(), nameof(NullableValueTypeProperty.NullableValueType));
|
var property = GetMetadataForProperty(model.GetType(), nameof(NullableValueTypeProperty.NullableValueType));
|
||||||
binder.Results[property] = ModelBindingResult.Failed("theModel.NullableValueType");
|
binder.Results[property] = ModelBindingResult.Failed("theModel.NullableValueType");
|
||||||
|
|
@ -743,7 +743,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
|
|
||||||
var bindingContext = CreateContext(containerMetadata, model);
|
var bindingContext = CreateContext(containerMetadata, model);
|
||||||
|
|
||||||
var binder = new TestableMutableObjectModelBinder();
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
var property = GetMetadataForProperty(model.GetType(), nameof(Person.ValueTypeRequired));
|
var property = GetMetadataForProperty(model.GetType(), nameof(Person.ValueTypeRequired));
|
||||||
binder.Results[property] = ModelBindingResult.Failed("theModel." + 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 bindingContext = CreateContext(containerMetadata, model);
|
||||||
|
|
||||||
var binder = new TestableMutableObjectModelBinder();
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
var property = GetMetadataForProperty(model.GetType(), nameof(Person.ValueTypeRequired));
|
var property = GetMetadataForProperty(model.GetType(), nameof(Person.ValueTypeRequired));
|
||||||
binder.Results[property] = ModelBindingResult.Success(
|
binder.Results[property] = ModelBindingResult.Success(
|
||||||
|
|
@ -794,7 +794,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
|
|
||||||
var bindingContext = CreateContext(containerMetadata, model);
|
var bindingContext = CreateContext(containerMetadata, model);
|
||||||
|
|
||||||
var binder = new TestableMutableObjectModelBinder();
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
foreach (var property in containerMetadata.Properties)
|
foreach (var property in containerMetadata.Properties)
|
||||||
{
|
{
|
||||||
|
|
@ -833,10 +833,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
var propertyMetadata = metadata.Properties[nameof(model.PropertyWithDefaultValue)];
|
var propertyMetadata = metadata.Properties[nameof(model.PropertyWithDefaultValue)];
|
||||||
|
|
||||||
var result = ModelBindingResult.Failed("foo");
|
var result = ModelBindingResult.Failed("foo");
|
||||||
var testableBinder = new TestableMutableObjectModelBinder();
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
binder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var person = Assert.IsType<Person>(bindingContext.Model);
|
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.
|
// The null model value won't be used because IsModelBound = false.
|
||||||
var result = ModelBindingResult.Failed("foo");
|
var result = ModelBindingResult.Failed("foo");
|
||||||
|
|
||||||
var testableBinder = new TestableMutableObjectModelBinder();
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
binder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var person = Assert.IsType<Person>(bindingContext.Model);
|
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.
|
// The null model value won't be used because IsModelBound = false.
|
||||||
var result = ModelBindingResult.Failed("foo");
|
var result = ModelBindingResult.Failed("foo");
|
||||||
|
|
||||||
var testableBinder = new TestableMutableObjectModelBinder();
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
binder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var person = Assert.IsType<Person>(bindingContext.Model);
|
var person = Assert.IsType<Person>(bindingContext.Model);
|
||||||
|
|
@ -906,10 +906,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
var propertyMetadata = metadata.Properties[nameof(model.NonUpdateableProperty)];
|
var propertyMetadata = metadata.Properties[nameof(model.NonUpdateableProperty)];
|
||||||
|
|
||||||
var result = ModelBindingResult.Failed("foo");
|
var result = ModelBindingResult.Failed("foo");
|
||||||
var testableBinder = new TestableMutableObjectModelBinder();
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
binder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
// If didn't throw, success!
|
// If didn't throw, success!
|
||||||
|
|
@ -953,10 +953,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
propertyName,
|
propertyName,
|
||||||
new Simple { Name = "Hanna" });
|
new Simple { Name = "Hanna" });
|
||||||
|
|
||||||
var testableBinder = new TestableMutableObjectModelBinder();
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
binder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("Joe", propertyAccessor(model));
|
Assert.Equal("Joe", propertyAccessor(model));
|
||||||
|
|
@ -977,7 +977,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
var bindingContext = CreateContext(modelMetadata, model);
|
var bindingContext = CreateContext(modelMetadata, model);
|
||||||
var result = ModelBindingResult.Success(propertyMetadata.PropertyName, new List<string>() { "hi" });
|
var result = ModelBindingResult.Success(propertyMetadata.PropertyName, new List<string>() { "hi" });
|
||||||
|
|
||||||
var binder = new TestableMutableObjectModelBinder();
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
binder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
binder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
||||||
|
|
@ -999,10 +999,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
var propertyMetadata = bindingContext.ModelMetadata.Properties[nameof(model.DateOfBirth)];
|
var propertyMetadata = bindingContext.ModelMetadata.Properties[nameof(model.DateOfBirth)];
|
||||||
|
|
||||||
var result = ModelBindingResult.Success("foo", new DateTime(2001, 1, 1));
|
var result = ModelBindingResult.Success("foo", new DateTime(2001, 1, 1));
|
||||||
var testableBinder = new TestableMutableObjectModelBinder();
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
binder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(bindingContext.ModelState.IsValid);
|
Assert.True(bindingContext.ModelState.IsValid);
|
||||||
|
|
@ -1026,10 +1026,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
var propertyMetadata = bindingContext.ModelMetadata.Properties[nameof(model.DateOfDeath)];
|
var propertyMetadata = bindingContext.ModelMetadata.Properties[nameof(model.DateOfDeath)];
|
||||||
|
|
||||||
var result = ModelBindingResult.Success("foo", new DateTime(1800, 1, 1));
|
var result = ModelBindingResult.Success("foo", new DateTime(1800, 1, 1));
|
||||||
var testableBinder = new TestableMutableObjectModelBinder();
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
binder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("Date of death can't be before date of birth." + Environment.NewLine
|
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 propertyMetadata = bindingContext.ModelMetadata.Properties[nameof(model.NameNoAttribute)];
|
||||||
|
|
||||||
var result = ModelBindingResult.Success("foo.NameNoAttribute", model: null);
|
var result = ModelBindingResult.Success("foo.NameNoAttribute", model: null);
|
||||||
var testableBinder = new TestableMutableObjectModelBinder();
|
var binder = CreateBinder(bindingContext.ModelMetadata);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
binder.SetPropertyPublic(bindingContext, propertyMetadata, result);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(bindingContext.ModelState.IsValid);
|
Assert.False(bindingContext.ModelState.IsValid);
|
||||||
|
|
@ -1064,6 +1064,30 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
bindingContext.ModelState["foo.NameNoAttribute"].Errors[0].Exception.Message);
|
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)
|
private static DefaultModelBindingContext CreateContext(ModelMetadata metadata, object model = null)
|
||||||
{
|
{
|
||||||
var valueProvider = new TestValueProvider(new Dictionary<string, object>());
|
var valueProvider = new TestValueProvider(new Dictionary<string, object>());
|
||||||
|
|
@ -1281,7 +1305,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
|
|
||||||
private class NonValueBinderMetadataAttribute : Attribute, IBindingSourceMetadata
|
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
|
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 };
|
public IList<int> SettableList { get; set; } = new List<int> { 3, 9, 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provides the ability to easily mock + call each of these APIs
|
private class TestableComplexTypeModelBinderProvider : IModelBinderProvider
|
||||||
public class TestableMutableObjectModelBinder : MutableObjectModelBinder
|
|
||||||
{
|
{
|
||||||
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>();
|
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.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
using Moq;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
{
|
{
|
||||||
public class DictionaryModelBinderTest
|
public class DictionaryModelBinderTest
|
||||||
{
|
{
|
||||||
|
|
@ -23,25 +22,28 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
public async Task BindModel_Succeeds(bool isReadOnly)
|
public async Task BindModel_Succeeds(bool isReadOnly)
|
||||||
{
|
{
|
||||||
// Arrange
|
// 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[0].Key", "42" },
|
||||||
{ "someName[1]", new KeyValuePair<int, string>(84, "eighty-four") },
|
{ "someName[0].Value", "forty-two" },
|
||||||
|
{ "someName[1].Key", "84" },
|
||||||
|
{ "someName[1].Value", "eighty-four" },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Value Provider
|
||||||
|
|
||||||
var bindingContext = GetModelBindingContext(isReadOnly, values);
|
var bindingContext = GetModelBindingContext(isReadOnly, values);
|
||||||
var modelState = bindingContext.ModelState;
|
bindingContext.ValueProvider = CreateEnumerableValueProvider("{0}", values);
|
||||||
var binder = new DictionaryModelBinder<int, string>();
|
|
||||||
|
var binder = new DictionaryModelBinder<int, string>(new SimpleTypeModelBinder(), new SimpleTypeModelBinder());
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await binder.BindModelResultAsync(bindingContext);
|
var result = await binder.BindModelResultAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.NotEqual(default(ModelBindingResult), result);
|
|
||||||
Assert.True(result.IsModelSet);
|
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.NotNull(dictionary);
|
||||||
Assert.Equal(2, dictionary.Count);
|
Assert.Equal(2, dictionary.Count);
|
||||||
Assert.Equal("forty-two", dictionary[42]);
|
Assert.Equal("forty-two", dictionary[42]);
|
||||||
|
|
@ -57,27 +59,29 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
public async Task BindModel_WithExistingModel_Succeeds(bool isReadOnly)
|
public async Task BindModel_WithExistingModel_Succeeds(bool isReadOnly)
|
||||||
{
|
{
|
||||||
// Arrange
|
// 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[0].Key", "42" },
|
||||||
{ "someName[1]", new KeyValuePair<int, string>(84, "eighty-four") },
|
{ "someName[0].Value", "forty-two" },
|
||||||
|
{ "someName[1].Key", "84" },
|
||||||
|
{ "someName[1].Value", "eighty-four" },
|
||||||
};
|
};
|
||||||
|
|
||||||
var bindingContext = GetModelBindingContext(isReadOnly, values);
|
var bindingContext = GetModelBindingContext(isReadOnly, values);
|
||||||
var modelState = bindingContext.ModelState;
|
bindingContext.ValueProvider = CreateEnumerableValueProvider("{0}", values);
|
||||||
|
|
||||||
var dictionary = new Dictionary<int, string>();
|
var dictionary = new Dictionary<int, string>();
|
||||||
bindingContext.Model = dictionary;
|
bindingContext.Model = dictionary;
|
||||||
var binder = new DictionaryModelBinder<int, string>();
|
|
||||||
|
var binder = new DictionaryModelBinder<int, string>(new SimpleTypeModelBinder(), new SimpleTypeModelBinder());
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await binder.BindModelResultAsync(bindingContext);
|
var result = await binder.BindModelResultAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.NotEqual(default(ModelBindingResult), result);
|
|
||||||
Assert.True(result.IsModelSet);
|
Assert.True(result.IsModelSet);
|
||||||
Assert.Same(dictionary, result.Model);
|
|
||||||
Assert.True(modelState.IsValid);
|
|
||||||
|
|
||||||
|
Assert.Same(dictionary, result.Model);
|
||||||
Assert.NotNull(dictionary);
|
Assert.NotNull(dictionary);
|
||||||
Assert.Equal(2, dictionary.Count);
|
Assert.Equal(2, dictionary.Count);
|
||||||
Assert.Equal("forty-two", dictionary[42]);
|
Assert.Equal("forty-two", dictionary[42]);
|
||||||
|
|
@ -123,10 +127,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
IDictionary<string, string> dictionary)
|
IDictionary<string, string> dictionary)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var binder = new DictionaryModelBinder<string, string>();
|
var binder = new DictionaryModelBinder<string, string>(new SimpleTypeModelBinder(), new SimpleTypeModelBinder());
|
||||||
|
|
||||||
var context = CreateContext();
|
var context = CreateContext();
|
||||||
context.ModelName = modelName;
|
context.ModelName = modelName;
|
||||||
context.OperationBindingContext.ModelBinder = CreateCompositeBinder();
|
|
||||||
context.OperationBindingContext.ValueProvider = CreateEnumerableValueProvider(keyFormat, dictionary);
|
context.OperationBindingContext.ValueProvider = CreateEnumerableValueProvider(keyFormat, dictionary);
|
||||||
context.ValueProvider = context.OperationBindingContext.ValueProvider;
|
context.ValueProvider = context.OperationBindingContext.ValueProvider;
|
||||||
context.FieldName = modelName;
|
context.FieldName = modelName;
|
||||||
|
|
@ -140,7 +144,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
var result = await binder.BindModelResultAsync(context);
|
var result = await binder.BindModelResultAsync(context);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.NotEqual(default(ModelBindingResult), result);
|
|
||||||
Assert.True(result.IsModelSet);
|
Assert.True(result.IsModelSet);
|
||||||
Assert.Equal(modelName, result.Key);
|
Assert.Equal(modelName, result.Key);
|
||||||
|
|
||||||
|
|
@ -160,10 +163,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
{ "three", "three" },
|
{ "three", "three" },
|
||||||
};
|
};
|
||||||
|
|
||||||
var binder = new DictionaryModelBinder<string, string>();
|
var binder = new DictionaryModelBinder<string, string>(new SimpleTypeModelBinder(), new SimpleTypeModelBinder());
|
||||||
|
|
||||||
var context = CreateContext();
|
var context = CreateContext();
|
||||||
context.ModelName = "prefix";
|
context.ModelName = "prefix";
|
||||||
context.OperationBindingContext.ModelBinder = CreateCompositeBinder();
|
|
||||||
context.OperationBindingContext.ValueProvider = CreateTestValueProvider("prefix[{0}]", dictionary);
|
context.OperationBindingContext.ValueProvider = CreateTestValueProvider("prefix[{0}]", dictionary);
|
||||||
context.ValueProvider = context.OperationBindingContext.ValueProvider;
|
context.ValueProvider = context.OperationBindingContext.ValueProvider;
|
||||||
context.FieldName = context.ModelName;
|
context.FieldName = context.ModelName;
|
||||||
|
|
@ -210,10 +213,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var stringDictionary = dictionary.ToDictionary(kvp => kvp.Key.ToString(), kvp => kvp.Value.ToString());
|
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();
|
var context = CreateContext();
|
||||||
context.ModelName = "prefix";
|
context.ModelName = "prefix";
|
||||||
context.OperationBindingContext.ModelBinder = CreateCompositeBinder();
|
|
||||||
context.OperationBindingContext.ValueProvider =
|
context.OperationBindingContext.ValueProvider =
|
||||||
CreateEnumerableValueProvider("prefix[{0}]", stringDictionary);
|
CreateEnumerableValueProvider("prefix[{0}]", stringDictionary);
|
||||||
context.ValueProvider = context.OperationBindingContext.ValueProvider;
|
context.ValueProvider = context.OperationBindingContext.ValueProvider;
|
||||||
|
|
@ -252,10 +256,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
{ "prefix[27].Id", "98" },
|
{ "prefix[27].Id", "98" },
|
||||||
{ "prefix[27].Name", "Fred" },
|
{ "prefix[27].Name", "Fred" },
|
||||||
};
|
};
|
||||||
var binder = new DictionaryModelBinder<int, ModelWithProperties>();
|
|
||||||
var context = CreateContext();
|
var context = CreateContext();
|
||||||
context.ModelName = "prefix";
|
context.ModelName = "prefix";
|
||||||
context.OperationBindingContext.ModelBinder = CreateCompositeBinder();
|
|
||||||
context.OperationBindingContext.ValueProvider = CreateEnumerableValueProvider("{0}", stringDictionary);
|
context.OperationBindingContext.ValueProvider = CreateEnumerableValueProvider("{0}", stringDictionary);
|
||||||
context.ValueProvider = context.OperationBindingContext.ValueProvider;
|
context.ValueProvider = context.OperationBindingContext.ValueProvider;
|
||||||
context.FieldName = context.ModelName;
|
context.FieldName = context.ModelName;
|
||||||
|
|
@ -265,6 +268,16 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
typeof(ModelWithDictionaryProperties),
|
typeof(ModelWithDictionaryProperties),
|
||||||
nameof(ModelWithDictionaryProperties.DictionaryWithComplexValuesProperty));
|
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
|
// Act
|
||||||
var result = await binder.BindModelResultAsync(context);
|
var result = await binder.BindModelResultAsync(context);
|
||||||
|
|
||||||
|
|
@ -298,10 +311,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expectedDictionary = new SortedDictionary<string, string>(dictionary);
|
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();
|
var context = CreateContext();
|
||||||
context.ModelName = modelName;
|
context.ModelName = modelName;
|
||||||
context.OperationBindingContext.ModelBinder = CreateCompositeBinder();
|
|
||||||
context.OperationBindingContext.ValueProvider = CreateEnumerableValueProvider(keyFormat, dictionary);
|
context.OperationBindingContext.ValueProvider = CreateEnumerableValueProvider(keyFormat, dictionary);
|
||||||
context.ValueProvider = context.OperationBindingContext.ValueProvider;
|
context.ValueProvider = context.OperationBindingContext.ValueProvider;
|
||||||
context.FieldName = context.ModelName;
|
context.FieldName = context.ModelName;
|
||||||
|
|
@ -327,7 +341,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
public async Task DictionaryModelBinder_CreatesEmptyCollection_IfIsTopLevelObject()
|
public async Task DictionaryModelBinder_CreatesEmptyCollection_IfIsTopLevelObject()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var binder = new DictionaryModelBinder<string, string>();
|
var binder = new DictionaryModelBinder<string, string>(new SimpleTypeModelBinder(), new SimpleTypeModelBinder());
|
||||||
|
|
||||||
var context = CreateContext();
|
var context = CreateContext();
|
||||||
context.IsTopLevelObject = true;
|
context.IsTopLevelObject = true;
|
||||||
|
|
@ -357,7 +371,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
public async Task DictionaryModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(string prefix)
|
public async Task DictionaryModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(string prefix)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var binder = new DictionaryModelBinder<string, string>();
|
var binder = new DictionaryModelBinder<int, int>(new SimpleTypeModelBinder(), new SimpleTypeModelBinder());
|
||||||
|
|
||||||
var context = CreateContext();
|
var context = CreateContext();
|
||||||
context.ModelName = ModelNames.CreatePropertyModelName(prefix, "ListProperty");
|
context.ModelName = ModelNames.CreatePropertyModelName(prefix, "ListProperty");
|
||||||
|
|
@ -399,7 +413,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
public void CanCreateInstance_ReturnsExpectedValue(Type modelType, bool expectedResult)
|
public void CanCreateInstance_ReturnsExpectedValue(Type modelType, bool expectedResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var binder = new DictionaryModelBinder<int, int>();
|
var binder = new DictionaryModelBinder<int, int>(new SimpleTypeModelBinder(), new SimpleTypeModelBinder());
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = binder.CanCreateInstance(modelType);
|
var result = binder.CanCreateInstance(modelType);
|
||||||
|
|
@ -427,17 +441,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
return modelBindingContext;
|
return modelBindingContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IModelBinder CreateCompositeBinder()
|
|
||||||
{
|
|
||||||
var binders = new IModelBinder[]
|
|
||||||
{
|
|
||||||
new SimpleTypeModelBinder(),
|
|
||||||
new MutableObjectModelBinder(),
|
|
||||||
};
|
|
||||||
|
|
||||||
return new CompositeModelBinder(binders);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IValueProvider CreateEnumerableValueProvider(
|
private static IValueProvider CreateEnumerableValueProvider(
|
||||||
string keyFormat,
|
string keyFormat,
|
||||||
IDictionary<string, string> dictionary)
|
IDictionary<string, string> dictionary)
|
||||||
|
|
@ -468,7 +471,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
|
|
||||||
private static DefaultModelBindingContext GetModelBindingContext(
|
private static DefaultModelBindingContext GetModelBindingContext(
|
||||||
bool isReadOnly,
|
bool isReadOnly,
|
||||||
IDictionary<string, KeyValuePair<int, string>> values)
|
IDictionary<string, string> values = null)
|
||||||
{
|
{
|
||||||
var metadataProvider = new TestModelMetadataProvider();
|
var metadataProvider = new TestModelMetadataProvider();
|
||||||
metadataProvider
|
metadataProvider
|
||||||
|
|
@ -478,15 +481,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
typeof(ModelWithIDictionaryProperty),
|
typeof(ModelWithIDictionaryProperty),
|
||||||
nameof(ModelWithIDictionaryProperty.DictionaryProperty));
|
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();
|
var valueProvider = new SimpleValueProvider();
|
||||||
foreach (var kvp in values)
|
foreach (var kvp in values)
|
||||||
{
|
{
|
||||||
|
|
@ -500,7 +494,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
ModelState = new ModelStateDictionary(),
|
ModelState = new ModelStateDictionary(),
|
||||||
OperationBindingContext = new OperationBindingContext
|
OperationBindingContext = new OperationBindingContext
|
||||||
{
|
{
|
||||||
ModelBinder = binder.Object,
|
|
||||||
MetadataProvider = metadataProvider,
|
MetadataProvider = metadataProvider,
|
||||||
ValueProvider = valueProvider,
|
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 Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
{
|
{
|
||||||
public class FormCollectionModelBinderTest
|
public class FormCollectionModelBinderTest
|
||||||
{
|
{
|
||||||
|
|
@ -47,47 +47,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
Assert.Equal("value2", form["field2"]);
|
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]
|
[Fact]
|
||||||
public async Task FormCollectionModelBinder_NoForm_BindSuccessful_ReturnsEmptyFormCollection()
|
public async Task FormCollectionModelBinder_NoForm_BindSuccessful_ReturnsEmptyFormCollection()
|
||||||
{
|
{
|
||||||
|
|
@ -127,7 +86,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
HttpContext = httpContext,
|
HttpContext = httpContext,
|
||||||
},
|
},
|
||||||
ModelBinder = new FormCollectionModelBinder(),
|
|
||||||
MetadataProvider = metadataProvider,
|
MetadataProvider = metadataProvider,
|
||||||
},
|
},
|
||||||
ValidationState = new ValidationStateDictionary(),
|
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 Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
{
|
{
|
||||||
public class FormFileModelBinderTest
|
public class FormFileModelBinderTest
|
||||||
{
|
{
|
||||||
|
|
@ -108,24 +108,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
Assert.Equal("file1.txt", file.FileName);
|
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]
|
[Fact]
|
||||||
public async Task FormFileModelBinder_ReturnsFailedResult_WhenNoFilePosted()
|
public async Task FormFileModelBinder_ReturnsFailedResult_WhenNoFilePosted()
|
||||||
{
|
{
|
||||||
|
|
@ -303,7 +285,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
HttpContext = httpContext,
|
HttpContext = httpContext,
|
||||||
},
|
},
|
||||||
ModelBinder = new FormFileModelBinder(),
|
|
||||||
MetadataProvider = metadataProvider,
|
MetadataProvider = metadataProvider,
|
||||||
},
|
},
|
||||||
ValidationState = new ValidationStateDictionary(),
|
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 Microsoft.AspNetCore.Http.Internal;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
{
|
{
|
||||||
public class HeaderModelBinderTests
|
public class HeaderModelBinderTests
|
||||||
{
|
{
|
||||||
|
|
@ -101,50 +101,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
Assert.Equal(headerValue.Split(','), result.Model as IEnumerable<string>);
|
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]
|
[Fact]
|
||||||
public async Task HeaderBinder_ReturnsFailedResult_ForReadOnlyDestination()
|
public async Task HeaderBinder_ReturnsFailedResult_ForReadOnlyDestination()
|
||||||
{
|
{
|
||||||
|
|
@ -227,7 +183,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
{
|
{
|
||||||
HttpContext = new DefaultHttpContext(),
|
HttpContext = new DefaultHttpContext(),
|
||||||
},
|
},
|
||||||
ModelBinder = new HeaderModelBinder(),
|
|
||||||
MetadataProvider = metadataProvider,
|
MetadataProvider = metadataProvider,
|
||||||
},
|
},
|
||||||
BinderModelName = modelMetadata.BinderModelName,
|
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.Http.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.DataAnnotations;
|
using Microsoft.AspNetCore.Mvc.DataAnnotations;
|
||||||
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
|
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
|
||||||
using Moq;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
{
|
{
|
||||||
public class KeyValuePairModelBinderTest
|
public class KeyValuePairModelBinderTest
|
||||||
{
|
{
|
||||||
|
|
@ -22,8 +20,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
var valueProvider = new SimpleValueProvider();
|
var valueProvider = new SimpleValueProvider();
|
||||||
|
|
||||||
// Create string binder to create the value but not the key.
|
// Create string binder to create the value but not the key.
|
||||||
var bindingContext = GetBindingContext(valueProvider, CreateStringBinder());
|
var bindingContext = GetBindingContext(valueProvider, typeof(KeyValuePair<int, string>));
|
||||||
var binder = new KeyValuePairModelBinder<int, string>();
|
var binder = new KeyValuePairModelBinder<int, string>(CreateIntBinder(false), CreateStringBinder());
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await binder.BindModelResultAsync(bindingContext);
|
var result = await binder.BindModelResultAsync(bindingContext);
|
||||||
|
|
@ -44,9 +42,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
var valueProvider = new SimpleValueProvider();
|
var valueProvider = new SimpleValueProvider();
|
||||||
|
|
||||||
// Create int binder to create the value but not the key.
|
// Create int binder to create the value but not the key.
|
||||||
var bindingContext = GetBindingContext(valueProvider, CreateIntBinder());
|
var bindingContext = GetBindingContext(valueProvider, typeof(KeyValuePair<int, string>));
|
||||||
var binder = new KeyValuePairModelBinder<int, string>();
|
var binder = new KeyValuePairModelBinder<int, string>(CreateIntBinder(), CreateStringBinder(false));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await binder.BindModelResultAsync(bindingContext);
|
var result = await binder.BindModelResultAsync(bindingContext);
|
||||||
|
|
||||||
|
|
@ -67,11 +65,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
var valueProvider = new SimpleValueProvider();
|
var valueProvider = new SimpleValueProvider();
|
||||||
|
|
||||||
// Create int binder to create the value but not the key.
|
// Create int binder to create the value but not the key.
|
||||||
var bindingContext = GetBindingContext(valueProvider);
|
var bindingContext = GetBindingContext(valueProvider, typeof(KeyValuePair<int, string>));
|
||||||
var mockBinder = new StubModelBinder();
|
var binder = new KeyValuePairModelBinder<int, string>(CreateIntBinder(false), CreateStringBinder(false));
|
||||||
|
|
||||||
bindingContext.OperationBindingContext.ModelBinder = mockBinder;
|
|
||||||
var binder = new KeyValuePairModelBinder<int, string>();
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await binder.BindModelResultAsync(bindingContext);
|
var result = await binder.BindModelResultAsync(bindingContext);
|
||||||
|
|
@ -86,11 +81,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
public async Task BindModel_SubBindingSucceeds()
|
public async Task BindModel_SubBindingSucceeds()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var innerBinder = new CompositeModelBinder(new[] { CreateStringBinder(), CreateIntBinder() });
|
|
||||||
var valueProvider = new SimpleValueProvider();
|
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
|
// Act
|
||||||
var result = await binder.BindModelResultAsync(bindingContext);
|
var result = await binder.BindModelResultAsync(bindingContext);
|
||||||
|
|
@ -124,12 +118,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
Assert.Equal("someName.key", context.ModelName);
|
Assert.Equal("someName.key", context.ModelName);
|
||||||
return innerResult;
|
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
|
// Act
|
||||||
var result = await binder.TryBindStrongModel<int>(bindingContext, "key");
|
var result = await binder.TryBindStrongModel<int>(bindingContext, innerBinder, "key");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(innerResult.Value, result);
|
Assert.Equal(innerResult.Value, result);
|
||||||
Assert.Empty(bindingContext.ModelState);
|
Assert.Empty(bindingContext.ModelState);
|
||||||
|
|
@ -139,7 +136,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
public async Task KeyValuePairModelBinder_CreatesEmptyCollection_IfIsTopLevelObject()
|
public async Task KeyValuePairModelBinder_CreatesEmptyCollection_IfIsTopLevelObject()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var binder = new KeyValuePairModelBinder<string, string>();
|
var binder = new KeyValuePairModelBinder<string, string>(new SimpleTypeModelBinder(), new SimpleTypeModelBinder());
|
||||||
|
|
||||||
var context = CreateContext();
|
var context = CreateContext();
|
||||||
context.IsTopLevelObject = true;
|
context.IsTopLevelObject = true;
|
||||||
|
|
@ -170,7 +167,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
public async Task KeyValuePairModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(string prefix)
|
public async Task KeyValuePairModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(string prefix)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var binder = new KeyValuePairModelBinder<string, string>();
|
var binder = new KeyValuePairModelBinder<string, string>(new SimpleTypeModelBinder(), new SimpleTypeModelBinder());
|
||||||
|
|
||||||
var context = CreateContext();
|
var context = CreateContext();
|
||||||
context.ModelName = ModelNames.CreatePropertyModelName(prefix, "KeyValuePairProperty");
|
context.ModelName = ModelNames.CreatePropertyModelName(prefix, "KeyValuePairProperty");
|
||||||
|
|
@ -200,7 +197,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
HttpContext = new DefaultHttpContext(),
|
HttpContext = new DefaultHttpContext(),
|
||||||
},
|
},
|
||||||
MetadataProvider = new TestModelMetadataProvider(),
|
MetadataProvider = new TestModelMetadataProvider(),
|
||||||
ModelBinder = new SimpleTypeModelBinder(),
|
|
||||||
},
|
},
|
||||||
ModelState = new ModelStateDictionary(),
|
ModelState = new ModelStateDictionary(),
|
||||||
};
|
};
|
||||||
|
|
@ -210,20 +206,17 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
|
|
||||||
private static DefaultModelBindingContext GetBindingContext(
|
private static DefaultModelBindingContext GetBindingContext(
|
||||||
IValueProvider valueProvider,
|
IValueProvider valueProvider,
|
||||||
IModelBinder innerBinder = null,
|
Type keyValuePairType)
|
||||||
Type keyValuePairType = null)
|
|
||||||
{
|
{
|
||||||
var metataProvider = new EmptyModelMetadataProvider();
|
var metataProvider = new EmptyModelMetadataProvider();
|
||||||
var bindingContext = new DefaultModelBindingContext
|
var bindingContext = new DefaultModelBindingContext
|
||||||
{
|
{
|
||||||
ModelMetadata = metataProvider.GetMetadataForType(
|
ModelMetadata = metataProvider.GetMetadataForType(keyValuePairType),
|
||||||
keyValuePairType ?? typeof(KeyValuePair<int, string>)),
|
|
||||||
ModelName = "someName",
|
ModelName = "someName",
|
||||||
ModelState = new ModelStateDictionary(),
|
ModelState = new ModelStateDictionary(),
|
||||||
ValueProvider = valueProvider,
|
ValueProvider = valueProvider,
|
||||||
OperationBindingContext = new OperationBindingContext
|
OperationBindingContext = new OperationBindingContext
|
||||||
{
|
{
|
||||||
ModelBinder = innerBinder ?? CreateIntBinder(),
|
|
||||||
MetadataProvider = metataProvider,
|
MetadataProvider = metataProvider,
|
||||||
ValidatorProvider = new DataAnnotationsModelValidatorProvider(
|
ValidatorProvider = new DataAnnotationsModelValidatorProvider(
|
||||||
new ValidationAttributeAdapterProvider(),
|
new ValidationAttributeAdapterProvider(),
|
||||||
|
|
@ -234,11 +227,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
return bindingContext;
|
return bindingContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IModelBinder CreateIntBinder()
|
private static IModelBinder CreateIntBinder(bool success = true)
|
||||||
{
|
{
|
||||||
var mockIntBinder = new StubModelBinder(mbc =>
|
var mockIntBinder = new StubModelBinder(mbc =>
|
||||||
{
|
{
|
||||||
if (mbc.ModelType == typeof(int))
|
if (mbc.ModelType == typeof(int) && success)
|
||||||
{
|
{
|
||||||
var model = 42;
|
var model = 42;
|
||||||
return ModelBindingResult.Success(mbc.ModelName, model);
|
return ModelBindingResult.Success(mbc.ModelName, model);
|
||||||
|
|
@ -248,11 +241,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
return mockIntBinder;
|
return mockIntBinder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IModelBinder CreateStringBinder()
|
private static IModelBinder CreateStringBinder(bool success = true)
|
||||||
{
|
{
|
||||||
return new StubModelBinder(mbc =>
|
return new StubModelBinder(mbc =>
|
||||||
{
|
{
|
||||||
if (mbc.ModelType == typeof(string))
|
if (mbc.ModelType == typeof(string) && success)
|
||||||
{
|
{
|
||||||
var model = "some-value";
|
var model = "some-value";
|
||||||
return ModelBindingResult.Success(mbc.ModelName, model);
|
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 Microsoft.Extensions.DependencyInjection;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
{
|
{
|
||||||
public class ServicesModelBinderTest
|
public class ServicesModelBinderTest
|
||||||
{
|
{
|
||||||
|
|
@ -36,40 +36,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
Assert.Null(entry.Metadata);
|
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)
|
private static DefaultModelBindingContext GetBindingContext(Type modelType)
|
||||||
{
|
{
|
||||||
var metadataProvider = new TestModelMetadataProvider();
|
var metadataProvider = new TestModelMetadataProvider();
|
||||||
|
|
@ -95,7 +61,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
RequestServices = services.BuildServiceProvider(),
|
RequestServices = services.BuildServiceProvider(),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ModelBinder = new HeaderModelBinder(),
|
|
||||||
MetadataProvider = metadataProvider,
|
MetadataProvider = metadataProvider,
|
||||||
},
|
},
|
||||||
BinderModelName = modelMetadata.BinderModelName,
|
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 Microsoft.AspNetCore.Testing;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
{
|
{
|
||||||
public class SimpleTypeModelBinderTest
|
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
|
public static TheoryData<Type> ConvertableTypeData
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -134,7 +112,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task BindModel_NullValueProviderResult_ReturnsNull()
|
public async Task BindModel_EmptyValueProviderResult_ReturnsFailed()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var bindingContext = GetBindingContext(typeof(int));
|
var bindingContext = GetBindingContext(typeof(int));
|
||||||
|
|
@ -144,7 +122,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
var result = await binder.BindModelResultAsync(bindingContext);
|
var result = await binder.BindModelResultAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(default(ModelBindingResult), result);
|
Assert.Equal(ModelBindingResult.Failed("theModelName"), result);
|
||||||
Assert.Empty(bindingContext.ModelState);
|
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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
using System.Threading.Tasks;
|
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;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
|
|
@ -37,7 +41,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var originalBinderModelName = bindingContext.BinderModelName;
|
var originalBinderModelName = bindingContext.BinderModelName;
|
||||||
var originalBinderType = bindingContext.BinderType;
|
|
||||||
var originalBindingSource = bindingContext.BindingSource;
|
var originalBindingSource = bindingContext.BindingSource;
|
||||||
var originalModelState = bindingContext.ModelState;
|
var originalModelState = bindingContext.ModelState;
|
||||||
var originalOperationBindingContext = bindingContext.OperationBindingContext;
|
var originalOperationBindingContext = bindingContext.OperationBindingContext;
|
||||||
|
|
@ -51,9 +54,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Same(newModelMetadata.BinderModelName, bindingContext.BinderModelName);
|
Assert.Same(newModelMetadata.BinderModelName, bindingContext.BinderModelName);
|
||||||
Assert.Same(newModelMetadata.BinderType, bindingContext.BinderType);
|
|
||||||
Assert.Same(newModelMetadata.BindingSource, bindingContext.BindingSource);
|
Assert.Same(newModelMetadata.BindingSource, bindingContext.BindingSource);
|
||||||
Assert.False(bindingContext.FallbackToEmptyPrefix);
|
|
||||||
Assert.Equal("fieldName", bindingContext.FieldName);
|
Assert.Equal("fieldName", bindingContext.FieldName);
|
||||||
Assert.False(bindingContext.IsTopLevelObject);
|
Assert.False(bindingContext.IsTopLevelObject);
|
||||||
Assert.Null(bindingContext.Model);
|
Assert.Null(bindingContext.Model);
|
||||||
|
|
@ -66,6 +67,98 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
disposable.Dispose();
|
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]
|
[Fact]
|
||||||
public void ModelTypeAreFedFromModelMetadata()
|
public void ModelTypeAreFedFromModelMetadata()
|
||||||
{
|
{
|
||||||
|
|
@ -79,6 +172,21 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
Assert.Equal(typeof(int), bindingContext.ModelType);
|
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
|
private class TestModelBinder : IModelBinder
|
||||||
{
|
{
|
||||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
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.DataAnnotations.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Test;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Test;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
using Moq;
|
using Moq;
|
||||||
|
|
@ -49,7 +50,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
string.Empty,
|
string.Empty,
|
||||||
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
||||||
metadataProvider,
|
metadataProvider,
|
||||||
GetCompositeBinder(binder),
|
GetModelBinderFactory(binder),
|
||||||
Mock.Of<IValueProvider>(),
|
Mock.Of<IValueProvider>(),
|
||||||
new List<IInputFormatter>(),
|
new List<IInputFormatter>(),
|
||||||
new Mock<IObjectModelValidator>(MockBehavior.Strict).Object,
|
new Mock<IObjectModelValidator>(MockBehavior.Strict).Object,
|
||||||
|
|
@ -66,10 +67,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
// Arrange
|
// Arrange
|
||||||
// Mono issue - https://github.com/aspnet/External/issues/19
|
// Mono issue - https://github.com/aspnet/External/issues/19
|
||||||
var expectedMessage = PlatformNormalizer.NormalizeContent("The MyProperty field is required.");
|
var expectedMessage = PlatformNormalizer.NormalizeContent("The MyProperty field is required.");
|
||||||
var binders = new IModelBinder[]
|
var binderProviders = new IModelBinderProvider[]
|
||||||
{
|
{
|
||||||
new SimpleTypeModelBinder(),
|
new SimpleTypeModelBinderProvider(),
|
||||||
new MutableObjectModelBinder()
|
new ComplexTypeModelBinderProvider(),
|
||||||
};
|
};
|
||||||
|
|
||||||
var validator = new DataAnnotationsModelValidatorProvider(
|
var validator = new DataAnnotationsModelValidatorProvider(
|
||||||
|
|
@ -94,7 +95,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
"",
|
"",
|
||||||
actionContext,
|
actionContext,
|
||||||
modelMetadataProvider,
|
modelMetadataProvider,
|
||||||
GetCompositeBinder(binders),
|
GetModelBinderFactory(binderProviders),
|
||||||
valueProvider,
|
valueProvider,
|
||||||
new List<IInputFormatter>(),
|
new List<IInputFormatter>(),
|
||||||
new DefaultObjectValidator(modelMetadataProvider, new ValidatorCache()),
|
new DefaultObjectValidator(modelMetadataProvider, new ValidatorCache()),
|
||||||
|
|
@ -110,10 +111,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
public async Task TryUpdateModel_ReturnsTrue_IfModelBindsAndValidatesSuccessfully()
|
public async Task TryUpdateModel_ReturnsTrue_IfModelBindsAndValidatesSuccessfully()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var binders = new IModelBinder[]
|
var binderProviders = new IModelBinderProvider[]
|
||||||
{
|
{
|
||||||
new SimpleTypeModelBinder(),
|
new SimpleTypeModelBinderProvider(),
|
||||||
new MutableObjectModelBinder()
|
new ComplexTypeModelBinderProvider(),
|
||||||
};
|
};
|
||||||
|
|
||||||
var validator = new DataAnnotationsModelValidatorProvider(
|
var validator = new DataAnnotationsModelValidatorProvider(
|
||||||
|
|
@ -136,7 +137,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
"",
|
"",
|
||||||
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
||||||
metadataProvider,
|
metadataProvider,
|
||||||
GetCompositeBinder(binders),
|
GetModelBinderFactory(binderProviders),
|
||||||
valueProvider,
|
valueProvider,
|
||||||
new List<IInputFormatter>(),
|
new List<IInputFormatter>(),
|
||||||
new DefaultObjectValidator(metadataProvider, new ValidatorCache()),
|
new DefaultObjectValidator(metadataProvider, new ValidatorCache()),
|
||||||
|
|
@ -164,7 +165,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
string.Empty,
|
string.Empty,
|
||||||
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
||||||
metadataProvider,
|
metadataProvider,
|
||||||
GetCompositeBinder(binder),
|
GetModelBinderFactory(binder),
|
||||||
Mock.Of<IValueProvider>(),
|
Mock.Of<IValueProvider>(),
|
||||||
new List<IInputFormatter>(),
|
new List<IInputFormatter>(),
|
||||||
new Mock<IObjectModelValidator>(MockBehavior.Strict).Object,
|
new Mock<IObjectModelValidator>(MockBehavior.Strict).Object,
|
||||||
|
|
@ -182,10 +183,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
public async Task TryUpdateModel_UsingIncludePredicateOverload_ReturnsTrue_ModelBindsAndValidatesSuccessfully()
|
public async Task TryUpdateModel_UsingIncludePredicateOverload_ReturnsTrue_ModelBindsAndValidatesSuccessfully()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var binders = new IModelBinder[]
|
var binderProviders = new IModelBinderProvider[]
|
||||||
{
|
{
|
||||||
new SimpleTypeModelBinder(),
|
new SimpleTypeModelBinderProvider(),
|
||||||
new MutableObjectModelBinder()
|
new ComplexTypeModelBinderProvider(),
|
||||||
};
|
};
|
||||||
|
|
||||||
var validator = new DataAnnotationsModelValidatorProvider(
|
var validator = new DataAnnotationsModelValidatorProvider(
|
||||||
|
|
@ -220,7 +221,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
"",
|
"",
|
||||||
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
||||||
metadataProvider,
|
metadataProvider,
|
||||||
GetCompositeBinder(binders),
|
GetModelBinderFactory(binderProviders),
|
||||||
valueProvider,
|
valueProvider,
|
||||||
new List<IInputFormatter>(),
|
new List<IInputFormatter>(),
|
||||||
new DefaultObjectValidator(metadataProvider, new ValidatorCache()),
|
new DefaultObjectValidator(metadataProvider, new ValidatorCache()),
|
||||||
|
|
@ -250,7 +251,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
string.Empty,
|
string.Empty,
|
||||||
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
||||||
metadataProvider,
|
metadataProvider,
|
||||||
GetCompositeBinder(binder),
|
GetModelBinderFactory(binder),
|
||||||
Mock.Of<IValueProvider>(),
|
Mock.Of<IValueProvider>(),
|
||||||
new List<IInputFormatter>(),
|
new List<IInputFormatter>(),
|
||||||
new Mock<IObjectModelValidator>(MockBehavior.Strict).Object,
|
new Mock<IObjectModelValidator>(MockBehavior.Strict).Object,
|
||||||
|
|
@ -268,10 +269,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
public async Task TryUpdateModel_UsingIncludeExpressionOverload_ReturnsTrue_ModelBindsAndValidatesSuccessfully()
|
public async Task TryUpdateModel_UsingIncludeExpressionOverload_ReturnsTrue_ModelBindsAndValidatesSuccessfully()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var binders = new IModelBinder[]
|
var binderProviders = new IModelBinderProvider[]
|
||||||
{
|
{
|
||||||
new SimpleTypeModelBinder(),
|
new SimpleTypeModelBinderProvider(),
|
||||||
new MutableObjectModelBinder()
|
new ComplexTypeModelBinderProvider(),
|
||||||
};
|
};
|
||||||
|
|
||||||
var validator = new DataAnnotationsModelValidatorProvider(
|
var validator = new DataAnnotationsModelValidatorProvider(
|
||||||
|
|
@ -302,7 +303,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
"",
|
"",
|
||||||
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
||||||
TestModelMetadataProvider.CreateDefaultProvider(),
|
TestModelMetadataProvider.CreateDefaultProvider(),
|
||||||
GetCompositeBinder(binders),
|
GetModelBinderFactory(binderProviders),
|
||||||
valueProvider,
|
valueProvider,
|
||||||
new List<IInputFormatter>(),
|
new List<IInputFormatter>(),
|
||||||
new DefaultObjectValidator(metadataProvider, new ValidatorCache()),
|
new DefaultObjectValidator(metadataProvider, new ValidatorCache()),
|
||||||
|
|
@ -321,10 +322,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
public async Task TryUpdateModel_UsingDefaultIncludeOverload_IncludesAllProperties()
|
public async Task TryUpdateModel_UsingDefaultIncludeOverload_IncludesAllProperties()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var binders = new IModelBinder[]
|
var binderProviders = new IModelBinderProvider[]
|
||||||
{
|
{
|
||||||
new SimpleTypeModelBinder(),
|
new SimpleTypeModelBinderProvider(),
|
||||||
new MutableObjectModelBinder()
|
new ComplexTypeModelBinderProvider(),
|
||||||
};
|
};
|
||||||
|
|
||||||
var validator = new DataAnnotationsModelValidatorProvider(
|
var validator = new DataAnnotationsModelValidatorProvider(
|
||||||
|
|
@ -355,7 +356,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
"",
|
"",
|
||||||
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
||||||
metadataProvider,
|
metadataProvider,
|
||||||
GetCompositeBinder(binders),
|
GetModelBinderFactory(binderProviders),
|
||||||
valueProvider,
|
valueProvider,
|
||||||
new List<IInputFormatter>(),
|
new List<IInputFormatter>(),
|
||||||
new DefaultObjectValidator(metadataProvider, new ValidatorCache()),
|
new DefaultObjectValidator(metadataProvider, new ValidatorCache()),
|
||||||
|
|
@ -506,7 +507,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
prefix: "",
|
prefix: "",
|
||||||
actionContext: new ActionContext() { HttpContext = new DefaultHttpContext() },
|
actionContext: new ActionContext() { HttpContext = new DefaultHttpContext() },
|
||||||
metadataProvider: metadataProvider,
|
metadataProvider: metadataProvider,
|
||||||
modelBinder: GetCompositeBinder(binder),
|
modelBinderFactory: GetModelBinderFactory(binder),
|
||||||
valueProvider: Mock.Of<IValueProvider>(),
|
valueProvider: Mock.Of<IValueProvider>(),
|
||||||
inputFormatters: new List<IInputFormatter>(),
|
inputFormatters: new List<IInputFormatter>(),
|
||||||
objectModelValidator: new Mock<IObjectModelValidator>(MockBehavior.Strict).Object,
|
objectModelValidator: new Mock<IObjectModelValidator>(MockBehavior.Strict).Object,
|
||||||
|
|
@ -524,10 +525,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
public async Task TryUpdateModelNonGeneric_PredicateOverload_ReturnsTrue_ModelBindsAndValidatesSuccessfully()
|
public async Task TryUpdateModelNonGeneric_PredicateOverload_ReturnsTrue_ModelBindsAndValidatesSuccessfully()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var binders = new IModelBinder[]
|
var binderProviders = new IModelBinderProvider[]
|
||||||
{
|
{
|
||||||
new SimpleTypeModelBinder(),
|
new SimpleTypeModelBinderProvider(),
|
||||||
new MutableObjectModelBinder()
|
new ComplexTypeModelBinderProvider(),
|
||||||
};
|
};
|
||||||
|
|
||||||
var validator = new DataAnnotationsModelValidatorProvider(
|
var validator = new DataAnnotationsModelValidatorProvider(
|
||||||
|
|
@ -563,7 +564,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
"",
|
"",
|
||||||
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
||||||
metadataProvider,
|
metadataProvider,
|
||||||
GetCompositeBinder(binders),
|
GetModelBinderFactory(binderProviders),
|
||||||
valueProvider,
|
valueProvider,
|
||||||
new List<IInputFormatter>(),
|
new List<IInputFormatter>(),
|
||||||
new DefaultObjectValidator(metadataProvider, new ValidatorCache()),
|
new DefaultObjectValidator(metadataProvider, new ValidatorCache()),
|
||||||
|
|
@ -595,7 +596,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
prefix: "",
|
prefix: "",
|
||||||
actionContext: new ActionContext() { HttpContext = new DefaultHttpContext() },
|
actionContext: new ActionContext() { HttpContext = new DefaultHttpContext() },
|
||||||
metadataProvider: metadataProvider,
|
metadataProvider: metadataProvider,
|
||||||
modelBinder: GetCompositeBinder(binder.Object),
|
modelBinderFactory: GetModelBinderFactory(binder.Object),
|
||||||
valueProvider: Mock.Of<IValueProvider>(),
|
valueProvider: Mock.Of<IValueProvider>(),
|
||||||
inputFormatters: new List<IInputFormatter>(),
|
inputFormatters: new List<IInputFormatter>(),
|
||||||
objectModelValidator: new Mock<IObjectModelValidator>(MockBehavior.Strict).Object,
|
objectModelValidator: new Mock<IObjectModelValidator>(MockBehavior.Strict).Object,
|
||||||
|
|
@ -610,10 +611,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
public async Task TryUpdateModelNonGeneric_ModelTypeOverload_ReturnsTrue_IfModelBindsAndValidatesSuccessfully()
|
public async Task TryUpdateModelNonGeneric_ModelTypeOverload_ReturnsTrue_IfModelBindsAndValidatesSuccessfully()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var binders = new IModelBinder[]
|
var binderProviders = new IModelBinderProvider[]
|
||||||
{
|
{
|
||||||
new SimpleTypeModelBinder(),
|
new SimpleTypeModelBinderProvider(),
|
||||||
new MutableObjectModelBinder()
|
new ComplexTypeModelBinderProvider(),
|
||||||
};
|
};
|
||||||
|
|
||||||
var validator = new DataAnnotationsModelValidatorProvider(
|
var validator = new DataAnnotationsModelValidatorProvider(
|
||||||
|
|
@ -637,7 +638,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
"",
|
"",
|
||||||
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
||||||
TestModelMetadataProvider.CreateDefaultProvider(),
|
TestModelMetadataProvider.CreateDefaultProvider(),
|
||||||
GetCompositeBinder(binders),
|
GetModelBinderFactory(binderProviders),
|
||||||
valueProvider,
|
valueProvider,
|
||||||
new List<IInputFormatter>(),
|
new List<IInputFormatter>(),
|
||||||
new DefaultObjectValidator(metadataProvider, new ValidatorCache()),
|
new DefaultObjectValidator(metadataProvider, new ValidatorCache()),
|
||||||
|
|
@ -666,7 +667,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
"",
|
"",
|
||||||
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
new ActionContext() { HttpContext = new DefaultHttpContext() },
|
||||||
metadataProvider,
|
metadataProvider,
|
||||||
GetCompositeBinder(binder.Object),
|
GetModelBinderFactory(binder.Object),
|
||||||
Mock.Of<IValueProvider>(),
|
Mock.Of<IValueProvider>(),
|
||||||
new List<IInputFormatter>(),
|
new List<IInputFormatter>(),
|
||||||
new DefaultObjectValidator(metadataProvider, new ValidatorCache()),
|
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
|
public class User
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
public sealed class SimpleValueProvider : Dictionary<string, object>, IValueProvider
|
public sealed class SimpleValueProvider : Dictionary<string, object>, IValueProvider
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ using System.Diagnostics;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
public class StubModelBinder : IModelBinder
|
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);
|
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, operationContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(null, modelBindingResult);
|
Assert.False(modelBindingResult.Value.IsModelSet);
|
||||||
|
|
||||||
// ModelState (not set unless inner binder sets it)
|
// ModelState (not set unless inner binder sets it)
|
||||||
Assert.True(modelState.IsValid);
|
Assert.True(modelState.IsValid);
|
||||||
|
|
@ -110,7 +110,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
||||||
// Assert
|
// Assert
|
||||||
|
|
||||||
// ModelBindingResult
|
// ModelBindingResult
|
||||||
Assert.Equal(null, modelBindingResult);
|
Assert.False(modelBindingResult.Value.IsModelSet);
|
||||||
|
|
||||||
// ModelState
|
// ModelState
|
||||||
Assert.True(modelState.IsValid);
|
Assert.True(modelState.IsValid);
|
||||||
|
|
@ -146,7 +146,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
||||||
// Assert
|
// Assert
|
||||||
|
|
||||||
// ModelBindingResult
|
// ModelBindingResult
|
||||||
Assert.Equal(null, modelBindingResult);
|
Assert.False(modelBindingResult.Value.IsModelSet);
|
||||||
|
|
||||||
// ModelState
|
// ModelState
|
||||||
Assert.True(modelState.IsValid);
|
Assert.True(modelState.IsValid);
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
||||||
// Assert
|
// Assert
|
||||||
|
|
||||||
// ModelBindingResult
|
// ModelBindingResult
|
||||||
Assert.Equal(default(ModelBindingResult), modelBindingResult);
|
Assert.False(modelBindingResult.IsModelSet);
|
||||||
|
|
||||||
// ModelState
|
// ModelState
|
||||||
Assert.True(modelState.IsValid);
|
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