Merge branch 'release' into dev

This commit is contained in:
Doug Bunting 2015-08-18 10:30:04 -07:00
commit c2347e4d4b
11 changed files with 750 additions and 548 deletions

View File

@ -1,32 +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.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
// Describes a complex model, but uses a collection rather than individual properties as the data store.
public class ComplexModelDto
{
public ComplexModelDto([NotNull] ModelMetadata modelMetadata,
[NotNull] IEnumerable<ModelMetadata> propertyMetadata)
{
ModelMetadata = modelMetadata;
PropertyMetadata = new Collection<ModelMetadata>(propertyMetadata.ToList());
Results = new Dictionary<ModelMetadata, ModelBindingResult>();
}
public ModelMetadata ModelMetadata { get; private set; }
public Collection<ModelMetadata> PropertyMetadata { get; private set; }
// Contains entries corresponding to each property against which binding was
// attempted. If binding failed, the entry's value will be null. If binding
// was never attempted, this dictionary will not contain a corresponding
// entry.
public IDictionary<ModelMetadata, ModelBindingResult> Results { get; private set; }
}
}

View File

@ -1,48 +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.Threading.Tasks;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public sealed class ComplexModelDtoModelBinder : IModelBinder
{
public async Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext.ModelType != typeof(ComplexModelDto))
{
return null;
}
ModelBindingHelper.ValidateBindingContext(bindingContext, typeof(ComplexModelDto), allowNullModel: false);
var dto = (ComplexModelDto)bindingContext.Model;
foreach (var propertyMetadata in dto.PropertyMetadata)
{
var propertyModelName = ModelNames.CreatePropertyModelName(
bindingContext.ModelName,
propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName);
var propertyBindingContext = ModelBindingContext.GetChildModelBindingContext(
bindingContext,
propertyModelName,
propertyMetadata);
var modelBindingResult =
await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(propertyBindingContext);
if (modelBindingResult == null)
{
// Could not bind. Let MutableObjectModelBinder know explicitly.
dto.Results[propertyMetadata] =
new ModelBindingResult(model: null, key: propertyModelName, isModelSet: false);
}
else
{
dto.Results[propertyMetadata] = modelBindingResult;
}
}
return new ModelBindingResult(dto, bindingContext.ModelName, isModelSet: true);
}
}
}

View File

@ -40,18 +40,22 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return null;
}
EnsureModel(bindingContext);
var result = await CreateAndPopulateDto(bindingContext, mutableObjectBinderContext.PropertyMetadata);
// Create model first (if necessary) to avoid reporting errors about properties when activation fails.
var model = GetModel(bindingContext);
var results = await BindPropertiesAsync(bindingContext, mutableObjectBinderContext.PropertyMetadata);
var validationNode = new ModelValidationNode(
bindingContext.ModelName,
bindingContext.ModelMetadata,
bindingContext.Model);
model);
// Post-processing e.g. property setters and hooking up validation.
bindingContext.Model = model;
ProcessResults(bindingContext, results, validationNode);
// post-processing, e.g. property setters and hooking up validation
ProcessDto(bindingContext, (ComplexModelDto)result.Model, validationNode);
return new ModelBindingResult(
bindingContext.Model,
model,
bindingContext.ModelName,
isModelSet: true,
validationNode: validationNode);
@ -192,7 +196,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var bindingSource = bindingContext.BindingSource;
if (bindingSource != null && !bindingSource.IsGreedy)
{
var rootValueProvider = bindingContext.OperationBindingContext.ValueProvider as IBindingSourceValueProvider;
var rootValueProvider =
bindingContext.OperationBindingContext.ValueProvider as IBindingSourceValueProvider;
if (rootValueProvider != null)
{
valueProvider = rootValueProvider.Filter(bindingSource);
@ -225,12 +230,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return false;
}
if (modelMetadata.ModelType == typeof(ComplexModelDto))
{
// forbidden type - will cause a stack overflow if we try binding this type
return false;
}
return true;
}
@ -265,24 +264,50 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return true;
}
private async Task<ModelBindingResult> CreateAndPopulateDto(
// Returned dictionary contains entries corresponding to properties against which binding was attempted. If
// binding failed, the entry's value will have IsModelSet == false. Binding is attempted for all elements of
// propertyMetadatas.
private async Task<IDictionary<ModelMetadata, ModelBindingResult>> BindPropertiesAsync(
ModelBindingContext bindingContext,
IEnumerable<ModelMetadata> propertyMetadatas)
{
// create a DTO and call into the DTO binder
var dto = new ComplexModelDto(bindingContext.ModelMetadata, propertyMetadatas);
var results = new Dictionary<ModelMetadata, ModelBindingResult>();
foreach (var propertyMetadata in propertyMetadatas)
{
var propertyModelName = ModelNames.CreatePropertyModelName(
bindingContext.ModelName,
propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName);
var childContext = ModelBindingContext.GetChildModelBindingContext(
bindingContext,
propertyModelName,
propertyMetadata);
var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider;
var dtoMetadata = metadataProvider.GetMetadataForType(typeof(ComplexModelDto));
// ModelBindingContext.Model property values may be non-null when invoked via TryUpdateModel(). Pass
// complex (including collection) values down so that binding system does not unnecessarily recreate
// instances or overwrite inner properties that are not bound. No need for this with simple values
// because they will be overwritten if binding succeeds. Arrays are never reused because they cannot
// be resized.
//
// ModelMetadata.PropertyGetter is not null safe; use it only if Model is non-null.
if (bindingContext.Model != null &&
propertyMetadata.PropertyGetter != null &&
propertyMetadata.IsComplexType &&
!propertyMetadata.ModelType.IsArray)
{
childContext.Model = propertyMetadata.PropertyGetter(bindingContext.Model);
}
var childContext = ModelBindingContext.GetChildModelBindingContext(
bindingContext,
bindingContext.ModelName,
dtoMetadata);
var result = await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(childContext);
if (result == null)
{
// Could not bind. Let ProcessResult() know explicitly.
result = new ModelBindingResult(model: null, key: propertyModelName, isModelSet: false);
}
childContext.Model = dto;
results[propertyMetadata] = result;
}
return await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(childContext);
return results;
}
/// <summary>
@ -298,16 +323,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
/// <summary>
/// Ensures <see cref="ModelBindingContext.Model"/> is not <c>null</c> in given
/// <paramref name="bindingContext"/>.
/// Get <see cref="ModelBindingContext.Model"/> if that property is not <c>null</c>. Otherwise activate a
/// new instance of <see cref="ModelBindingContext.ModelType"/>.
/// </summary>
/// <param name="bindingContext">The <see cref="ModelBindingContext"/>.</param>
protected virtual void EnsureModel([NotNull] ModelBindingContext bindingContext)
protected virtual object GetModel([NotNull] ModelBindingContext bindingContext)
{
if (bindingContext.Model == null)
if (bindingContext.Model != null)
{
bindingContext.Model = CreateModel(bindingContext);
return bindingContext.Model;
}
return CreateModel(bindingContext);
}
/// <summary>
@ -365,17 +392,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
// Internal for testing.
internal ModelValidationNode ProcessDto(
internal ModelValidationNode ProcessResults(
ModelBindingContext bindingContext,
ComplexModelDto dto,
IDictionary<ModelMetadata, ModelBindingResult> results,
ModelValidationNode validationNode)
{
var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider;
var modelExplorer = metadataProvider.GetModelExplorerForType(bindingContext.ModelType, bindingContext.Model);
var modelExplorer =
metadataProvider.GetModelExplorerForType(bindingContext.ModelType, bindingContext.Model);
var validationInfo = GetPropertyValidationInfo(bindingContext);
// Eliminate provided properties from RequiredProperties; leaving just *missing* required properties.
var boundProperties = dto.Results.Where(p => p.Value.IsModelSet).Select(p => p.Key.PropertyName);
var boundProperties = results.Where(p => p.Value.IsModelSet).Select(p => p.Key.PropertyName);
validationInfo.RequiredProperties.ExceptWith(boundProperties);
foreach (var missingRequiredProperty in validationInfo.RequiredProperties)
@ -389,25 +417,25 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Resources.FormatModelBinding_MissingBindRequiredMember(propertyName));
}
// For each property that ComplexModelDtoModelBinder attempted to bind, call the setter, recording
// For each property that BindPropertiesAsync() attempted to bind, call the setter, recording
// exceptions as necessary.
foreach (var entry in dto.Results)
foreach (var entry in results)
{
var dtoResult = entry.Value;
if (dtoResult != null)
var result = entry.Value;
if (result != null)
{
var propertyMetadata = entry.Key;
SetProperty(bindingContext, modelExplorer, propertyMetadata, dtoResult);
SetProperty(bindingContext, modelExplorer, propertyMetadata, result);
var dtoValidationNode = dtoResult.ValidationNode;
if (dtoValidationNode == null)
var propertyValidationNode = result.ValidationNode;
if (propertyValidationNode == null)
{
// Make sure that irrespective of if the properties of the model were bound with a value,
// Make sure that irrespective of whether the properties of the model were bound with a value,
// create a validation node so that these get validated.
dtoValidationNode = new ModelValidationNode(dtoResult.Key, entry.Key, dtoResult.Model);
propertyValidationNode = new ModelValidationNode(result.Key, entry.Key, result.Model);
}
validationNode.ChildNodes.Add(dtoValidationNode);
validationNode.ChildNodes.Add(propertyValidationNode);
}
}
@ -422,18 +450,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// The <see cref="ModelExplorer"/> for the model containing property to set.
/// </param>
/// <param name="propertyMetadata">The <see cref="ModelMetadata"/> for the property to set.</param>
/// <param name="dtoResult">The <see cref="ModelBindingResult"/> for the property's new value.</param>
/// <param name="result">The <see cref="ModelBindingResult"/> for the property's new value.</param>
/// <remarks>Should succeed in all cases that <see cref="CanUpdateProperty"/> returns <c>true</c>.</remarks>
protected virtual void SetProperty(
[NotNull] ModelBindingContext bindingContext,
[NotNull] ModelExplorer modelExplorer,
[NotNull] ModelMetadata propertyMetadata,
[NotNull] ModelBindingResult dtoResult)
[NotNull] ModelBindingResult result)
{
var bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase;
var property = bindingContext.ModelType.GetProperty(
propertyMetadata.PropertyName,
bindingFlags);
var property = bindingContext.ModelType.GetProperty(propertyMetadata.PropertyName, bindingFlags);
if (property == null)
{
@ -441,33 +467,27 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return;
}
if (!result.IsModelSet)
{
// If we don't have a value, don't set it on the model and trounce a pre-initialized value.
return;
}
if (!property.CanWrite)
{
// Try to handle as a collection if property exists but is not settable.
AddToProperty(bindingContext, modelExplorer, property, dtoResult);
return;
}
object value = null;
if (dtoResult.IsModelSet)
{
value = dtoResult.Model;
}
if (!dtoResult.IsModelSet)
{
// If we don't have a value, don't set it on the model and trounce a pre-initialized
// value.
AddToProperty(bindingContext, modelExplorer, property, result);
return;
}
var value = result.Model;
try
{
propertyMetadata.PropertySetter(bindingContext.Model, value);
}
catch (Exception exception)
{
AddModelError(exception, bindingContext, dtoResult);
AddModelError(exception, bindingContext, result);
}
}
@ -475,18 +495,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
ModelBindingContext bindingContext,
ModelExplorer modelExplorer,
PropertyInfo property,
ModelBindingResult dtoResult)
ModelBindingResult result)
{
var propertyExplorer = modelExplorer.GetExplorerForProperty(property.Name);
var target = propertyExplorer.Model;
var source = dtoResult.Model;
if (!dtoResult.IsModelSet || target == null || source == null)
var source = result.Model;
if (target == null || source == null)
{
// Cannot copy to or from a null collection.
return;
}
if (target == source)
{
// Added to the target collection in BindPropertiesAsync().
return;
}
// Determine T if this is an ICollection<T> property. No need for a T[] case because CanUpdateProperty()
// ensures property is either settable or not an array. Underlying assumption is that CanUpdateProperty()
// and SetProperty() are overridden together.
@ -507,7 +533,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
catch (Exception exception)
{
AddModelError(exception, bindingContext, dtoResult);
AddModelError(exception, bindingContext, result);
}
}
@ -529,7 +555,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private static void AddModelError(
Exception exception,
ModelBindingContext bindingContext,
ModelBindingResult dtoResult)
ModelBindingResult result)
{
var targetInvocationException = exception as TargetInvocationException;
if (targetInvocationException != null && targetInvocationException.InnerException != null)
@ -539,7 +565,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Do not add an error message if a binding error has already occurred for this property.
var modelState = bindingContext.ModelState;
var modelStateKey = dtoResult.Key;
var modelStateKey = result.Key;
var validationState = modelState.GetFieldValidationState(modelStateKey);
if (validationState == ModelValidationState.Unvalidated)
{

View File

@ -36,7 +36,6 @@ namespace Microsoft.AspNet.Mvc
options.ModelBinders.Add(new FormCollectionModelBinder());
options.ModelBinders.Add(new GenericModelBinder());
options.ModelBinders.Add(new MutableObjectModelBinder());
options.ModelBinders.Add(new ComplexModelDtoModelBinder());
// Set up default output formatters.
options.OutputFormatters.Add(new HttpNoContentOutputFormatter());

View File

@ -1,32 +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.Linq;
using Xunit;
namespace Microsoft.AspNet.Mvc.ModelBinding.Test
{
public class ComplexModelDtoTest
{
[Fact]
public void ConstructorSetsProperties()
{
// Arrange
ModelMetadata modelMetadata = GetModelMetadata();
ModelMetadata[] propertyMetadata = new ModelMetadata[0];
// Act
ComplexModelDto dto = new ComplexModelDto(modelMetadata, propertyMetadata);
// Assert
Assert.Equal(modelMetadata, dto.ModelMetadata);
Assert.Equal(propertyMetadata, dto.PropertyMetadata.ToArray());
Assert.Empty(dto.Results);
}
private static ModelMetadata GetModelMetadata()
{
return new EmptyModelMetadataProvider().GetMetadataForProperty(typeof(string), "Length");
}
}
}

View File

@ -534,7 +534,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
new TypeMatchModelBinder(),
new ByteArrayModelBinder(),
new GenericModelBinder(),
new ComplexModelDtoModelBinder(),
new TypeConverterModelBinder(),
new MutableObjectModelBinder()
};

View File

@ -433,7 +433,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
new TypeConverterModelBinder(),
new TypeMatchModelBinder(),
new MutableObjectModelBinder(),
new ComplexModelDtoModelBinder(),
};
return new CompositeModelBinder(binders);

