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.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
|
|
@ -73,6 +74,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
options.ValueProviderFactories.Add(new JQueryFormValueProviderFactory());
|
||||
|
||||
// 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 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>
|
||||
/// 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>
|
||||
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)
|
||||
{
|
||||
if (!providerContext.Metadata.IsBindingAllowed)
|
||||
{
|
||||
return NoOpBinder.Instance;
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
|
@ -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 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.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Internal;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
|
@ -14,7 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
[Fact]
|
||||
public void CreateBinder_Throws_WhenBinderNotCreated()
|
||||
{
|
||||
// Arrange
|
||||
// Arrange
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
var options = new TestOptionsManager<MvcOptions>();
|
||||
var factory = new ModelBinderFactory(metadataProvider, options);
|
||||
|
|
@ -36,7 +38,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
[Fact]
|
||||
public void CreateBinder_CreatesNoOpBinder_WhenPropertyDoesntHaveABinder()
|
||||
{
|
||||
// Arrange
|
||||
// Arrange
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
|
||||
// There isn't a provider that can handle WidgetId.
|
||||
|
|
@ -66,10 +68,47 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
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]
|
||||
public void CreateBinder_NestedProperties()
|
||||
{
|
||||
// Arrange
|
||||
// Arrange
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
|
||||
var options = new TestOptionsManager<MvcOptions>();
|
||||
|
|
@ -105,7 +144,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
[Fact]
|
||||
public void CreateBinder_BreaksCycles()
|
||||
{
|
||||
// Arrange
|
||||
// Arrange
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
|
||||
var callCount = 0;
|
||||
|
|
@ -142,7 +181,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
[Fact]
|
||||
public void CreateBinder_DoesNotCache_WhenTokenIsNull()
|
||||
{
|
||||
// Arrange
|
||||
// Arrange
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
|
||||
var options = new TestOptionsManager<MvcOptions>();
|
||||
|
|
@ -170,7 +209,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
[Fact]
|
||||
public void CreateBinder_Caches_WhenTokenIsNotNull()
|
||||
{
|
||||
// Arrange
|
||||
// Arrange
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
|
||||
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
|
||||
var providers = options.ModelMetadataDetailsProviders;
|
||||
Assert.Collection(providers,
|
||||
provider => Assert.IsType<ExcludeBindingMetadataProvider>(provider),
|
||||
provider => Assert.IsType<DefaultBindingMetadataProvider>(provider),
|
||||
provider => Assert.IsType<DefaultValidationMetadataProvider>(provider),
|
||||
provider =>
|
||||
|
|
|
|||
Loading…
Reference in New Issue