Merge branch 'release' into dev

This commit is contained in:
Sebastien Ros 2016-04-22 11:54:56 -07:00
commit 4b4d67e48e
9 changed files with 302 additions and 18 deletions

View File

@ -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());

View File

@ -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;
}
}
}

View File

@ -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
{

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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; }
}
}
}

View File

@ -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>();

View File

@ -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;
}
}
}
}

View File

@ -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 =>