View File

@ -73,8 +73,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
};
var mutableBinder = new TestableMutableObjectModelBinder();
bindingContext.PropertyMetadata = mutableBinder.GetMetadataForProperties(
bindingContext.ModelBindingContext).ToArray();
bindingContext.PropertyMetadata =
mutableBinder.GetMetadataForProperties(bindingContext.ModelBindingContext).ToArray();
// Act
var canCreate = await mutableBinder.CanCreateModel(bindingContext);
@ -160,14 +160,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
MetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(),
},
// Setting it to empty ensures that model does not get created becasue of no model name.
// Setting it to empty ensures that model does not get created because of no model name.
ModelName = "dummyModelName",
},
};
var mutableBinder = new TestableMutableObjectModelBinder();
bindingContext.PropertyMetadata = mutableBinder.GetMetadataForProperties(
bindingContext.ModelBindingContext).ToArray();
bindingContext.PropertyMetadata =
mutableBinder.GetMetadataForProperties(bindingContext.ModelBindingContext).ToArray();
// Act
var retModel = await mutableBinder.CanCreateModel(bindingContext);
@ -232,8 +232,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
};
var mutableBinder = new TestableMutableObjectModelBinder();
bindingContext.PropertyMetadata = mutableBinder.GetMetadataForProperties(
bindingContext.ModelBindingContext).ToArray();
bindingContext.PropertyMetadata =
mutableBinder.GetMetadataForProperties(bindingContext.ModelBindingContext).ToArray();
// Act
var canCreate = await mutableBinder.CanCreateModel(bindingContext);
@ -256,8 +256,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
};
var mutableBinder = new TestableMutableObjectModelBinder();
bindingContext.PropertyMetadata = mutableBinder.GetMetadataForProperties(
bindingContext.ModelBindingContext).ToArray();
bindingContext.PropertyMetadata =
mutableBinder.GetMetadataForProperties(bindingContext.ModelBindingContext).ToArray();
// Act
var retModel = await mutableBinder.CanCreateModel(bindingContext);
@ -299,8 +299,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
};
var mutableBinder = new TestableMutableObjectModelBinder();
bindingContext.PropertyMetadata = mutableBinder.GetMetadataForProperties(
bindingContext.ModelBindingContext).ToArray();
bindingContext.PropertyMetadata =
mutableBinder.GetMetadataForProperties(bindingContext.ModelBindingContext).ToArray();
// Act
var retModel = await mutableBinder.CanCreateModel(bindingContext);
@ -359,8 +359,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
};
var mutableBinder = new TestableMutableObjectModelBinder();
bindingContext.PropertyMetadata = mutableBinder.GetMetadataForProperties(
bindingContext.ModelBindingContext).ToArray();
bindingContext.PropertyMetadata =
mutableBinder.GetMetadataForProperties(bindingContext.ModelBindingContext).ToArray();
// Act
var retModel = await mutableBinder.CanCreateModel(bindingContext);
@ -374,7 +374,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
[InlineData(typeof(TypeWithUnmarkedAndBinderMetadataMarkedProperties), true)]
[InlineData(typeof(TypeWithNoBinderMetadata), false)]
[InlineData(typeof(TypeWithNoBinderMetadata), true)]
public async Task CanCreateModel_UnmarkedProperties_UsesCurrentValueProvider(Type modelType, bool valueProviderProvidesValue)
public async Task CanCreateModel_UnmarkedProperties_UsesCurrentValueProvider(
Type modelType,
bool valueProviderProvidesValue)
{
var mockValueProvider = new Mock<IValueProvider>();
mockValueProvider.Setup(o => o.ContainsPrefixAsync(It.IsAny<string>()))
@ -402,8 +404,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
};
var mutableBinder = new TestableMutableObjectModelBinder();
bindingContext.PropertyMetadata = mutableBinder.GetMetadataForProperties(
bindingContext.ModelBindingContext).ToArray();
bindingContext.PropertyMetadata =
mutableBinder.GetMetadataForProperties(bindingContext.ModelBindingContext).ToArray();
// Act
var retModel = await mutableBinder.CanCreateModel(bindingContext);
@ -417,10 +419,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
// Arrange
var mockValueProvider = new Mock<IValueProvider>();
mockValueProvider.Setup(o => o.ContainsPrefixAsync(It.IsAny<string>()))
.Returns(Task.FromResult(true));
mockValueProvider
.Setup(o => o.ContainsPrefixAsync(It.IsAny<string>()))
.Returns(Task.FromResult(true));
// Mock binder fails to bind all properties.
var mockBinder = new Mock<IModelBinder>();
mockBinder
.Setup(o => o.BindModelAsync(It.IsAny<ModelBindingContext>()))
.Returns(Task.FromResult<ModelBindingResult>(null));
var mockDtoBinder = new Mock<IModelBinder>();
var bindingContext = new ModelBindingContext
{
ModelMetadata = GetMetadataForType(typeof(Person)),
@ -428,26 +436,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
ValueProvider = mockValueProvider.Object,
OperationBindingContext = new OperationBindingContext
{
ModelBinder = mockDtoBinder.Object,
ModelBinder = mockBinder.Object,
MetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(),
ValidatorProvider = Mock.Of<IModelValidatorProvider>()
}
};
mockDtoBinder
.Setup(o => o.BindModelAsync(It.IsAny<ModelBindingContext>()))
.Returns((ModelBindingContext mbc) =>
{
// just return the DTO unchanged
return Task.FromResult(new ModelBindingResult(mbc.Model, mbc.ModelName, true));
});
var model = new Person();
var testableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
testableBinder
.Setup(o => o.EnsureModelPublic(bindingContext))
.Callback<ModelBindingContext>(c => c.Model = model)
.Setup(o => o.GetModelPublic(bindingContext))
.Returns(model)
.Verifiable();
testableBinder
.Setup(o => o.GetMetadataForProperties(bindingContext))
@ -469,38 +469,36 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
// Arrange
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));
// Mock binder fails to bind all properties.
var mockBinder = new Mock<IModelBinder>();
mockBinder
.Setup(o => o.BindModelAsync(It.IsAny<ModelBindingContext>()))
.Returns(Task.FromResult<ModelBindingResult>(null));
var mockDtoBinder = new Mock<IModelBinder>();
var bindingContext = new ModelBindingContext
{
IsTopLevelObject = true,
ModelMetadata = GetMetadataForType(typeof(Person)),
ModelName = "",
ModelName = string.Empty,
ValueProvider = mockValueProvider.Object,
OperationBindingContext = new OperationBindingContext
{
ModelBinder = mockDtoBinder.Object,
ModelBinder = mockBinder.Object,
MetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(),
ValidatorProvider = Mock.Of<IModelValidatorProvider>()
}
};
mockDtoBinder
.Setup(o => o.BindModelAsync(It.IsAny<ModelBindingContext>()))
.Returns((ModelBindingContext mbc) =>
{
// just return the DTO unchanged
return Task.FromResult(new ModelBindingResult(mbc.Model, mbc.ModelName, true));
});
var model = new Person();
var testableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
testableBinder
.Setup(o => o.EnsureModelPublic(bindingContext))
.Callback<ModelBindingContext>(c => c.Model = model)
.Setup(o => o.GetModelPublic(bindingContext))
.Returns(model)
.Verifiable();
testableBinder
@ -576,7 +574,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
[Fact]
public void EnsureModel_ModelIsNotNull_DoesNothing()
public void GetModel_ModelIsNotNull_DoesNothing()
{
// Arrange
var bindingContext = new ModelBindingContext
@ -585,19 +583,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
ModelMetadata = GetMetadataForType(typeof(Person))
};
var originalModel = bindingContext.Model;
var testableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
// Act
var originalModel = bindingContext.Model;
testableBinder.Object.EnsureModelPublic(bindingContext);
var newModel = testableBinder.Object.GetModelPublic(bindingContext);
// Assert
Assert.Same(originalModel, bindingContext.Model);
Assert.Same(originalModel, newModel);
testableBinder.Verify(o => o.CreateModelPublic(bindingContext), Times.Never());
}
[Fact]
public void EnsureModel_ModelIsNull_CallsCreateModel()
public void GetModel_ModelIsNull_CallsCreateModel()
{
// Arrange
var bindingContext = new ModelBindingContext
@ -605,17 +604,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
ModelMetadata = GetMetadataForType(typeof(Person))
};
var originalModel = bindingContext.Model;
var testableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
testableBinder.Setup(o => o.CreateModelPublic(bindingContext))
.Returns(new Person()).Verifiable();
// Act
var originalModel = bindingContext.Model;
testableBinder.Object.EnsureModelPublic(bindingContext);
var newModel = bindingContext.Model;
var newModel = testableBinder.Object.GetModelPublic(bindingContext);
// Assert
Assert.Null(originalModel);
Assert.Null(bindingContext.Model);
Assert.IsType<Person>(newModel);
testableBinder.Verify();
}
@ -779,7 +779,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
[Fact]
[ReplaceCulture]
public void ProcessDto_BindRequiredFieldMissing_RaisesModelError()
public void ProcessResults_BindRequiredFieldMissing_RaisesModelError()
{
// Arrange
var model = new ModelWithBindRequired
@ -800,18 +800,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
ValidatorProvider = Mock.Of<IModelValidatorProvider>()
}
};
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
var nameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "Name");
dto.Results[nameProperty] = new ModelBindingResult(
"John Doe",
isModelSet: true,
key: "");
var results = containerMetadata.Properties.ToDictionary(
property => property,
property => new ModelBindingResult(model: null, key: property.PropertyName, isModelSet: false));
var nameProperty = containerMetadata.Properties[nameof(model.Name)];
results[nameProperty] = new ModelBindingResult("John Doe", isModelSet: true, key: string.Empty);
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
var testableBinder = new TestableMutableObjectModelBinder();
// Act
testableBinder.ProcessDto(bindingContext, dto, modelValidationNode);
testableBinder.ProcessResults(bindingContext, results, modelValidationNode);
// Assert
var modelStateDictionary = bindingContext.ModelState;
@ -829,7 +829,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
[Fact]
[ReplaceCulture]
public void ProcessDto_DataMemberIsRequiredFieldMissing_RaisesModelError()
public void ProcessResults_DataMemberIsRequiredFieldMissing_RaisesModelError()
{
// Arrange
var model = new ModelWithDataMemberIsRequired
@ -850,19 +850,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
ValidatorProvider = Mock.Of<IModelValidatorProvider>()
}
};
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
var nameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "Name");
dto.Results[nameProperty] = new ModelBindingResult(
"John Doe",
isModelSet: true,
key: "");
var results = containerMetadata.Properties.ToDictionary(
property => property,
property => new ModelBindingResult(model: null, key: property.PropertyName, isModelSet: false));
var nameProperty = containerMetadata.Properties[nameof(model.Name)];
results[nameProperty] = new ModelBindingResult("John Doe", isModelSet: true, key: string.Empty);
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
var testableBinder = new TestableMutableObjectModelBinder();
// Act
testableBinder.ProcessDto(bindingContext, dto, modelValidationNode);
testableBinder.ProcessResults(bindingContext, results, modelValidationNode);
// Assert
var modelStateDictionary = bindingContext.ModelState;
@ -880,7 +879,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
[Fact]
[ReplaceCulture]
public void ProcessDto_ValueTypePropertyWithBindRequired_SetToNull_CapturesException()
public void ProcessResults_ValueTypePropertyWithBindRequired_SetToNull_CapturesException()
{
// Arrange
var model = new ModelWithBindRequired
@ -903,27 +902,22 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
};
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
var testableBinder = new TestableMutableObjectModelBinder();
var propertyMetadata = dto.PropertyMetadata.Single(o => o.PropertyName == "Name");
dto.Results[propertyMetadata] = new ModelBindingResult(
"John Doe",
isModelSet: true,
key: "theModel.Name");
var results = containerMetadata.Properties.ToDictionary(
property => property,
property => new ModelBindingResult(model: null, key: property.PropertyName, isModelSet: false));
var propertyMetadata = containerMetadata.Properties[nameof(model.Name)];
results[propertyMetadata] = new ModelBindingResult("John Doe", isModelSet: true, key: "theModel.Name");
// Attempt to set non-Nullable property to null. BindRequiredAttribute should not be relevant in this
// case because the binding exists.
propertyMetadata = dto.PropertyMetadata.Single(o => o.PropertyName == "Age");
dto.Results[propertyMetadata] = new ModelBindingResult(
null,
isModelSet: true,
key: "theModel.Age");
propertyMetadata = containerMetadata.Properties[nameof(model.Age)];
results[propertyMetadata] = new ModelBindingResult(model: null, isModelSet: true, key: "theModel.Age");
var testableBinder = new TestableMutableObjectModelBinder();
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
// Act
testableBinder.ProcessDto(bindingContext, dto, modelValidationNode);
testableBinder.ProcessResults(bindingContext, results, modelValidationNode);
// Assert
var modelStateDictionary = bindingContext.ModelState;
@ -941,19 +935,22 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
[Fact]
public void ProcessDto_ValueTypeProperty_WithBindingOptional_NoValueSet_NoError()
public void ProcessResults_ValueTypeProperty_WithBindingOptional_NoValueSet_NoError()
{
// Arrange
var model = new BindingOptionalProperty();
var containerMetadata = GetMetadataForType(model.GetType());
var bindingContext = CreateContext(containerMetadata, model);
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
var results = containerMetadata.Properties.ToDictionary(
property => property,
property => new ModelBindingResult(model: null, key: property.PropertyName, isModelSet: false));
var testableBinder = new TestableMutableObjectModelBinder();
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
// Act
testableBinder.ProcessDto(bindingContext, dto, modelValidationNode);
testableBinder.ProcessResults(bindingContext, results, modelValidationNode);
// Assert
var modelStateDictionary = bindingContext.ModelState;
@ -961,19 +958,22 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
[Fact]
public void ProcessDto_NullableValueTypeProperty_NoValueSet_NoError()
public void ProcessResults_NullableValueTypeProperty_NoValueSet_NoError()
{
// Arrange
var model = new NullableValueTypeProperty();
var containerMetadata = GetMetadataForType(model.GetType());
var bindingContext = CreateContext(containerMetadata, model);
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
var results = containerMetadata.Properties.ToDictionary(
property => property,
property => new ModelBindingResult(model: null, key: property.PropertyName, isModelSet: false));
var testableBinder = new TestableMutableObjectModelBinder();
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
// Act
testableBinder.ProcessDto(bindingContext, dto, modelValidationNode);
testableBinder.ProcessResults(bindingContext, results, modelValidationNode);
// Assert
var modelStateDictionary = bindingContext.ModelState;
@ -981,7 +981,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
[Fact]
public void ProcessDto_ValueTypeProperty_TriesToSetNullModel_CapturesException()
public void ProcessResults_ValueTypeProperty_TriesToSetNullModel_CapturesException()
{
// Arrange
var model = new Person();
@ -990,23 +990,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var bindingContext = CreateContext(containerMetadata, model);
var modelStateDictionary = bindingContext.ModelState;
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
var results = containerMetadata.Properties.ToDictionary(
property => property,
property => new ModelBindingResult(model: null, key: property.PropertyName, isModelSet: false));
var testableBinder = new TestableMutableObjectModelBinder();
// The [DefaultValue] on ValueTypeRequiredWithDefaultValue is ignored by model binding.
var expectedValue = 0;
// Make ValueTypeRequired invalid.
var propertyMetadata = dto.PropertyMetadata.Single(p => p.PropertyName == nameof(Person.ValueTypeRequired));
dto.Results[propertyMetadata] = new ModelBindingResult(
null,
var propertyMetadata = containerMetadata.Properties[nameof(Person.ValueTypeRequired)];
results[propertyMetadata] = new ModelBindingResult(
model: null,
isModelSet: true,
key: "theModel." + nameof(Person.ValueTypeRequired));
// Make ValueTypeRequiredWithDefaultValue invalid
propertyMetadata = dto.PropertyMetadata
.Single(p => p.PropertyName == nameof(Person.ValueTypeRequiredWithDefaultValue));
dto.Results[propertyMetadata] = new ModelBindingResult(
propertyMetadata = containerMetadata.Properties[nameof(Person.ValueTypeRequiredWithDefaultValue)];
results[propertyMetadata] = new ModelBindingResult(
model: null,
isModelSet: true,
key: "theModel." + nameof(Person.ValueTypeRequiredWithDefaultValue));
@ -1014,7 +1015,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
// Act
testableBinder.ProcessDto(bindingContext, dto, modelValidationNode);
testableBinder.ProcessResults(bindingContext, results, modelValidationNode);
// Assert
Assert.False(modelStateDictionary.IsValid);
@ -1050,7 +1051,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
[Fact]
public void ProcessDto_ValueTypeProperty_NoValue_NoError()
public void ProcessResults_ValueTypeProperty_NoValue_NoError()
{
// Arrange
var model = new Person();
@ -1059,20 +1060,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var bindingContext = CreateContext(containerMetadata, model);
var modelState = bindingContext.ModelState;
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
var results = containerMetadata.Properties.ToDictionary(
property => property,
property => new ModelBindingResult(model: null, key: property.PropertyName, isModelSet: false));
var testableBinder = new TestableMutableObjectModelBinder();
// Make ValueTypeRequired invalid.
var propertyMetadata = dto.PropertyMetadata.Single(p => p.PropertyName == nameof(Person.ValueTypeRequired));
dto.Results[propertyMetadata] = new ModelBindingResult(
null,
var propertyMetadata = containerMetadata.Properties[nameof(Person.ValueTypeRequired)];
results[propertyMetadata] = new ModelBindingResult(
model: null,
isModelSet: false,
key: "theModel." + nameof(Person.ValueTypeRequired));
// Make ValueTypeRequiredWithDefaultValue invalid
propertyMetadata = dto.PropertyMetadata
.Single(p => p.PropertyName == nameof(Person.ValueTypeRequiredWithDefaultValue));
dto.Results[propertyMetadata] = new ModelBindingResult(
propertyMetadata = containerMetadata.Properties[nameof(Person.ValueTypeRequiredWithDefaultValue)];
results[propertyMetadata] = new ModelBindingResult(
model: null,
isModelSet: false,
key: "theModel." + nameof(Person.ValueTypeRequiredWithDefaultValue));
@ -1080,14 +1082,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
// Act
testableBinder.ProcessDto(bindingContext, dto, modelValidationNode);
testableBinder.ProcessResults(bindingContext, results, modelValidationNode);
// Assert
Assert.True(modelState.IsValid);
}
[Fact]
public void ProcessDto_ProvideRequiredFields_Success()
public void ProcessResults_ProvideRequiredFields_Success()
{
// Arrange
var model = new Person();
@ -1096,36 +1098,35 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var bindingContext = CreateContext(containerMetadata, model);
var modelStateDictionary = bindingContext.ModelState;
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
var results = containerMetadata.Properties.ToDictionary(
property => property,
property => new ModelBindingResult(model: null, key: property.PropertyName, isModelSet: false));
var testableBinder = new TestableMutableObjectModelBinder();
// Make ValueTypeRequired valid.
var propertyMetadata = dto.PropertyMetadata
.Single(p => p.PropertyName == nameof(Person.ValueTypeRequired));
dto.Results[propertyMetadata] = new ModelBindingResult(
41,
var propertyMetadata = containerMetadata.Properties[nameof(Person.ValueTypeRequired)];
results[propertyMetadata] = new ModelBindingResult(
model: 41,
isModelSet: true,
key: "theModel." + nameof(Person.ValueTypeRequired));
// Make ValueTypeRequiredWithDefaultValue valid.
propertyMetadata = dto.PropertyMetadata
.Single(p => p.PropertyName == nameof(Person.ValueTypeRequiredWithDefaultValue));
dto.Results[propertyMetadata] = new ModelBindingResult(
propertyMetadata = containerMetadata.Properties[nameof(Person.ValueTypeRequiredWithDefaultValue)];
results[propertyMetadata] = new ModelBindingResult(
model: 57,
isModelSet: true,
key: "theModel." + nameof(Person.ValueTypeRequiredWithDefaultValue));
// Also remind ProcessDto about PropertyWithDefaultValue -- as ComplexModelDtoModelBinder would.
propertyMetadata = dto.PropertyMetadata
.Single(p => p.PropertyName == nameof(Person.PropertyWithDefaultValue));
dto.Results[propertyMetadata] = new ModelBindingResult(
// Also remind ProcessResults about PropertyWithDefaultValue -- as BindPropertiesAsync() would.
propertyMetadata = containerMetadata.Properties[nameof(Person.PropertyWithDefaultValue)];
results[propertyMetadata] = new ModelBindingResult(
model: null,
isModelSet: false,
key: "theModel." + nameof(Person.PropertyWithDefaultValue));
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
// Act
testableBinder.ProcessDto(bindingContext, dto, modelValidationNode);
testableBinder.ProcessResults(bindingContext, results, modelValidationNode);
// Assert
Assert.True(modelStateDictionary.IsValid);
@ -1139,7 +1140,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// [Required] cannot provide a custom validation for [BindRequired] errors.
[Fact]
public void ProcessDto_ValueTypePropertyWithBindRequired_RequiredValidatorIgnored()
public void ProcessResults_ValueTypePropertyWithBindRequired_RequiredValidatorIgnored()
{
// Arrange
var model = new ModelWithBindRequiredAndRequiredAttribute();
@ -1148,21 +1149,23 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var bindingContext = CreateContext(containerMetadata, model);
var modelStateDictionary = bindingContext.ModelState;
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
var results = containerMetadata.Properties.ToDictionary(
property => property,
property => new ModelBindingResult(model: null, key: property.PropertyName, isModelSet: false));
var testableBinder = new TestableMutableObjectModelBinder();
// Make ValueTypeProperty not have a value.
var propertyMetadata = containerMetadata
.Properties[nameof(ModelWithBindRequiredAndRequiredAttribute.ValueTypeProperty)];
dto.Results[propertyMetadata] = new ModelBindingResult(
null,
results[propertyMetadata] = new ModelBindingResult(
model: null,
isModelSet: false,
key: "theModel." + nameof(ModelWithBindRequiredAndRequiredAttribute.ValueTypeProperty));
// Make ReferenceTypeProperty have a value.
propertyMetadata = containerMetadata
.Properties[nameof(ModelWithBindRequiredAndRequiredAttribute.ReferenceTypeProperty)];
dto.Results[propertyMetadata] = new ModelBindingResult(
results[propertyMetadata] = new ModelBindingResult(
model: "value",
isModelSet: true,
key: "theModel." + nameof(ModelWithBindRequiredAndRequiredAttribute.ReferenceTypeProperty));
@ -1170,7 +1173,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
// Act
testableBinder.ProcessDto(bindingContext, dto, modelValidationNode);
testableBinder.ProcessResults(bindingContext, results, modelValidationNode);
// Assert
Assert.False(modelStateDictionary.IsValid);
@ -1190,7 +1193,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// [Required] cannot provide a custom validation for [BindRequired] errors.
[Fact]
public void ProcessDto_ReferenceTypePropertyWithBindRequired_RequiredValidatorIgnored()
public void ProcessResults_ReferenceTypePropertyWithBindRequired_RequiredValidatorIgnored()
{
// Arrange
var model = new ModelWithBindRequiredAndRequiredAttribute();
@ -1199,21 +1202,23 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var bindingContext = CreateContext(containerMetadata, model);
var modelStateDictionary = bindingContext.ModelState;
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
var results = containerMetadata.Properties.ToDictionary(
property => property,
property => new ModelBindingResult(model: null, key: property.PropertyName, isModelSet: false));
var testableBinder = new TestableMutableObjectModelBinder();
// Make ValueTypeProperty have a value.
var propertyMetadata = containerMetadata
.Properties[nameof(ModelWithBindRequiredAndRequiredAttribute.ValueTypeProperty)];
dto.Results[propertyMetadata] = new ModelBindingResult(
17,
results[propertyMetadata] = new ModelBindingResult(
model: 17,
isModelSet: true,
key: "theModel." + nameof(ModelWithBindRequiredAndRequiredAttribute.ValueTypeProperty));
// Make ReferenceTypeProperty not have a value.
propertyMetadata = containerMetadata
.Properties[nameof(ModelWithBindRequiredAndRequiredAttribute.ReferenceTypeProperty)];
dto.Results[propertyMetadata] = new ModelBindingResult(
results[propertyMetadata] = new ModelBindingResult(
model: null,
isModelSet: false,
key: "theModel." + nameof(ModelWithBindRequiredAndRequiredAttribute.ReferenceTypeProperty));
@ -1221,7 +1226,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
// Act
testableBinder.ProcessDto(bindingContext, dto, modelValidationNode);
testableBinder.ProcessResults(bindingContext, results, modelValidationNode);
// Assert
Assert.False(modelStateDictionary.IsValid);
@ -1241,7 +1246,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
[Fact]
public void ProcessDto_Success()
public void ProcessResults_Success()
{
// Arrange
var dob = new DateTime(2001, 1, 1);
@ -1252,28 +1257,27 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var containerMetadata = GetMetadataForType(model.GetType());
var bindingContext = CreateContext(containerMetadata, model);
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
var results = containerMetadata.Properties.ToDictionary(
property => property,
property => new ModelBindingResult(model: null, key: property.PropertyName, isModelSet: false));
var firstNameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "FirstName");
dto.Results[firstNameProperty] = new ModelBindingResult(
"John",
var firstNameProperty = containerMetadata.Properties[nameof(model.FirstName)];
results[firstNameProperty] = new ModelBindingResult(
model: "John",
isModelSet: true,
key: "");
key: nameof(model.FirstName));
var lastNameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "LastName");
dto.Results[lastNameProperty] = new ModelBindingResult(
"Doe",
var lastNameProperty = containerMetadata.Properties[nameof(model.LastName)];
results[lastNameProperty] = new ModelBindingResult(
model: "Doe",
isModelSet: true,
key: "");
key: nameof(model.LastName));
var dobProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "DateOfBirth");
dto.Results[dobProperty] = null;
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
var testableBinder = new TestableMutableObjectModelBinder();
// Act
testableBinder.ProcessDto(bindingContext, dto, modelValidationNode);
testableBinder.ProcessResults(bindingContext, results, modelValidationNode);
// Assert
Assert.Equal("John", model.FirstName);
@ -1283,15 +1287,34 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Ensure that we add child nodes for all the nodes which have a result (irrespective of if they
// are bound or not).
Assert.Equal(2, modelValidationNode.ChildNodes.Count());
Assert.Equal(5, modelValidationNode.ChildNodes.Count);
var validationNode = modelValidationNode.ChildNodes[0];
Assert.Equal("", validationNode.Key);
Assert.Equal("John", validationNode.Model);
validationNode = modelValidationNode.ChildNodes[1];
Assert.Equal("", validationNode.Key);
Assert.Equal("Doe", validationNode.Model);
Assert.Collection(modelValidationNode.ChildNodes,
child =>
{
Assert.Equal(nameof(model.DateOfBirth), child.Key);
Assert.Equal(null, child.Model);
},
child =>
{
Assert.Equal(nameof(model.DateOfDeath), child.Key);
Assert.Equal(null, child.Model);
},
child =>
{
Assert.Equal(nameof(model.FirstName), child.Key);
Assert.Equal("John", child.Model);
},
child =>
{
Assert.Equal(nameof(model.LastName), child.Key);
Assert.Equal("Doe", child.Model);
},
child =>
{
Assert.Equal(nameof(model.NonUpdateableProperty), child.Key);
Assert.Equal(null, child.Model);
});
}
[Fact]
@ -1303,21 +1326,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider;
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(Person), model);
var propertyMetadata = bindingContext.ModelMetadata.Properties["PropertyWithDefaultValue"];
var dtoResult = new ModelBindingResult(
model: null,
isModelSet: false,
key: "foo");
var propertyMetadata = bindingContext.ModelMetadata.Properties[nameof(model.PropertyWithDefaultValue)];
var result = new ModelBindingResult(model: null, isModelSet: false, key: "foo");
var testableBinder = new TestableMutableObjectModelBinder();
// Act
testableBinder.SetProperty(
bindingContext,
modelExplorer,
propertyMetadata,
dtoResult);
testableBinder.SetProperty(bindingContext, modelExplorer, propertyMetadata, result);
// Assert
var person = Assert.IsType<Person>(bindingContext.Model);
@ -1334,22 +1349,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider;
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(Person), model);
var propertyMetadata = bindingContext.ModelMetadata.Properties["PropertyWithInitializedValue"];
var propertyMetadata = bindingContext.ModelMetadata.Properties[nameof(model.PropertyWithInitializedValue)];
// This value won't be used because IsModelBound = false.
var dtoResult = new ModelBindingResult(
model: "bad-value",
isModelSet: false,
key: "foo");
var result = new ModelBindingResult(model: "bad-value", isModelSet: false, key: "foo");
var testableBinder = new TestableMutableObjectModelBinder();
// Act
testableBinder.SetProperty(
bindingContext,
modelExplorer,
propertyMetadata,
dtoResult);
testableBinder.SetProperty(bindingContext, modelExplorer, propertyMetadata, result);
// Assert
var person = Assert.IsType<Person>(bindingContext.Model);
@ -1366,22 +1374,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider;
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(Person), model);
var propertyMetadata = bindingContext.ModelMetadata.Properties["PropertyWithInitializedValueAndDefault"];
var propertyMetadata =
bindingContext.ModelMetadata.Properties[nameof(model.PropertyWithInitializedValueAndDefault)];
// This value won't be used because IsModelBound = false.
var dtoResult = new ModelBindingResult(
model: "bad-value",
isModelSet: false,
key: "foo");
var result = new ModelBindingResult(model: "bad-value", isModelSet: false, key: "foo");
var testableBinder = new TestableMutableObjectModelBinder();
// Act
testableBinder.SetProperty(
bindingContext,
modelExplorer,
propertyMetadata,
dtoResult);
testableBinder.SetProperty(bindingContext, modelExplorer, propertyMetadata, result);
// Assert
var person = Assert.IsType<Person>(bindingContext.Model);
@ -1398,21 +1400,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider;
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(Person), model);
var propertyMetadata = bindingContext.ModelMetadata.Properties["NonUpdateableProperty"];
var dtoResult = new ModelBindingResult(
model: null,
isModelSet: false,
key: "foo");
var propertyMetadata = bindingContext.ModelMetadata.Properties[nameof(model.NonUpdateableProperty)];
var result = new ModelBindingResult(model: null, isModelSet: false, key: "foo");
var testableBinder = new TestableMutableObjectModelBinder();
// Act
testableBinder.SetProperty(
bindingContext,
modelExplorer,
propertyMetadata,
dtoResult);
testableBinder.SetProperty(bindingContext, modelExplorer, propertyMetadata, result);
// Assert
// If didn't throw, success!
@ -1453,7 +1447,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var modelExplorer = metadataProvider.GetModelExplorerForType(type, model);
var propertyMetadata = bindingContext.ModelMetadata.Properties[propertyName];
var dtoResult = new ModelBindingResult(
var result = new ModelBindingResult(
model: new Simple { Name = "Hanna" },
isModelSet: true,
key: propertyName);
@ -1461,11 +1455,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var testableBinder = new TestableMutableObjectModelBinder();
// Act
testableBinder.SetProperty(
bindingContext,
modelExplorer,
propertyMetadata,
dtoResult);
testableBinder.SetProperty(bindingContext, modelExplorer, propertyMetadata, result);
// Assert
Assert.Equal("Joe", propertAccessor(model));
@ -1535,16 +1525,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var modelExplorer = metadataProvider.GetModelExplorerForType(type, model);
var propertyMetadata = bindingContext.ModelMetadata.Properties[propertyName];
var dtoResult = new ModelBindingResult(model: collection, isModelSet: true, key: propertyName);
var result = new ModelBindingResult(model: collection, isModelSet: true, key: propertyName);
var testableBinder = new TestableMutableObjectModelBinder();
// Act
testableBinder.SetProperty(
bindingContext,
modelExplorer,
propertyMetadata,
dtoResult);
testableBinder.SetProperty(bindingContext, modelExplorer, propertyMetadata, result);
// Assert
Assert.Equal(collection, propertyAccessor(model));
@ -1561,21 +1546,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider;
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(Person), model);
var propertyMetadata = bindingContext.ModelMetadata.Properties["DateOfBirth"];
var dtoResult = new ModelBindingResult(
new DateTime(2001, 1, 1),
key: "foo",
isModelSet: true);
var propertyMetadata = bindingContext.ModelMetadata.Properties[nameof(model.DateOfBirth)];
var result = new ModelBindingResult(new DateTime(2001, 1, 1), key: "foo", isModelSet: true);
var testableBinder = new TestableMutableObjectModelBinder();
// Act
testableBinder.SetProperty(
bindingContext,
modelExplorer,
propertyMetadata,
dtoResult);
testableBinder.SetProperty(bindingContext, modelExplorer, propertyMetadata, result);
// Assert
Assert.True(bindingContext.ModelState.IsValid);
@ -1596,21 +1573,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider;
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(Person), model);
var propertyMetadata = bindingContext.ModelMetadata.Properties["DateOfDeath"];
var dtoResult = new ModelBindingResult(
new DateTime(1800, 1, 1),
isModelSet: true,
key: "foo");
var propertyMetadata = bindingContext.ModelMetadata.Properties[nameof(model.DateOfDeath)];
var result = new ModelBindingResult(new DateTime(1800, 1, 1), isModelSet: true, key: "foo");
var testableBinder = new TestableMutableObjectModelBinder();
// Act
testableBinder.SetProperty(
bindingContext,
modelExplorer,
propertyMetadata,
dtoResult);
testableBinder.SetProperty(bindingContext, modelExplorer, propertyMetadata, result);
// Assert
Assert.Equal("Date of death can't be before date of birth." + Environment.NewLine
@ -1629,21 +1598,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider;
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(Person), model);
var propertyMetadata = bindingContext.ModelMetadata.Properties["DateOfBirth"];
var dtoResult = new ModelBindingResult(
model: null,
isModelSet: true,
key: "foo.DateOfBirth");
var propertyMetadata = bindingContext.ModelMetadata.Properties[nameof(model.DateOfBirth)];
var result = new ModelBindingResult(model: null, isModelSet: true, key: "foo.DateOfBirth");
var testableBinder = new TestableMutableObjectModelBinder();
// Act
testableBinder.SetProperty(
bindingContext,
modelExplorer,
propertyMetadata,
dtoResult);
testableBinder.SetProperty(bindingContext, modelExplorer, propertyMetadata, result);
// Assert
Assert.False(bindingContext.ModelState.IsValid);
@ -1665,21 +1626,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider;
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(ModelWhosePropertySetterThrows), model);
var propertyMetadata = bindingContext.ModelMetadata.Properties["NameNoAttribute"];
var dtoResult = new ModelBindingResult(
model: null,
isModelSet: true,
key: "foo.NameNoAttribute");
var propertyMetadata = bindingContext.ModelMetadata.Properties[nameof(model.NameNoAttribute)];
var result = new ModelBindingResult(model: null, isModelSet: true, key: "foo.NameNoAttribute");
var testableBinder = new TestableMutableObjectModelBinder();
// Act
testableBinder.SetProperty(
bindingContext,
modelExplorer,
propertyMetadata,
dtoResult);
testableBinder.SetProperty(bindingContext, modelExplorer, propertyMetadata, result);
// Assert
Assert.False(bindingContext.ModelState.IsValid);
@ -2014,14 +1967,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return CreateModelPublic(bindingContext);
}
public virtual void EnsureModelPublic(ModelBindingContext bindingContext)
public virtual object GetModelPublic(ModelBindingContext bindingContext)
{
base.EnsureModel(bindingContext);
return base.GetModel(bindingContext);
}
protected override void EnsureModel(ModelBindingContext bindingContext)
protected override object GetModel(ModelBindingContext bindingContext)
{
EnsureModelPublic(bindingContext);
return GetModelPublic(bindingContext);
}
public virtual new IEnumerable<ModelMetadata> GetMetadataForProperties(ModelBindingContext bindingContext)
@ -2033,13 +1986,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
ModelBindingContext bindingContext,
ModelExplorer modelExplorer,
ModelMetadata propertyMetadata,
ModelBindingResult dtoResult)
ModelBindingResult result)
{
base.SetProperty(
bindingContext,
modelExplorer,
propertyMetadata,
dtoResult);
base.SetProperty(bindingContext, modelExplorer, propertyMetadata, result);
}
}
}

View File

@ -55,7 +55,6 @@ namespace Microsoft.AspNet.Mvc.Test
var binders = new IModelBinder[]
{
new TypeConverterModelBinder(),
new ComplexModelDtoModelBinder(),
new MutableObjectModelBinder()
};
@ -95,7 +94,6 @@ namespace Microsoft.AspNet.Mvc.Test
var binders = new IModelBinder[]
{
new TypeConverterModelBinder(),
new ComplexModelDtoModelBinder(),
new MutableObjectModelBinder()
};
@ -168,7 +166,6 @@ namespace Microsoft.AspNet.Mvc.Test
var binders = new IModelBinder[]
{
new TypeConverterModelBinder(),
new ComplexModelDtoModelBinder(),
new MutableObjectModelBinder()
};
@ -256,7 +253,6 @@ namespace Microsoft.AspNet.Mvc.Test
var binders = new IModelBinder[]
{
new TypeConverterModelBinder(),
new ComplexModelDtoModelBinder(),
new MutableObjectModelBinder()
};
@ -309,7 +305,6 @@ namespace Microsoft.AspNet.Mvc.Test
var binders = new IModelBinder[]
{
new TypeConverterModelBinder(),
new ComplexModelDtoModelBinder(),
new MutableObjectModelBinder()
};
@ -513,7 +508,6 @@ namespace Microsoft.AspNet.Mvc.Test
var binders = new IModelBinder[]
{
new TypeConverterModelBinder(),
new ComplexModelDtoModelBinder(),
new MutableObjectModelBinder()
};
@ -603,7 +597,6 @@ namespace Microsoft.AspNet.Mvc.Test
var binders = new IModelBinder[]
{
new TypeConverterModelBinder(),
new ComplexModelDtoModelBinder(),
new MutableObjectModelBinder()
};

View File

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.ModelBinding;
@ -16,10 +15,12 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
private class Address
{
public string Street { get; set; }
public string City { get; set; }
}
[Fact]
public async Task TryUpdateModel_ExistingModel_EmptyPrefix_GetsOverWritten()
public async Task TryUpdateModel_ExistingModel_EmptyPrefix_OverwritesBoundValues()
{
// Arrange
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
@ -28,7 +29,11 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
});
var modelState = new ModelStateDictionary();
var model = new Address { Street = "DefaultStreet" };
var model = new Address
{
Street = "DefaultStreet",
City = "Toronto",
};
var oldModel = model;
// Act
@ -40,17 +45,19 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// Model
Assert.Same(oldModel, model);
Assert.Equal("SomeStreet", model.Street);
Assert.Equal("Toronto", model.City);
// ModelState
Assert.True(modelState.IsValid);
Assert.Equal(1, modelState.Keys.Count);
var key = Assert.Single(modelState.Keys, k => k == "Street");
Assert.NotNull(modelState[key].Value);
Assert.Equal("SomeStreet", modelState[key].Value.AttemptedValue);
Assert.Equal("SomeStreet", modelState[key].Value.RawValue);
Assert.Empty(modelState[key].Errors);
Assert.Equal(ModelValidationState.Valid, modelState[key].ValidationState);
var entry = Assert.Single(modelState);
Assert.Equal("Street", entry.Key);
var state = entry.Value;
Assert.NotNull(state.Value);
Assert.Equal("SomeStreet", state.Value.AttemptedValue);
Assert.Equal("SomeStreet", state.Value.RawValue);
Assert.Empty(state.Errors);
Assert.Equal(ModelValidationState.Valid, state.ValidationState);
}
[Fact]
@ -64,6 +71,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var modelState = new ModelStateDictionary();
var model = new Address();
// Act
var result = await TryUpdateModel(model, string.Empty, operationContext, modelState);
@ -72,17 +80,72 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// Model
Assert.Equal("SomeStreet", model.Street);
Assert.Null(model.City);
// ModelState
Assert.True(modelState.IsValid);
Assert.Equal(1, modelState.Keys.Count);
var key = Assert.Single(modelState.Keys, k => k == "Street");
Assert.NotNull(modelState[key].Value);
Assert.Equal("SomeStreet", modelState[key].Value.AttemptedValue);
Assert.Equal("SomeStreet", modelState[key].Value.RawValue);
Assert.Empty(modelState[key].Errors);
Assert.Equal(ModelValidationState.Valid, modelState[key].ValidationState);
var entry = Assert.Single(modelState);
Assert.Equal("Street", entry.Key);
var state = entry.Value;
Assert.NotNull(state.Value);
Assert.Equal("SomeStreet", state.Value.AttemptedValue);
Assert.Equal("SomeStreet", state.Value.RawValue);
Assert.Empty(state.Errors);
Assert.Equal(ModelValidationState.Valid, state.ValidationState);
}
private class Person1
{
public string Name { get; set; }
public Address Address { get; set; }
}
[Fact]
public async Task TryUpdateModel_NestedPoco_EmptyPrefix_DoesNotTrounceUnboundValues()
{
// Arrange
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
request.QueryString = QueryString.Create("Address.Street", "SomeStreet");
});
var modelState = new ModelStateDictionary();
var model = new Person1
{
Name = "Joe",
Address = new Address
{
Street = "DefaultStreet",
City = "Toronto",
},
};
var oldModel = model;
// Act
var result = await TryUpdateModel(model, string.Empty, operationContext, modelState);
// Assert
Assert.True(result);
// Model
Assert.Same(oldModel, model);
Assert.Equal("Joe", model.Name);
Assert.Equal("SomeStreet", model.Address.Street);
Assert.Equal("Toronto", model.Address.City);
// ModelState
Assert.True(modelState.IsValid);
var entry = Assert.Single(modelState);
Assert.Equal("Address.Street", entry.Key);
var state = entry.Value;
Assert.NotNull(state.Value);
Assert.Equal("SomeStreet", state.Value.AttemptedValue);
Assert.Equal("SomeStreet", state.Value.RawValue);
Assert.Empty(state.Errors);
Assert.Equal(ModelValidationState.Valid, state.ValidationState);
}
private class Person2
@ -91,7 +154,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
}
[Fact]
public async Task TryUpdateModel_SettableCollectionModel_EmptyPrefix_GetsBound()
public async Task TryUpdateModel_SettableCollectionModel_EmptyPrefix_CreatesCollection()
{
// Arrange
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
@ -101,6 +164,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var modelState = new ModelStateDictionary();
var model = new Person2();
// Act
var result = await TryUpdateModel(model, string.Empty, operationContext, modelState);
@ -111,17 +175,61 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.NotNull(model.Address);
Assert.Equal(1, model.Address.Count);
Assert.Equal("SomeStreet", model.Address[0].Street);
Assert.Null(model.Address[0].City);
// ModelState
Assert.True(modelState.IsValid);
Assert.Equal(1, modelState.Keys.Count);
var key = Assert.Single(modelState.Keys, k => k == "Address[0].Street");
Assert.NotNull(modelState[key].Value);
Assert.Equal("SomeStreet", modelState[key].Value.AttemptedValue);
Assert.Equal("SomeStreet", modelState[key].Value.RawValue);
Assert.Empty(modelState[key].Errors);
Assert.Equal(ModelValidationState.Valid, modelState[key].ValidationState);
var entry = Assert.Single(modelState);
Assert.Equal("Address[0].Street", entry.Key);
var state = entry.Value;
Assert.NotNull(state.Value);
Assert.Equal("SomeStreet", state.Value.AttemptedValue);
Assert.Equal("SomeStreet", state.Value.RawValue);
Assert.Empty(state.Errors);
Assert.Equal(ModelValidationState.Valid, state.ValidationState);
}
[Fact]
public async Task TryUpdateModel_SettableCollectionModel_EmptyPrefix_MaintainsCollectionIfNonNull()
{
// Arrange
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
request.QueryString = QueryString.Create("Address[0].Street", "SomeStreet");
});
var modelState = new ModelStateDictionary();
var model = new Person2
{
Address = new List<Address>(),
};
var collection = model.Address;
// Act
var result = await TryUpdateModel(model, string.Empty, operationContext, modelState);
// Assert
Assert.True(result);
// Model
Assert.NotNull(model.Address);
Assert.Same(collection, model.Address);
Assert.Equal(1, model.Address.Count);
Assert.Equal("SomeStreet", model.Address[0].Street);
Assert.Null(model.Address[0].City);
// ModelState
Assert.True(modelState.IsValid);
var entry = Assert.Single(modelState);
Assert.Equal("Address[0].Street", entry.Key);
var state = entry.Value;
Assert.NotNull(state.Value);
Assert.Equal("SomeStreet", state.Value.AttemptedValue);
Assert.Equal("SomeStreet", state.Value.RawValue);
Assert.Empty(state.Errors);
Assert.Equal(ModelValidationState.Valid, state.ValidationState);
}
private class Person3
@ -144,28 +252,46 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
});
var modelState = new ModelStateDictionary();
var model = new Person3();
var model = new Person3
{
Address =
{
new Address
{
Street = "Old street",
City = "Redmond",
},
new Address
{
Street = "Older street",
City = "Toronto",
},
},
};
// Act
var result = await TryUpdateModel(model, string.Empty, operationContext, modelState);
// Assert
Assert.True(result);
// Model
// Model (collection is cleared and new members created from scratch).
Assert.NotNull(model.Address);
Assert.Equal(1, model.Address.Count);
Assert.Equal("SomeStreet", model.Address[0].Street);
Assert.Null(model.Address[0].City);
// ModelState
Assert.True(modelState.IsValid);
Assert.Equal(1, modelState.Keys.Count);
var key = Assert.Single(modelState.Keys, k => k == "Address[0].Street");
Assert.NotNull(modelState[key].Value);
Assert.Equal("SomeStreet", modelState[key].Value.AttemptedValue);
Assert.Equal("SomeStreet", modelState[key].Value.RawValue);
Assert.Empty(modelState[key].Errors);
Assert.Equal(ModelValidationState.Valid, modelState[key].ValidationState);
var entry = Assert.Single(modelState);
Assert.Equal("Address[0].Street", entry.Key);
var state = entry.Value;
Assert.NotNull(state.Value);
Assert.Equal("SomeStreet", state.Value.AttemptedValue);
Assert.Equal("SomeStreet", state.Value.RawValue);
Assert.Empty(state.Errors);
Assert.Equal(ModelValidationState.Valid, state.ValidationState);
}
private class Person6
@ -213,7 +339,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
}
[Fact]
public async Task TryUpdateModel_SettableArrayModel_EmptyPrefix_GetsBound()
public async Task TryUpdateModel_SettableArrayModel_EmptyPrefix_CreatesArray()
{
// Arrange
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
@ -223,6 +349,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var modelState = new ModelStateDictionary();
var model = new Person4();
// Act
var result = await TryUpdateModel(model, string.Empty, operationContext, modelState);
@ -231,19 +358,70 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// Model
Assert.NotNull(model.Address);
Assert.Equal(1, model.Address.Count());
Assert.Equal(1, model.Address.Length);
Assert.Equal("SomeStreet", model.Address[0].Street);
Assert.Null(model.Address[0].City);
// ModelState
Assert.True(modelState.IsValid);
Assert.Equal(1, modelState.Keys.Count);
var key = Assert.Single(modelState.Keys, k => k == "Address[0].Street");
Assert.NotNull(modelState[key].Value);
Assert.Equal("SomeStreet", modelState[key].Value.AttemptedValue);
Assert.Equal("SomeStreet", modelState[key].Value.RawValue);
Assert.Empty(modelState[key].Errors);
Assert.Equal(ModelValidationState.Valid, modelState[key].ValidationState);
var entry = Assert.Single(modelState);
Assert.Equal("Address[0].Street", entry.Key);
var state = entry.Value;
Assert.NotNull(state.Value);
Assert.Equal("SomeStreet", state.Value.AttemptedValue);
Assert.Equal("SomeStreet", state.Value.RawValue);
Assert.Empty(state.Errors);
Assert.Equal(ModelValidationState.Valid, state.ValidationState);
}
[Fact]
public async Task TryUpdateModel_SettableArrayModel_EmptyPrefix_OverwritesArray()
{
// Arrange
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
request.QueryString = QueryString.Create("Address[0].Street", "SomeStreet");
});
var modelState = new ModelStateDictionary();
var model = new Person4
{
Address = new Address[]
{
new Address
{
Street = "Old street",
City = "Toronto",
},
},
};
var collection = model.Address;
// Act
var result = await TryUpdateModel(model, string.Empty, operationContext, modelState);
// Assert
Assert.True(result);
// Model
Assert.NotNull(model.Address);
Assert.NotSame(collection, model.Address);
Assert.Equal(1, model.Address.Length);
Assert.Equal("SomeStreet", model.Address[0].Street);
Assert.Null(model.Address[0].City);
// ModelState
Assert.True(modelState.IsValid);
var entry = Assert.Single(modelState);
Assert.Equal("Address[0].Street", entry.Key);
var state = entry.Value;
Assert.NotNull(state.Value);
Assert.Equal("SomeStreet", state.Value.AttemptedValue);
Assert.Equal("SomeStreet", state.Value.RawValue);
Assert.Empty(state.Errors);
Assert.Equal(ModelValidationState.Valid, state.ValidationState);
}
private class Person5
@ -262,6 +440,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var modelState = new ModelStateDictionary();
var model = new Person5();
// Act
var result = await TryUpdateModel(model, string.Empty, operationContext, modelState);
@ -272,16 +451,16 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.NotNull(model.Address);
// Arrays should not be updated.
Assert.Equal(0, model.Address.Count());
Assert.Equal(0, model.Address.Length);
// ModelState
Assert.True(modelState.IsValid);
Assert.Empty(modelState.Keys);
Assert.Empty(modelState);
}
[Fact]
public async Task TryUpdateModel_ExistingModel_WithPrefix_GetsOverWritten()
public async Task TryUpdateModel_ExistingModel_WithPrefix_ValuesGetOverwritten()
{
// Arrange
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
@ -290,7 +469,11 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
});
var modelState = new ModelStateDictionary();
var model = new Address { Street = "DefaultStreet" };
var model = new Address
{
Street = "DefaultStreet",
City = "Toronto",
};
var oldModel = model;
// Act
@ -302,17 +485,19 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// Model
Assert.Same(oldModel, model);
Assert.Equal("SomeStreet", model.Street);
Assert.Equal("Toronto", model.City);
// ModelState
Assert.True(modelState.IsValid);
Assert.Equal(1, modelState.Keys.Count);
var key = Assert.Single(modelState.Keys, k => k == "prefix.Street");
Assert.NotNull(modelState[key].Value);
Assert.Equal("SomeStreet", modelState[key].Value.AttemptedValue);
Assert.Equal("SomeStreet", modelState[key].Value.RawValue);
Assert.Empty(modelState[key].Errors);
Assert.Equal(ModelValidationState.Valid, modelState[key].ValidationState);
var entry = Assert.Single(modelState);
Assert.Equal("prefix.Street", entry.Key);
var state = entry.Value;
Assert.NotNull(state.Value);
Assert.Equal("SomeStreet", state.Value.AttemptedValue);
Assert.Equal("SomeStreet", state.Value.RawValue);
Assert.Empty(state.Errors);
Assert.Equal(ModelValidationState.Valid, state.ValidationState);
}
[Fact]
@ -326,6 +511,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var modelState = new ModelStateDictionary();
var model = new Address();
// Act
var result = await TryUpdateModel(model, "prefix", operationContext, modelState);
@ -334,21 +520,69 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// Model
Assert.Equal("SomeStreet", model.Street);
Assert.Null(model.City);
// ModelState
Assert.True(modelState.IsValid);
Assert.Equal(1, modelState.Keys.Count);
var key = Assert.Single(modelState.Keys, k => k == "prefix.Street");
Assert.NotNull(modelState[key].Value);
Assert.Equal("SomeStreet", modelState[key].Value.AttemptedValue);
Assert.Equal("SomeStreet", modelState[key].Value.RawValue);
Assert.Empty(modelState[key].Errors);
Assert.Equal(ModelValidationState.Valid, modelState[key].ValidationState);
var entry = Assert.Single(modelState);
Assert.Equal("prefix.Street", entry.Key);
var state = entry.Value;
Assert.NotNull(state.Value);
Assert.Equal("SomeStreet", state.Value.AttemptedValue);
Assert.Equal("SomeStreet", state.Value.RawValue);
Assert.Empty(state.Errors);
Assert.Equal(ModelValidationState.Valid, state.ValidationState);
}
[Fact]
public async Task TryUpdateModel_SettableCollectionModel_WithPrefix_GetsBound()
public async Task TryUpdateModel_NestedPoco_WithPrefix_DoesNotTrounceUnboundValues()
{
// Arrange
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
request.QueryString = QueryString.Create("prefix.Address.Street", "SomeStreet");
});
var modelState = new ModelStateDictionary();
var model = new Person1
{
Name = "Joe",
Address = new Address
{
Street = "DefaultStreet",
City = "Toronto",
},
};
var oldModel = model;
// Act
var result = await TryUpdateModel(model, "prefix", operationContext, modelState);
// Assert
Assert.True(result);
// Model
Assert.Same(oldModel, model);
Assert.Equal("Joe", model.Name);
Assert.Equal("SomeStreet", model.Address.Street);
Assert.Equal("Toronto", model.Address.City);
// ModelState
Assert.True(modelState.IsValid);
var entry = Assert.Single(modelState);
Assert.Equal("prefix.Address.Street", entry.Key);
var state = entry.Value;
Assert.NotNull(state.Value);
Assert.Equal("SomeStreet", state.Value.AttemptedValue);
Assert.Equal("SomeStreet", state.Value.RawValue);
Assert.Empty(state.Errors);
Assert.Equal(ModelValidationState.Valid, state.ValidationState);
}
[Fact]
public async Task TryUpdateModel_SettableCollectionModel_WithPrefix_CreatesCollection()
{
// Arrange
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
@ -358,6 +592,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var modelState = new ModelStateDictionary();
var model = new Person2();
// Act
var result = await TryUpdateModel(model, "prefix", operationContext, modelState);
@ -368,17 +603,61 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.NotNull(model.Address);
Assert.Equal(1, model.Address.Count);
Assert.Equal("SomeStreet", model.Address[0].Street);
Assert.Null(model.Address[0].City);
// ModelState
Assert.True(modelState.IsValid);
Assert.Equal(1, modelState.Keys.Count);
var key = Assert.Single(modelState.Keys, k => k == "prefix.Address[0].Street");
Assert.NotNull(modelState[key].Value);
Assert.Equal("SomeStreet", modelState[key].Value.AttemptedValue);
Assert.Equal("SomeStreet", modelState[key].Value.RawValue);
Assert.Empty(modelState[key].Errors);
Assert.Equal(ModelValidationState.Valid, modelState[key].ValidationState);
var entry = Assert.Single(modelState);
Assert.Equal("prefix.Address[0].Street", entry.Key);
var state = entry.Value;
Assert.NotNull(state.Value);
Assert.Equal("SomeStreet", state.Value.AttemptedValue);
Assert.Equal("SomeStreet", state.Value.RawValue);
Assert.Empty(state.Errors);
Assert.Equal(ModelValidationState.Valid, state.ValidationState);
}
[Fact]
public async Task TryUpdateModel_SettableCollectionModel_WithPrefix_MaintainsCollectionIfNonNull()
{
// Arrange
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
request.QueryString = QueryString.Create("prefix.Address[0].Street", "SomeStreet");
});
var modelState = new ModelStateDictionary();
var model = new Person2
{
Address = new List<Address>(),
};
var collection = model.Address;
// Act
var result = await TryUpdateModel(model, "prefix", operationContext, modelState);
// Assert
Assert.True(result);
// Model
Assert.NotNull(model.Address);
Assert.Same(collection, model.Address);
Assert.Equal(1, model.Address.Count);
Assert.Equal("SomeStreet", model.Address[0].Street);
Assert.Null(model.Address[0].City);
// ModelState
Assert.True(modelState.IsValid);
var entry = Assert.Single(modelState);
Assert.Equal("prefix.Address[0].Street", entry.Key);
var state = entry.Value;
Assert.NotNull(state.Value);
Assert.Equal("SomeStreet", state.Value.AttemptedValue);
Assert.Equal("SomeStreet", state.Value.RawValue);
Assert.Empty(state.Errors);
Assert.Equal(ModelValidationState.Valid, state.ValidationState);
}
[Fact]
@ -391,28 +670,46 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
});
var modelState = new ModelStateDictionary();
var model = new Person3();
var model = new Person3
{
Address =
{
new Address
{
Street = "Old street",
City = "Redmond",
},
new Address
{
Street = "Older street",
City = "Toronto",
},
},
};
// Act
var result = await TryUpdateModel(model, "prefix", operationContext, modelState);
// Assert
Assert.True(result);
// Model
// Model (collection is cleared and new members created from scratch).
Assert.NotNull(model.Address);
Assert.Equal(1, model.Address.Count);
Assert.Equal("SomeStreet", model.Address[0].Street);
Assert.Null(model.Address[0].City);
// ModelState
Assert.True(modelState.IsValid);
Assert.Equal(1, modelState.Keys.Count);
var key = Assert.Single(modelState.Keys, k => k == "prefix.Address[0].Street");
Assert.NotNull(modelState[key].Value);
Assert.Equal("SomeStreet", modelState[key].Value.AttemptedValue);
Assert.Equal("SomeStreet", modelState[key].Value.RawValue);
Assert.Empty(modelState[key].Errors);
Assert.Equal(ModelValidationState.Valid, modelState[key].ValidationState);
var entry = Assert.Single(modelState);
Assert.Equal("prefix.Address[0].Street", entry.Key);
var state = entry.Value;
Assert.NotNull(state.Value);
Assert.Equal("SomeStreet", state.Value.AttemptedValue);
Assert.Equal("SomeStreet", state.Value.RawValue);
Assert.Empty(state.Errors);
Assert.Equal(ModelValidationState.Valid, state.ValidationState);
}
[Fact(Skip = "Validation incorrect for collections when using TryUpdateModel, #2941")]
@ -450,7 +747,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
}
[Fact]
public async Task TryUpdateModel_SettableArrayModel_WithPrefix_GetsBound()
public async Task TryUpdateModel_SettableArrayModel_WithPrefix_CreatesArray()
{
// Arrange
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
@ -460,6 +757,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var modelState = new ModelStateDictionary();
var model = new Person4();
// Act
var result = await TryUpdateModel(model, "prefix", operationContext, modelState);
@ -468,23 +766,74 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// Model
Assert.NotNull(model.Address);
Assert.Equal(1, model.Address.Count());
Assert.Equal(1, model.Address.Length);
Assert.Equal("SomeStreet", model.Address[0].Street);
Assert.Null(model.Address[0].City);
// ModelState
Assert.True(modelState.IsValid);
Assert.Equal(1, modelState.Keys.Count);
var key = Assert.Single(modelState.Keys, k => k == "prefix.Address[0].Street");
Assert.NotNull(modelState[key].Value);
Assert.Equal("SomeStreet", modelState[key].Value.AttemptedValue);
Assert.Equal("SomeStreet", modelState[key].Value.RawValue);
Assert.Empty(modelState[key].Errors);
Assert.Equal(ModelValidationState.Valid, modelState[key].ValidationState);
var entry = Assert.Single(modelState);
Assert.Equal("prefix.Address[0].Street", entry.Key);
var state = entry.Value;
Assert.NotNull(state.Value);
Assert.Equal("SomeStreet", state.Value.AttemptedValue);
Assert.Equal("SomeStreet", state.Value.RawValue);
Assert.Empty(state.Errors);
Assert.Equal(ModelValidationState.Valid, state.ValidationState);
}
[Fact]
public async Task TryUpdateModel_NonSettableArrayModel_WithPrefix_DoesNotGetBound()
public async Task TryUpdateModel_SettableArrayModel_WithPrefix_OverwritesArray()
{
// Arrange
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
request.QueryString = QueryString.Create("prefix.Address[0].Street", "SomeStreet");
});
var modelState = new ModelStateDictionary();
var model = new Person4
{
Address = new Address[]
{
new Address
{
Street = "Old street",
City = "Toronto",
},
},
};
var collection = model.Address;
// Act
var result = await TryUpdateModel(model, "prefix", operationContext, modelState);
// Assert
Assert.True(result);
// Model
Assert.NotNull(model.Address);
Assert.NotSame(collection, model.Address);
Assert.Equal(1, model.Address.Length);
Assert.Equal("SomeStreet", model.Address[0].Street);
Assert.Null(model.Address[0].City);
// ModelState
Assert.True(modelState.IsValid);
var entry = Assert.Single(modelState);
Assert.Equal("prefix.Address[0].Street", entry.Key);
var state = entry.Value;
Assert.NotNull(state.Value);
Assert.Equal("SomeStreet", state.Value.AttemptedValue);
Assert.Equal("SomeStreet", state.Value.RawValue);
Assert.Empty(state.Errors);
Assert.Equal(ModelValidationState.Valid, state.ValidationState);
}
[Fact]
public async Task TryUpdateModel_NonSettableArrayModel_WithPrefix_GetsBound()
{
// Arrange
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
@ -494,6 +843,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var modelState = new ModelStateDictionary();
var model = new Person5();
// Act
var result = await TryUpdateModel(model, "prefix", operationContext, modelState);
@ -504,11 +854,11 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.NotNull(model.Address);
// Arrays should not be updated.
Assert.Equal(0, model.Address.Count());
Assert.Equal(0, model.Address.Length);
// ModelState
Assert.True(modelState.IsValid);
Assert.Empty(modelState.Keys);
Assert.Empty(modelState);
}
private class CustomReadOnlyCollection<T> : ICollection<T>

View File

@ -39,7 +39,7 @@ namespace Microsoft.AspNet.Mvc
// Assert
var i = 0;
Assert.Equal(13, options.ModelBinders.Count);
Assert.Equal(12, options.ModelBinders.Count);
Assert.IsType(typeof(BinderTypeBasedModelBinder), options.ModelBinders[i++]);
Assert.IsType(typeof(ServicesModelBinder), options.ModelBinders[i++]);
Assert.IsType(typeof(BodyModelBinder), options.ModelBinders[i++]);
@ -52,7 +52,6 @@ namespace Microsoft.AspNet.Mvc
Assert.IsType(typeof(FormCollectionModelBinder), options.ModelBinders[i++]);
Assert.IsType(typeof(GenericModelBinder), options.ModelBinders[i++]);
Assert.IsType(typeof(MutableObjectModelBinder), options.ModelBinders[i++]);
Assert.IsType(typeof(ComplexModelDtoModelBinder), options.ModelBinders[i++]);
}
[Fact]