Merge branch 'release' into dev
This commit is contained in:
commit
4b4d67e48e
|
|
@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Mvc.Core;
|
||||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
|
@ -73,6 +74,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
options.ValueProviderFactories.Add(new JQueryFormValueProviderFactory());
|
options.ValueProviderFactories.Add(new JQueryFormValueProviderFactory());
|
||||||
|
|
||||||
// Set up metadata providers
|
// Set up metadata providers
|
||||||
|
|
||||||
|
// Don't bind the Type class by default as it's expensive. A user can override this behavior
|
||||||
|
// by altering the collection of providers.
|
||||||
|
options.ModelMetadataDetailsProviders.Add(new ExcludeBindingMetadataProvider(typeof(Type)));
|
||||||
|
|
||||||
options.ModelMetadataDetailsProviders.Add(new DefaultBindingMetadataProvider(messageProvider));
|
options.ModelMetadataDetailsProviders.Add(new DefaultBindingMetadataProvider(messageProvider));
|
||||||
options.ModelMetadataDetailsProviders.Add(new DefaultValidationMetadataProvider());
|
options.ModelMetadataDetailsProviders.Add(new DefaultValidationMetadataProvider());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
{
|
||||||
|
public class NoOpBinder : IModelBinder
|
||||||
|
{
|
||||||
|
public static readonly IModelBinder Instance = new NoOpBinder();
|
||||||
|
|
||||||
|
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||||
|
{
|
||||||
|
bindingContext.Result = ModelBindingResult.Failed(bindingContext.ModelName);
|
||||||
|
return TaskCache.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,7 +14,7 @@ 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"/>
|
||||||
/// when a model has the binding source <see cref="BindingSource.Body"/>/
|
/// when a model has the binding source <see cref="BindingSource.Body"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class BodyModelBinder : IModelBinder
|
public class BodyModelBinder : IModelBinder
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An <see cref="IBindingMetadataProvider"/> which configures <see cref="ModelMetadata.IsBindingAllowed"/> to
|
||||||
|
/// <c>false</c> for matching types.
|
||||||
|
/// </summary>
|
||||||
|
public class ExcludeBindingMetadataProvider : IBindingMetadataProvider
|
||||||
|
{
|
||||||
|
private readonly Type _type;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="ExcludeBindingMetadataProvider"/> for the given <paramref name="type"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">
|
||||||
|
/// The <see cref="Type"/>. All properties of this <see cref="Type"/> will have
|
||||||
|
/// <see cref="ModelMetadata.IsBindingAllowed"/> set to <c>false</c>.
|
||||||
|
/// </param>
|
||||||
|
public ExcludeBindingMetadataProvider(Type type)
|
||||||
|
{
|
||||||
|
if (type == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
_type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void CreateBindingMetadata(BindingMetadataProviderContext context)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
// No-op if the metadata is not for the target type
|
||||||
|
if (!_type.IsAssignableFrom(context.Key.ModelType))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.BindingMetadata.IsBindingAllowed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -73,6 +73,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
|
|
||||||
private IModelBinder CreateBinderCore(DefaultModelBinderProviderContext providerContext, object token)
|
private IModelBinder CreateBinderCore(DefaultModelBinderProviderContext providerContext, object token)
|
||||||
{
|
{
|
||||||
|
if (!providerContext.Metadata.IsBindingAllowed)
|
||||||
|
{
|
||||||
|
return NoOpBinder.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
// A non-null token will usually be passed in at the the top level (ParameterDescriptor likely).
|
// 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
|
// This prevents us from treating a parameter the same as a collection-element - which could
|
||||||
// happen looking at just model metadata.
|
// happen looking at just model metadata.
|
||||||
|
|
@ -188,17 +193,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 struct Key : IEquatable<Key>
|
||||||
{
|
{
|
||||||
private readonly ModelMetadata _metadata;
|
private readonly ModelMetadata _metadata;
|
||||||
|
|
|
||||||
|
|
@ -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.Metadata
|
||||||
|
{
|
||||||
|
public class ExcludeBindingMetadataProviderTest
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData(true)]
|
||||||
|
[InlineData(false)]
|
||||||
|
public void IsBindingAllowed_LeftAlone_WhenTypeDoesntMatch(bool initialValue)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = new ExcludeBindingMetadataProvider(typeof(string));
|
||||||
|
|
||||||
|
var key = ModelMetadataIdentity.ForProperty(
|
||||||
|
typeof(int),
|
||||||
|
nameof(Person.Age),
|
||||||
|
typeof(Person));
|
||||||
|
|
||||||
|
var context = new BindingMetadataProviderContext(key, new ModelAttributes(new object[0], new object[0]));
|
||||||
|
|
||||||
|
context.BindingMetadata.IsBindingAllowed = initialValue;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
provider.CreateBindingMetadata(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(initialValue, context.BindingMetadata.IsBindingAllowed);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(true)]
|
||||||
|
[InlineData(false)]
|
||||||
|
public void IsBindingAllowed_IsFalse_WhenTypeMatches(bool initialValue)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = new ExcludeBindingMetadataProvider(typeof(int));
|
||||||
|
|
||||||
|
var key = ModelMetadataIdentity.ForProperty(
|
||||||
|
typeof(int),
|
||||||
|
nameof(Person.Age),
|
||||||
|
typeof(Person));
|
||||||
|
|
||||||
|
var context = new BindingMetadataProviderContext(key, new ModelAttributes(new object[0], new object[0]));
|
||||||
|
|
||||||
|
context.BindingMetadata.IsBindingAllowed = initialValue;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
provider.CreateBindingMetadata(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(context.BindingMetadata.IsBindingAllowed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Person
|
||||||
|
{
|
||||||
|
public int Age { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
// 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 Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Internal;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Internal;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -14,7 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CreateBinder_Throws_WhenBinderNotCreated()
|
public void CreateBinder_Throws_WhenBinderNotCreated()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var metadataProvider = new TestModelMetadataProvider();
|
var metadataProvider = new TestModelMetadataProvider();
|
||||||
var options = new TestOptionsManager<MvcOptions>();
|
var options = new TestOptionsManager<MvcOptions>();
|
||||||
var factory = new ModelBinderFactory(metadataProvider, options);
|
var factory = new ModelBinderFactory(metadataProvider, options);
|
||||||
|
|
@ -36,7 +38,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CreateBinder_CreatesNoOpBinder_WhenPropertyDoesntHaveABinder()
|
public void CreateBinder_CreatesNoOpBinder_WhenPropertyDoesntHaveABinder()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var metadataProvider = new TestModelMetadataProvider();
|
var metadataProvider = new TestModelMetadataProvider();
|
||||||
|
|
||||||
// There isn't a provider that can handle WidgetId.
|
// There isn't a provider that can handle WidgetId.
|
||||||
|
|
@ -66,10 +68,47 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
Assert.NotNull(result);
|
Assert.NotNull(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateBinder_CreatesNoOpBinder_WhenPropertyBindingIsNotAllowed()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var metadataProvider = new TestModelMetadataProvider();
|
||||||
|
metadataProvider
|
||||||
|
.ForProperty<Widget>(nameof(Widget.Id))
|
||||||
|
.BindingDetails(m => m.IsBindingAllowed = false);
|
||||||
|
|
||||||
|
var modelBinder = new ByteArrayModelBinder();
|
||||||
|
|
||||||
|
var options = new TestOptionsManager<MvcOptions>();
|
||||||
|
options.Value.ModelBinderProviders.Add(new TestModelBinderProvider(c =>
|
||||||
|
{
|
||||||
|
if (c.Metadata.ModelType == typeof(WidgetId))
|
||||||
|
{
|
||||||
|
return modelBinder;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}));
|
||||||
|
|
||||||
|
var factory = new ModelBinderFactory(metadataProvider, options);
|
||||||
|
|
||||||
|
var context = new ModelBinderFactoryContext()
|
||||||
|
{
|
||||||
|
Metadata = metadataProvider.GetMetadataForProperty(typeof(Widget), nameof(Widget.Id)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = factory.CreateBinder(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.IsType<NoOpBinder>(result);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CreateBinder_NestedProperties()
|
public void CreateBinder_NestedProperties()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var metadataProvider = new TestModelMetadataProvider();
|
var metadataProvider = new TestModelMetadataProvider();
|
||||||
|
|
||||||
var options = new TestOptionsManager<MvcOptions>();
|
var options = new TestOptionsManager<MvcOptions>();
|
||||||
|
|
@ -105,7 +144,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CreateBinder_BreaksCycles()
|
public void CreateBinder_BreaksCycles()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var metadataProvider = new TestModelMetadataProvider();
|
var metadataProvider = new TestModelMetadataProvider();
|
||||||
|
|
||||||
var callCount = 0;
|
var callCount = 0;
|
||||||
|
|
@ -142,7 +181,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CreateBinder_DoesNotCache_WhenTokenIsNull()
|
public void CreateBinder_DoesNotCache_WhenTokenIsNull()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var metadataProvider = new TestModelMetadataProvider();
|
var metadataProvider = new TestModelMetadataProvider();
|
||||||
|
|
||||||
var options = new TestOptionsManager<MvcOptions>();
|
var options = new TestOptionsManager<MvcOptions>();
|
||||||
|
|
@ -170,7 +209,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CreateBinder_Caches_WhenTokenIsNotNull()
|
public void CreateBinder_Caches_WhenTokenIsNotNull()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var metadataProvider = new TestModelMetadataProvider();
|
var metadataProvider = new TestModelMetadataProvider();
|
||||||
|
|
||||||
var options = new TestOptionsManager<MvcOptions>();
|
var options = new TestOptionsManager<MvcOptions>();
|
||||||
|
|
|
||||||
|
|
@ -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.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
||||||
|
{
|
||||||
|
public class ExcludeBindingMetadataProviderIntegrationTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task BindParameter_WithTypeProperty_IsNotBound()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var options = new MvcOptions();
|
||||||
|
var setup = new MvcCoreMvcOptionsSetup(new TestHttpRequestStreamReaderFactory());
|
||||||
|
|
||||||
|
// Adding a custom model binder for Type to ensure it doesn't get called
|
||||||
|
options.ModelBinderProviders.Insert(0, new TypeModelBinderProvider());
|
||||||
|
|
||||||
|
setup.Configure(options);
|
||||||
|
|
||||||
|
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(options);
|
||||||
|
var parameter = new ParameterDescriptor()
|
||||||
|
{
|
||||||
|
Name = "Parameter1",
|
||||||
|
BindingInfo = new BindingInfo(),
|
||||||
|
ParameterType = typeof(TypesBundle),
|
||||||
|
};
|
||||||
|
|
||||||
|
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
|
||||||
|
{
|
||||||
|
request.Form = new FormCollection(new Dictionary<string, StringValues>
|
||||||
|
{
|
||||||
|
{ "name", new[] { "Fred" } },
|
||||||
|
{ "type", new[] { "SomeType" } },
|
||||||
|
{ "typeArray", new[] { "SomeType1", "SomeType2" } },
|
||||||
|
{ "typeList", new[] { "SomeType1", "SomeType2" } },
|
||||||
|
{ "typeDictionary", new[] { "parameter[0].Key=key", "parameter[0].Value=value" } },
|
||||||
|
{ "methodInfo", new[] { "value" } },
|
||||||
|
{ "func", new[] { "value" } },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
var modelState = operationContext.ActionContext.ModelState;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, operationContext) ??
|
||||||
|
default(ModelBindingResult);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
// ModelBindingResult
|
||||||
|
Assert.True(modelBindingResult.IsModelSet);
|
||||||
|
|
||||||
|
// Model
|
||||||
|
var boundPerson = Assert.IsType<TypesBundle>(modelBindingResult.Model);
|
||||||
|
Assert.NotNull(boundPerson);
|
||||||
|
Assert.Equal("Fred", boundPerson.Name);
|
||||||
|
|
||||||
|
// ModelState
|
||||||
|
|
||||||
|
// The TypeModelBinder should not be called
|
||||||
|
Assert.True(modelState.IsValid);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TypesBundle
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public Type Type { get; set; }
|
||||||
|
|
||||||
|
public Type[] TypeArray { get; set; }
|
||||||
|
|
||||||
|
public List<Type> TypeList { get; set; }
|
||||||
|
|
||||||
|
public Dictionary<string, Type> TypeDictionary { get; set; }
|
||||||
|
|
||||||
|
public MethodInfo MethodInfo { get; set; }
|
||||||
|
|
||||||
|
public Func<object> Func { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TypeModelBinderProvider : IModelBinderProvider
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IModelBinder GetBinder(ModelBinderProviderContext context)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.Metadata.ModelType == typeof(Type))
|
||||||
|
{
|
||||||
|
throw new Exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -157,6 +157,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
// Assert
|
// Assert
|
||||||
var providers = options.ModelMetadataDetailsProviders;
|
var providers = options.ModelMetadataDetailsProviders;
|
||||||
Assert.Collection(providers,
|
Assert.Collection(providers,
|
||||||
|
provider => Assert.IsType<ExcludeBindingMetadataProvider>(provider),
|
||||||
provider => Assert.IsType<DefaultBindingMetadataProvider>(provider),
|
provider => Assert.IsType<DefaultBindingMetadataProvider>(provider),
|
||||||
provider => Assert.IsType<DefaultValidationMetadataProvider>(provider),
|
provider => Assert.IsType<DefaultValidationMetadataProvider>(provider),
|
||||||
provider =>
|
provider =>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue