Add `ModelBindingContext.IsFirstChanceBinding` and `IsTopLevelObject`
- cleanup duplicate code now that #2445 is fixed - update unit tests using old `ModelBindingContext` setups - fix (just) one integration test where `MutableObjectModelBinder` incorrectly calculated `isTopLevelObject` and returned a non-`null` model - undo temporary changes in `BodyModelBinderTests` due to increased reliance on incorrect `isTopLevelObject` in #2445 fix nits: - combine tests that are now duplicates - beef up coverage of some `MutableObjectModelBinderTest` cases - remove unused `using`s
This commit is contained in:
parent
a8c37e57de
commit
a170a4e1e4
|
|
@ -174,8 +174,31 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
/// 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 bool FallbackToEmptyPrefix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an indication that the current binder is handling the top-level object.
|
||||
/// </summary>
|
||||
/// <remarks>Passed into the model binding system.</remarks>
|
||||
public bool IsTopLevelObject { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an indication that the model binding system will make another binding attempt (e.g. fall back
|
||||
/// to the empty prefix) after this one.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Not passed into the model binding system but instead set by the top-level binder. With built-in binders,
|
||||
/// <c>true</c> only in binders called directly from a
|
||||
/// <c>Microsoft.AspNet.Mvc.ModelBinding.CompositeModelBinder</c> that was passed a
|
||||
/// <see cref="ModelBindingContext"/> with <see cref="FallbackToEmptyPrefix"/> <c>true</c>. <c>false</c>
|
||||
/// otherwise.
|
||||
/// </remarks>
|
||||
public bool IsFirstChanceBinding { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="IValueProvider"/> associated with this context.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -196,6 +196,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
bindingInfo,
|
||||
parameterName);
|
||||
|
||||
modelBindingContext.IsTopLevelObject = true;
|
||||
modelBindingContext.ModelState = modelState;
|
||||
modelBindingContext.ValueProvider = operationBindingContext.ValueProvider;
|
||||
modelBindingContext.OperationBindingContext = operationBindingContext;
|
||||
|
|
|
|||
|
|
@ -30,8 +30,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
// For compatibility with MVC 5.0 for top level object we want to consider an empty key instead of
|
||||
// the parameter name/a custom name. In all other cases (like when binding body to a property) we
|
||||
// consider the entire ModelName as a prefix.
|
||||
var isTopLevelObject = bindingContext.ModelMetadata.ContainerType == null;
|
||||
var modelBindingKey = isTopLevelObject ? string.Empty : bindingContext.ModelName;
|
||||
var modelBindingKey = bindingContext.IsTopLevelObject ? string.Empty : bindingContext.ModelName;
|
||||
|
||||
var httpContext = bindingContext.OperationBindingContext.HttpContext;
|
||||
|
||||
|
|
|
|||
|
|
@ -27,12 +27,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
if (!await bindingContext.ValueProvider.ContainsPrefixAsync(bindingContext.ModelName))
|
||||
{
|
||||
// If this is the fallback case, and we failed to find data as a top-level model, then generate a
|
||||
// If this is the fallback case and we failed to find data for a top-level model, then generate a
|
||||
// default 'empty' model and return it.
|
||||
var isTopLevelObject = bindingContext.ModelMetadata.ContainerType == null;
|
||||
var hasExplicitAlias = bindingContext.BinderModelName != null;
|
||||
|
||||
if (isTopLevelObject && (hasExplicitAlias || bindingContext.ModelName == string.Empty))
|
||||
if (!bindingContext.IsFirstChanceBinding && bindingContext.IsTopLevelObject)
|
||||
{
|
||||
model = CreateEmptyCollection();
|
||||
|
||||
|
|
|
|||
|
|
@ -34,12 +34,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
public virtual async Task<ModelBindingResult> BindModelAsync([NotNull] ModelBindingContext bindingContext)
|
||||
{
|
||||
// Will there be a last chance (fallback) binding attempt?
|
||||
var isFirstChanceBinding = bindingContext.FallbackToEmptyPrefix &&
|
||||
!string.IsNullOrEmpty(bindingContext.ModelName);
|
||||
|
||||
var newBindingContext = CreateNewBindingContext(bindingContext, bindingContext.ModelName);
|
||||
newBindingContext.IsFirstChanceBinding = isFirstChanceBinding;
|
||||
var modelBindingResult = await TryBind(newBindingContext);
|
||||
|
||||
if (modelBindingResult == null &&
|
||||
bindingContext.FallbackToEmptyPrefix &&
|
||||
!string.IsNullOrEmpty(bindingContext.ModelName))
|
||||
if (modelBindingResult == null && isFirstChanceBinding)
|
||||
{
|
||||
// Fall back to empty prefix.
|
||||
newBindingContext = CreateNewBindingContext(bindingContext, modelName: string.Empty);
|
||||
|
|
@ -142,6 +145,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
BinderModelName = oldBindingContext.BinderModelName,
|
||||
BindingSource = oldBindingContext.BindingSource,
|
||||
BinderType = oldBindingContext.BinderType,
|
||||
IsTopLevelObject = oldBindingContext.IsTopLevelObject,
|
||||
};
|
||||
|
||||
newBindingContext.OperationBindingContext.BodyBindingState = GetBodyBindingState(oldBindingContext);
|
||||
|
|
|
|||
|
|
@ -62,12 +62,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
else
|
||||
{
|
||||
// If this is the fallback case, and we failed to find data as a top-level model, then generate a
|
||||
// If this is the fallback case and we failed to find data for a top-level model, then generate a
|
||||
// default 'empty' model and return it.
|
||||
var isTopLevelObject = bindingContext.ModelMetadata.ContainerType == null;
|
||||
var hasExplicitAlias = bindingContext.BinderModelName != null;
|
||||
|
||||
if (isTopLevelObject && (hasExplicitAlias || bindingContext.ModelName == string.Empty))
|
||||
if (!bindingContext.IsFirstChanceBinding && bindingContext.IsTopLevelObject)
|
||||
{
|
||||
var model = new KeyValuePair<TKey, TValue>();
|
||||
|
||||
|
|
|
|||
|
|
@ -71,9 +71,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
internal async Task<bool> CanCreateModel(MutableObjectBinderContext context)
|
||||
{
|
||||
var bindingContext = context.ModelBindingContext;
|
||||
|
||||
var isTopLevelObject = bindingContext.ModelMetadata.ContainerType == null;
|
||||
var hasExplicitAlias = bindingContext.BinderModelName != null;
|
||||
var isTopLevelObject = bindingContext.IsTopLevelObject;
|
||||
|
||||
// If we get here the model is a complex object which was not directly bound by any previous model binder,
|
||||
// so we want to decide if we want to continue binding. This is important to get right to avoid infinite
|
||||
|
|
@ -83,34 +81,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
// will always include value provider data. For instance if the model is marked with [FromBody], then we
|
||||
// can just skip it. A greedy source cannot be a value provider.
|
||||
//
|
||||
// If the model isn't marked with ANY binding source, then we assume it's ok also.
|
||||
// If the model isn't marked with ANY binding source, then we assume it's OK also.
|
||||
//
|
||||
// We skip this check if it is a top level object because we want to always evaluate
|
||||
// the creation of top level object (this is also required for ModelBinderAttribute to work.)
|
||||
var bindingSource = bindingContext.BindingSource;
|
||||
if (!isTopLevelObject &&
|
||||
bindingSource != null &&
|
||||
bindingSource.IsGreedy)
|
||||
if (!isTopLevelObject && bindingSource != null && bindingSource.IsGreedy)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create the object if :
|
||||
// 1. It is a top level model with an explicit user supplied prefix.
|
||||
// In this case since it will never fallback to empty prefix, we need to create the model here.
|
||||
if (isTopLevelObject && hasExplicitAlias)
|
||||
// Create the object if:
|
||||
// 1. It is a top level model and no later fallback (to empty prefix) will occur.
|
||||
if (isTopLevelObject && !bindingContext.IsFirstChanceBinding)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2. It is a top level object and there is no model name ( Fallback to empty prefix case ).
|
||||
// This is necessary as we do not want to depend on a value provider to contain an empty prefix.
|
||||
if (isTopLevelObject && bindingContext.ModelName == string.Empty)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. Any of the model properties can be bound using a value provider.
|
||||
// 2. Any of the model properties can be bound using a value provider.
|
||||
if (await CanValueBindAnyModelProperties(context))
|
||||
{
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -2,10 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
|
@ -303,8 +300,9 @@ namespace Microsoft.AspNet.Mvc
|
|||
ModelState = modelState,
|
||||
ValueProvider = valueProvider,
|
||||
FallbackToEmptyPrefix = true,
|
||||
IsTopLevelObject = true,
|
||||
OperationBindingContext = operationBindingContext,
|
||||
PropertyFilter = predicate
|
||||
PropertyFilter = predicate,
|
||||
};
|
||||
|
||||
var modelBindingResult = await modelBinder.BindModelAsync(modelBindingContext);
|
||||
|
|
|
|||
|
|
@ -37,13 +37,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ArrayModelBinder_DoesNotCreateCollection_ForTopLevelModel_OnFirstPass()
|
||||
public async Task ArrayModelBinder_DoesNotCreateCollection_IfIsTopLevelObjectAndIsFirstChanceBinding()
|
||||
{
|
||||
// Arrange
|
||||
var binder = new ArrayModelBinder<string>();
|
||||
|
||||
var context = CreateContext();
|
||||
context.ModelName = "param";
|
||||
context.IsFirstChanceBinding = true;
|
||||
context.IsTopLevelObject = true;
|
||||
|
||||
// Explicit prefix and empty model name both ignored.
|
||||
context.BinderModelName = "prefix";
|
||||
context.ModelName = string.Empty;
|
||||
|
||||
var metadataProvider = context.OperationBindingContext.MetadataProvider;
|
||||
context.ModelMetadata = metadataProvider.GetMetadataForType(typeof(string[]));
|
||||
|
|
@ -58,13 +63,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ArrayModelBinder_CreatesEmptyCollection_ForTopLevelModel_OnFallback()
|
||||
public async Task ArrayModelBinder_CreatesEmptyCollection_IfIsTopLevelObjectAndNotIsFirstChanceBinding()
|
||||
{
|
||||
// Arrange
|
||||
var binder = new ArrayModelBinder<string>();
|
||||
|
||||
var context = CreateContext();
|
||||
context.ModelName = string.Empty;
|
||||
context.IsTopLevelObject = true;
|
||||
|
||||
// Lack of prefix and non-empty model name both ignored.
|
||||
context.ModelName = "modelName";
|
||||
|
||||
var metadataProvider = context.OperationBindingContext.MetadataProvider;
|
||||
context.ModelMetadata = metadataProvider.GetMetadataForType(typeof(string[]));
|
||||
|
|
@ -78,37 +86,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
Assert.NotNull(result);
|
||||
|
||||
Assert.Empty(Assert.IsType<string[]>(result.Model));
|
||||
Assert.Equal(string.Empty, result.Key);
|
||||
Assert.True(result.IsModelSet);
|
||||
|
||||
Assert.Same(result.ValidationNode.Model, result.Model);
|
||||
Assert.Same(result.ValidationNode.Key, result.Key);
|
||||
Assert.Same(result.ValidationNode.ModelMetadata, context.ModelMetadata);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ArrayModelBinder_CreatesEmptyCollection_ForTopLevelModel_WithExplicitPrefix()
|
||||
{
|
||||
// Arrange
|
||||
var binder = new ArrayModelBinder<string>();
|
||||
|
||||
var context = CreateContext();
|
||||
context.ModelName = "prefix";
|
||||
context.BinderModelName = "prefix";
|
||||
|
||||
var metadataProvider = context.OperationBindingContext.MetadataProvider;
|
||||
context.ModelMetadata = metadataProvider.GetMetadataForType(typeof(string[]));
|
||||
|
||||
context.ValueProvider = new TestValueProvider(new Dictionary<string, object>());
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
|
||||
Assert.Empty(Assert.IsType<string[]>(result.Model));
|
||||
Assert.Equal("prefix", result.Key);
|
||||
Assert.Equal("modelName", result.Key);
|
||||
Assert.True(result.IsModelSet);
|
||||
|
||||
Assert.Same(result.ValidationNode.Model, result.Model);
|
||||
|
|
@ -119,7 +97,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData("param")]
|
||||
public async Task ArrayModelBinder_DoesNotCreateCollection_ForNonTopLevelModel(string prefix)
|
||||
public async Task ArrayModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(string prefix)
|
||||
{
|
||||
// Arrange
|
||||
var binder = new ArrayModelBinder<string>();
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Internal;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
|
@ -279,6 +278,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
var bindingContext = new ModelBindingContext
|
||||
{
|
||||
IsTopLevelObject = true,
|
||||
ModelMetadata = metadataProvider.GetMetadataForType(modelType),
|
||||
ModelName = "someName",
|
||||
ValueProvider = Mock.Of<IValueProvider>(),
|
||||
|
|
|
|||
|
|
@ -218,13 +218,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CollectionModelBinder_DoesNotCreateCollection_ForTopLevelModel_OnFirstPass()
|
||||
public async Task CollectionModelBinder_DoesNotCreateCollection_IfIsTopLevelObjectAndIsFirstChanceBinding()
|
||||
{
|
||||
// Arrange
|
||||
var binder = new CollectionModelBinder<string>();
|
||||
|
||||
var context = CreateContext();
|
||||
context.ModelName = "param";
|
||||
context.IsTopLevelObject = true;
|
||||
context.IsFirstChanceBinding = true;
|
||||
|
||||
// Explicit prefix and empty model name both ignored.
|
||||
context.BinderModelName = "prefix";
|
||||
context.ModelName = string.Empty;
|
||||
|
||||
var metadataProvider = context.OperationBindingContext.MetadataProvider;
|
||||
context.ModelMetadata = metadataProvider.GetMetadataForType(typeof(List<string>));
|
||||
|
|
@ -239,13 +244,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CollectionModelBinder_CreatesEmptyCollection_ForTopLevelModel_OnFallback()
|
||||
public async Task CollectionModelBinder_CreatesEmptyCollection_IfIsTopLevelObjectAndNotIsFirstChanceBinding()
|
||||
{
|
||||
// Arrange
|
||||
var binder = new CollectionModelBinder<string>();
|
||||
|
||||
var context = CreateContext();
|
||||
context.ModelName = string.Empty;
|
||||
context.IsTopLevelObject = true;
|
||||
|
||||
// Lack of prefix and non-empty model name both ignored.
|
||||
context.ModelName = "modelName";
|
||||
|
||||
var metadataProvider = context.OperationBindingContext.MetadataProvider;
|
||||
context.ModelMetadata = metadataProvider.GetMetadataForType(typeof(List<string>));
|
||||
|
|
@ -259,37 +267,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
Assert.NotNull(result);
|
||||
|
||||
Assert.Empty(Assert.IsType<List<string>>(result.Model));
|
||||
Assert.Equal(string.Empty, result.Key);
|
||||
Assert.True(result.IsModelSet);
|
||||
|
||||
Assert.Same(result.ValidationNode.Model, result.Model);
|
||||
Assert.Same(result.ValidationNode.Key, result.Key);
|
||||
Assert.Same(result.ValidationNode.ModelMetadata, context.ModelMetadata);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CollectionModelBinder_CreatesEmptyCollection_ForTopLevelModel_WithExplicitPrefix()
|
||||
{
|
||||
// Arrange
|
||||
var binder = new CollectionModelBinder<string>();
|
||||
|
||||
var context = CreateContext();
|
||||
context.ModelName = "prefix";
|
||||
context.BinderModelName = "prefix";
|
||||
|
||||
var metadataProvider = context.OperationBindingContext.MetadataProvider;
|
||||
context.ModelMetadata = metadataProvider.GetMetadataForType(typeof(List<string>));
|
||||
|
||||
context.ValueProvider = new TestValueProvider(new Dictionary<string, object>());
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
|
||||
Assert.Empty(Assert.IsType<List<string>>(result.Model));
|
||||
Assert.Equal("prefix", result.Key);
|
||||
Assert.Equal("modelName", result.Key);
|
||||
Assert.True(result.IsModelSet);
|
||||
|
||||
Assert.Same(result.ValidationNode.Model, result.Model);
|
||||
|
|
@ -300,7 +278,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData("param")]
|
||||
public async Task CollectionModelBinder_DoesNotCreateCollection_ForNonTopLevelModel(string prefix)
|
||||
public async Task CollectionModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(string prefix)
|
||||
{
|
||||
// Arrange
|
||||
var binder = new CollectionModelBinder<string>();
|
||||
|
|
|
|||
|
|
@ -65,13 +65,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DictionaryModelBinder_DoesNotCreateCollection_ForTopLevelModel_OnFirstPass()
|
||||
public async Task DictionaryModelBinder_DoesNotCreateCollection_IfIsTopLevelObjectAndIsFirstChanceBinding()
|
||||
{
|
||||
// Arrange
|
||||
var binder = new DictionaryModelBinder<string, string>();
|
||||
|
||||
var context = CreateContext();
|
||||
context.ModelName = "param";
|
||||
context.IsTopLevelObject = true;
|
||||
context.IsFirstChanceBinding = true;
|
||||
|
||||
// Explicit prefix and empty model name both ignored.
|
||||
context.BinderModelName = "prefix";
|
||||
context.ModelName = string.Empty;
|
||||
|
||||
var metadataProvider = context.OperationBindingContext.MetadataProvider;
|
||||
context.ModelMetadata = metadataProvider.GetMetadataForType(typeof(Dictionary<string, string>));
|
||||
|
|
@ -86,13 +91,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DictionaryModelBinder_CreatesEmptyCollection_ForTopLevelModel_OnFallback()
|
||||
public async Task DictionaryModelBinder_CreatesEmptyCollection_IfIsTopLevelObjectAndNotIsFirstChanceBinding()
|
||||
{
|
||||
// Arrange
|
||||
var binder = new DictionaryModelBinder<string, string>();
|
||||
|
||||
var context = CreateContext();
|
||||
context.ModelName = string.Empty;
|
||||
context.IsTopLevelObject = true;
|
||||
|
||||
// Lack of prefix and non-empty model name both ignored.
|
||||
context.ModelName = "modelName";
|
||||
|
||||
var metadataProvider = context.OperationBindingContext.MetadataProvider;
|
||||
context.ModelMetadata = metadataProvider.GetMetadataForType(typeof(Dictionary<string, string>));
|
||||
|
|
@ -106,37 +114,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
Assert.NotNull(result);
|
||||
|
||||
Assert.Empty(Assert.IsType<Dictionary<string, string>>(result.Model));
|
||||
Assert.Equal(string.Empty, result.Key);
|
||||
Assert.True(result.IsModelSet);
|
||||
|
||||
Assert.Same(result.ValidationNode.Model, result.Model);
|
||||
Assert.Same(result.ValidationNode.Key, result.Key);
|
||||
Assert.Same(result.ValidationNode.ModelMetadata, context.ModelMetadata);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DictionaryModelBinder_CreatesEmptyCollection_ForTopLevelModel_WithExplicitPrefix()
|
||||
{
|
||||
// Arrange
|
||||
var binder = new DictionaryModelBinder<string, string>();
|
||||
|
||||
var context = CreateContext();
|
||||
context.ModelName = "prefix";
|
||||
context.BinderModelName = "prefix";
|
||||
|
||||
var metadataProvider = context.OperationBindingContext.MetadataProvider;
|
||||
context.ModelMetadata = metadataProvider.GetMetadataForType(typeof(Dictionary<string, string>));
|
||||
|
||||
context.ValueProvider = new TestValueProvider(new Dictionary<string, object>());
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
|
||||
Assert.Empty(Assert.IsType<Dictionary<string, string>>(result.Model));
|
||||
Assert.Equal("prefix", result.Key);
|
||||
Assert.Equal("modelName", result.Key);
|
||||
Assert.True(result.IsModelSet);
|
||||
|
||||
Assert.Same(result.ValidationNode.Model, result.Model);
|
||||
|
|
@ -147,7 +125,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData("param")]
|
||||
public async Task DictionaryModelBinder_DoesNotCreateCollection_ForNonTopLevelModel(string prefix)
|
||||
public async Task DictionaryModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(string prefix)
|
||||
{
|
||||
// Arrange
|
||||
var binder = new DictionaryModelBinder<string, string>();
|
||||
|
|
|
|||
|
|
@ -148,13 +148,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task KeyValuePairModelBinder_DoesNotCreateCollection_ForTopLevelModel_OnFirstPass()
|
||||
public async Task KeyValuePairModelBinder_DoesNotCreateCollection_IfIsTopLevelObjectAndIsFirstChanceBinding()
|
||||
{
|
||||
// Arrange
|
||||
var binder = new KeyValuePairModelBinder<string, string>();
|
||||
|
||||
var context = CreateContext();
|
||||
context.ModelName = "param";
|
||||
context.IsTopLevelObject = true;
|
||||
context.IsFirstChanceBinding = true;
|
||||
|
||||
// Explicit prefix and empty model name both ignored.
|
||||
context.BinderModelName = "prefix";
|
||||
context.ModelName = string.Empty;
|
||||
|
||||
var metadataProvider = context.OperationBindingContext.MetadataProvider;
|
||||
context.ModelMetadata = metadataProvider.GetMetadataForType(typeof(KeyValuePair<string, string>));
|
||||
|
|
@ -169,13 +174,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task KeyValuePairModelBinder_CreatesEmptyCollection_ForTopLevelModel_OnFallback()
|
||||
public async Task KeyValuePairModelBinder_CreatesEmptyCollection_IfIsTopLevelObjectAndNotIsFirstChanceBinding()
|
||||
{
|
||||
// Arrange
|
||||
var binder = new KeyValuePairModelBinder<string, string>();
|
||||
|
||||
var context = CreateContext();
|
||||
context.ModelName = string.Empty;
|
||||
context.IsTopLevelObject = true;
|
||||
|
||||
// Lack of prefix and non-empty model name both ignored.
|
||||
context.ModelName = "modelName";
|
||||
|
||||
var metadataProvider = context.OperationBindingContext.MetadataProvider;
|
||||
context.ModelMetadata = metadataProvider.GetMetadataForType(typeof(KeyValuePair<string, string>));
|
||||
|
|
@ -189,37 +197,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
Assert.NotNull(result);
|
||||
|
||||
Assert.Equal(default(KeyValuePair<string, string>), Assert.IsType<KeyValuePair<string, string>>(result.Model));
|
||||
Assert.Equal(string.Empty, result.Key);
|
||||
Assert.True(result.IsModelSet);
|
||||
|
||||
Assert.Equal(result.ValidationNode.Model, result.Model);
|
||||
Assert.Same(result.ValidationNode.Key, result.Key);
|
||||
Assert.Same(result.ValidationNode.ModelMetadata, context.ModelMetadata);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task KeyValuePairModelBinder_CreatesEmptyCollection_ForTopLevelModel_WithExplicitPrefix()
|
||||
{
|
||||
// Arrange
|
||||
var binder = new KeyValuePairModelBinder<string, string>();
|
||||
|
||||
var context = CreateContext();
|
||||
context.ModelName = "prefix";
|
||||
context.BinderModelName = "prefix";
|
||||
|
||||
var metadataProvider = context.OperationBindingContext.MetadataProvider;
|
||||
context.ModelMetadata = metadataProvider.GetMetadataForType(typeof(KeyValuePair<string, string>));
|
||||
|
||||
context.ValueProvider = new TestValueProvider(new Dictionary<string, object>());
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
|
||||
Assert.Equal(default(KeyValuePair<string, string>), Assert.IsType<KeyValuePair<string, string>>(result.Model));
|
||||
Assert.Equal("prefix", result.Key);
|
||||
Assert.Equal("modelName", result.Key);
|
||||
Assert.True(result.IsModelSet);
|
||||
|
||||
Assert.Equal(result.ValidationNode.Model, result.Model);
|
||||
|
|
@ -230,7 +208,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData("param")]
|
||||
public async Task KeyValuePairModelBinder_DoesNotCreateCollection_ForNonTopLevelModel(string prefix)
|
||||
public async Task KeyValuePairModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(string prefix)
|
||||
{
|
||||
// Arrange
|
||||
var binder = new KeyValuePairModelBinder<string, string>();
|
||||
|
|
|
|||
|
|
@ -20,23 +20,42 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
public class MutableObjectModelBinderTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(typeof(Person), true)]
|
||||
[InlineData(typeof(Person), false)]
|
||||
[InlineData(typeof(EmptyModel), true)]
|
||||
[InlineData(typeof(EmptyModel), false)]
|
||||
public async Task CanCreateModel_CreatesModel_ForTopLevelObjectIfThereIsExplicitPrefix(
|
||||
Type modelType,
|
||||
bool isPrefixProvided)
|
||||
[InlineData(true, true, "", "", false)]
|
||||
[InlineData(true, false, "", "", true)]
|
||||
[InlineData(false, true, "", "", false)] // !isTopLevelObject && isFirstChanceBinding cases are unexpected
|
||||
[InlineData(false, false, "", "", false)]
|
||||
[InlineData(true, true, "prefix", "", false)]
|
||||
[InlineData(true, false, "prefix", "", true)]
|
||||
[InlineData(false, true, "prefix", "", false)]
|
||||
[InlineData(false, false, "prefix", "", false)]
|
||||
[InlineData(true, true, "", "dummyModelName", false)]
|
||||
[InlineData(true, false, "", "dummyModelName", true)]
|
||||
[InlineData(false, true, "", "dummyModelName", false)]
|
||||
[InlineData(false, false, "", "dummyModelName", false)]
|
||||
[InlineData(true, true, "prefix", "dummyModelName", false)]
|
||||
[InlineData(true, false, "prefix", "dummyModelName", true)]
|
||||
[InlineData(false, true, "prefix", "dummyModelName", false)]
|
||||
[InlineData(false, false, "prefix", "dummyModelName", false)]
|
||||
public async Task CanCreateModel_ReturnsTrue_IfIsTopLevelObjectAndNotIsFirstChanceBinding(
|
||||
bool isTopLevelObject,
|
||||
bool isFirstChanceBinding,
|
||||
string binderModelName,
|
||||
string modelName,
|
||||
bool expectedCanCreate)
|
||||
{
|
||||
var mockValueProvider = new Mock<IValueProvider>();
|
||||
mockValueProvider.Setup(o => o.ContainsPrefixAsync(It.IsAny<string>()))
|
||||
.Returns(Task.FromResult(false));
|
||||
mockValueProvider
|
||||
.Setup(o => o.ContainsPrefixAsync(It.IsAny<string>()))
|
||||
.Returns(Task.FromResult(false));
|
||||
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
var bindingContext = new MutableObjectBinderContext
|
||||
{
|
||||
ModelBindingContext = new ModelBindingContext
|
||||
{
|
||||
IsTopLevelObject = isTopLevelObject,
|
||||
IsFirstChanceBinding = isFirstChanceBinding,
|
||||
|
||||
// Random type.
|
||||
ModelMetadata = metadataProvider.GetMetadataForType(typeof(Person)),
|
||||
ValueProvider = mockValueProvider.Object,
|
||||
|
|
@ -47,65 +66,25 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
|
||||
},
|
||||
|
||||
// Setting it to empty ensures that model does not get created becasue of no model name.
|
||||
ModelName = "dummyModelName",
|
||||
BinderModelName = isPrefixProvided ? "prefix" : null,
|
||||
}
|
||||
// CanCreateModel() ignores the BinderModelName and ModelName properties.
|
||||
BinderModelName = binderModelName,
|
||||
ModelName = modelName,
|
||||
},
|
||||
};
|
||||
|
||||
var mutableBinder = new TestableMutableObjectModelBinder();
|
||||
bindingContext.PropertyMetadata = mutableBinder.GetMetadataForProperties(
|
||||
bindingContext.ModelBindingContext);
|
||||
bindingContext.ModelBindingContext);
|
||||
|
||||
// Act
|
||||
var retModel = await mutableBinder.CanCreateModel(bindingContext);
|
||||
var canCreate = await mutableBinder.CanCreateModel(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(isPrefixProvided, retModel);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(Person), true)]
|
||||
[InlineData(typeof(Person), false)]
|
||||
[InlineData(typeof(EmptyModel), true)]
|
||||
[InlineData(typeof(EmptyModel), false)]
|
||||
public async Task
|
||||
CanCreateModel_CreatesModel_ForTopLevelObjectIfThereIsEmptyModelName(Type modelType, bool emptyModelName)
|
||||
{
|
||||
var mockValueProvider = new Mock<IValueProvider>();
|
||||
mockValueProvider.Setup(o => o.ContainsPrefixAsync(It.IsAny<string>()))
|
||||
.Returns(Task.FromResult(false));
|
||||
|
||||
var bindingContext = new MutableObjectBinderContext
|
||||
{
|
||||
ModelBindingContext = new ModelBindingContext
|
||||
{
|
||||
// Random type.
|
||||
ModelMetadata = GetMetadataForType(typeof(Person)),
|
||||
ValueProvider = mockValueProvider.Object,
|
||||
OperationBindingContext = new OperationBindingContext
|
||||
{
|
||||
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
|
||||
ValueProvider = mockValueProvider.Object,
|
||||
MetadataProvider = TestModelMetadataProvider.CreateDefaultProvider()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bindingContext.ModelBindingContext.ModelName = emptyModelName ? string.Empty : "dummyModelName";
|
||||
var mutableBinder = new TestableMutableObjectModelBinder();
|
||||
bindingContext.PropertyMetadata = mutableBinder.GetMetadataForProperties(
|
||||
bindingContext.ModelBindingContext);
|
||||
|
||||
// Act
|
||||
var retModel = await mutableBinder.CanCreateModel(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(emptyModelName, retModel);
|
||||
Assert.Equal(expectedCanCreate, canCreate);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanCreateModel_ReturnsFalse_ForNonTopLevelModel_IfModelIsMarkedWithBinderMetadata()
|
||||
public async Task CanCreateModel_ReturnsFalse_IfNotIsTopLevelObjectAndModelIsMarkedWithBinderMetadata()
|
||||
{
|
||||
// Get the property metadata so that it is not a top level object.
|
||||
var modelMetadata = GetMetadataForType(typeof(Document))
|
||||
|
|
@ -135,13 +114,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanCreateModel_ReturnsTrue_ForTopLevelModel_IfModelIsMarkedWithBinderMetadata()
|
||||
public async Task CanCreateModel_ReturnsTrue_IfIsTopLevelObjectAndModelIsMarkedWithBinderMetadata()
|
||||
{
|
||||
var bindingContext = new MutableObjectBinderContext
|
||||
{
|
||||
ModelBindingContext = new ModelBindingContext
|
||||
{
|
||||
// Here the metadata represents a top level object.
|
||||
IsTopLevelObject = true,
|
||||
|
||||
ModelMetadata = GetMetadataForType(typeof(Document)),
|
||||
OperationBindingContext = new OperationBindingContext
|
||||
{
|
||||
|
|
@ -198,7 +179,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task CanCreateModel_ReturnsTrue_ForNonTopLevelModel_BasedOnValueAvailability(bool valueAvailable)
|
||||
public async Task CanCreateModel_ReturnsTrue_IfNotIsTopLevelObject_BasedOnValueAvailability(
|
||||
bool valueAvailable)
|
||||
{
|
||||
// Arrange
|
||||
var mockValueProvider = new Mock<IValueProvider>(MockBehavior.Strict);
|
||||
|
|
@ -434,7 +416,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModel_InitsInstance_ForEmptyModelName()
|
||||
public async Task BindModel_InitsInstance_IfIsTopLevelObjectAndNotIsFirstChanceBinding()
|
||||
{
|
||||
// Arrange
|
||||
var mockValueProvider = new Mock<IValueProvider>();
|
||||
|
|
@ -444,6 +426,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var mockDtoBinder = new Mock<IModelBinder>();
|
||||
var bindingContext = new ModelBindingContext
|
||||
{
|
||||
IsTopLevelObject = true,
|
||||
ModelMetadata = GetMetadataForType(typeof(Person)),
|
||||
ModelName = "",
|
||||
ValueProvider = mockValueProvider.Object,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.DataProtection;
|
||||
|
|
|
|||
|
|
@ -181,10 +181,9 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
};
|
||||
|
||||
// Need to have a key here so that the GenericModelBinder will recurse to bind elements.
|
||||
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
|
||||
{
|
||||
request.QueryString = new QueryString("?parameter.index=0");
|
||||
});
|
||||
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(
|
||||
request => request.QueryString = new QueryString("?parameter.index=0"),
|
||||
options => options.ModelBinders.Add(new AddressBinder()));
|
||||
|
||||
var modelState = new ModelStateDictionary();
|
||||
|
||||
|
|
@ -204,6 +203,41 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
Assert.True(modelState.IsValid);
|
||||
}
|
||||
|
||||
// Similar to the GenericModelBinder_BindsCollection_ElementTypeUsesGreedyModelBinder_WithPrefix_Success
|
||||
// scenario but mis-configured. Model using a BindingSource for which no ModelBinder is enabled.
|
||||
[Fact]
|
||||
public async Task GenericModelBinder_BindsCollection_ElementTypeUsesGreedyBindingSource_WithPrefix_NullElement()
|
||||
{
|
||||
// Arrange
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
var parameter = new ParameterDescriptor()
|
||||
{
|
||||
Name = "parameter",
|
||||
ParameterType = typeof(Address[])
|
||||
};
|
||||
|
||||
// Need to have a key here so that the GenericModelBinder will recurse to bind elements.
|
||||
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(
|
||||
request => request.QueryString = new QueryString("?parameter.index=0"));
|
||||
|
||||
var modelState = new ModelStateDictionary();
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(modelBindingResult);
|
||||
Assert.True(modelBindingResult.IsModelSet);
|
||||
|
||||
var model = Assert.IsType<Address[]>(modelBindingResult.Model);
|
||||
Assert.Equal(1, model.Length);
|
||||
Assert.Null(model[0]);
|
||||
|
||||
Assert.Equal(0, modelState.Count);
|
||||
Assert.Equal(0, modelState.ErrorCount);
|
||||
Assert.True(modelState.IsValid);
|
||||
}
|
||||
|
||||
// This is part of a random sampling of scenarios where a GenericModelBinder is used
|
||||
// recursively.
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
|
|
|
|||
Loading…
Reference in New Issue