diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs index e7d5cf6a61..aee377fad0 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs @@ -280,7 +280,6 @@ namespace Microsoft.AspNet.Mvc.Core.Test mockValidatorProvider.Verify(o => o.Validate(It.IsAny()), Times.Never()); } - [Fact] public async Task BindActionArgumentsAsync_SetsControllerProperties_ForReferenceTypes() { diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/ActionParametersIntegrationTest.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/ActionParametersIntegrationTest.cs new file mode 100644 index 0000000000..f9dfc27bcd --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/ActionParametersIntegrationTest.cs @@ -0,0 +1,444 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Mvc.ModelBinding; +using Xunit; + +namespace Microsoft.AspNet.Mvc.IntegrationTests +{ + public class ActionParameterIntegrationTest + { + private class Address + { + public string Street { get; set; } + } + + private class Person3 + { + public Person3() + { + Address = new List
(); + } + + public List
Address { get; } + } + + [Fact(Skip = "Extra entries in model state dictionary. #2466")] + public async Task ActionParameter_NonSettableCollectionModel_EmptyPrefix_GetsBound() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "Address", + ParameterType = typeof(Person3) + }; + + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = QueryString.Create("[0].Street", "SomeStreet"); + }); + + var modelState = new ModelStateDictionary(); + var model = new Person3(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.NotNull(modelBindingResult); + Assert.True(modelBindingResult.IsModelSet); + + // Model + Assert.NotNull(modelBindingResult.Model); + var boundModel = Assert.IsType(modelBindingResult.Model); + Assert.Equal(1, boundModel.Address.Count); + Assert.Equal("SomeStreet", boundModel.Address[0].Street); + + // ModelState + Assert.True(modelState.IsValid); + + Assert.Equal(1, modelState.Keys.Count); + var key = Assert.Single(modelState.Keys, k => k == "[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); + } + + private class Person6 + { + public CustomReadOnlyCollection
Address { get; set; } + } + + [Fact(Skip = "Extra entries in model state dictionary. #2466")] + public async Task ActionParameter_ReadOnlyCollectionModel_EmptyPrefix_DoesNotGetBound() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "Address", + ParameterType = typeof(Person6) + }; + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = QueryString.Create("[0].Street", "SomeStreet"); + }); + + var modelState = new ModelStateDictionary(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.NotNull(modelBindingResult); + Assert.True(modelBindingResult.IsModelSet); + + // Model + var boundModel = Assert.IsType(modelBindingResult.Model); + Assert.NotNull(boundModel); + Assert.NotNull(boundModel.Address); + + // Arrays should not be updated. + Assert.Equal(0, boundModel.Address.Count()); + + // ModelState + Assert.True(modelState.IsValid); + Assert.Empty(modelState.Keys); + } + + private class Person4 + { + public Address[] Address { get; set; } + } + + [Fact(Skip = "Extra entries in model state dictionary. #2466")] + public async Task ActionParameter_SettableArrayModel_EmptyPrefix_GetsBound() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "Address", + ParameterType = typeof(Person4) + }; + + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = QueryString.Create("[0].Street", "SomeStreet"); + }); + + var modelState = new ModelStateDictionary(); + var model = new Person4(); + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.NotNull(modelBindingResult); + Assert.True(modelBindingResult.IsModelSet); + + // Model + Assert.NotNull(modelBindingResult.Model); + var boundModel = Assert.IsType(modelBindingResult.Model); + Assert.Equal(1, boundModel.Address.Count()); + Assert.Equal("SomeStreet", boundModel.Address[0].Street); + + // ModelState + Assert.True(modelState.IsValid); + + Assert.Equal(1, modelState.Keys.Count); + var key = Assert.Single(modelState.Keys, k => k == "[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); + } + + private class Person5 + { + public Address[] Address { get; } = new Address[] { }; + } + + [Fact(Skip = "Extra entries in model state dictionary. #2466")] + public async Task ActionParameter_NonSettableArrayModel_EmptyPrefix_DoesNotGetBound() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "Address", + ParameterType = typeof(Person5) + }; + + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = QueryString.Create("[0].Street", "SomeStreet"); + }); + + var modelState = new ModelStateDictionary(); + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.NotNull(modelBindingResult); + Assert.True(modelBindingResult.IsModelSet); + + // Model + Assert.NotNull(modelBindingResult.Model); + var boundModel = Assert.IsType(modelBindingResult.Model); + Assert.NotNull(boundModel.Address); + + // Arrays should not be updated. + Assert.Equal(0, boundModel.Address.Count()); + + // ModelState + Assert.True(modelState.IsValid); + Assert.Empty(modelState.Keys); + } + + [Fact(Skip = "Extra entries in model state dictionary. #2466")] + public async Task ActionParameter_NonSettableCollectionModel_WithPrefix_GetsBound() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "Address", + BindingInfo = new BindingInfo() + { + BinderModelName = "prefix" + }, + ParameterType = typeof(Person3) + }; + + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = QueryString.Create("prefix.Address[0].Street", "SomeStreet"); + }); + + var modelState = new ModelStateDictionary(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.NotNull(modelBindingResult); + Assert.True(modelBindingResult.IsModelSet); + + // Model + Assert.NotNull(modelBindingResult.Model); + var boundModel = Assert.IsType(modelBindingResult.Model); + Assert.Equal(1, boundModel.Address.Count); + Assert.Equal("SomeStreet", boundModel.Address[0].Street); + + // 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); + } + + [Fact(Skip = "Extra entries in model state dictionary. #2466")] + public async Task ActionParameter_ReadOnlyCollectionModel_WithPrefix_DoesNotGetBound() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "Address", + BindingInfo = new BindingInfo + { + BinderModelName = "prefix" + }, + ParameterType = typeof(Person6) + }; + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = QueryString.Create("prefix.Address[0].Street", "SomeStreet"); + }); + + var modelState = new ModelStateDictionary(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.NotNull(modelBindingResult); + Assert.True(modelBindingResult.IsModelSet); + + // Model + var boundModel = Assert.IsType(modelBindingResult.Model); + Assert.NotNull(boundModel); + Assert.NotNull(boundModel.Address); + + // Arrays should not be updated. + Assert.Equal(0, boundModel.Address.Count()); + + // ModelState + Assert.True(modelState.IsValid); + Assert.Empty(modelState.Keys); + } + + [Fact(Skip = "Extra entries in model state dictionary. #2466")] + public async Task ActionParameter_SettableArrayModel_WithPrefix_GetsBound() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "Address", + BindingInfo = new BindingInfo() + { + BinderModelName = "prefix" + }, + ParameterType = typeof(Person4) + }; + + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = QueryString.Create("prefix.Address[0].Street", "SomeStreet"); + }); + + var modelState = new ModelStateDictionary(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.NotNull(modelBindingResult); + Assert.True(modelBindingResult.IsModelSet); + + // Model + Assert.NotNull(modelBindingResult.Model); + var boundModel = Assert.IsType(modelBindingResult.Model); + Assert.Equal(1, boundModel.Address.Count()); + Assert.Equal("SomeStreet", boundModel.Address[0].Street); + + // 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); + } + + [Fact(Skip = "Extra entries in model state dictionary. #2466")] + public async Task ActionParameter_NonSettableArrayModel_WithPrefix_DoesNotGetBound() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "Address", + BindingInfo = new BindingInfo() + { + BinderModelName = "prefix" + }, + ParameterType = typeof(Person5) + }; + + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = QueryString.Create("prefix.Address[0].Street", "SomeStreet"); + }); + + var modelState = new ModelStateDictionary(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + Assert.NotNull(modelBindingResult); + Assert.True(modelBindingResult.IsModelSet); + + // Model + Assert.NotNull(modelBindingResult.Model); + var boundModel = Assert.IsType(modelBindingResult.Model); + + // Arrays should not be updated. + Assert.Equal(0, boundModel.Address.Count()); + + // ModelState + Assert.True(modelState.IsValid); + Assert.Empty(modelState.Keys); + } + + private class CustomReadOnlyCollection : ICollection, IReadOnlyCollection + { + private ICollection _original; + + public CustomReadOnlyCollection() : this(new List()) + { + } + + public CustomReadOnlyCollection(ICollection original) + { + _original = original; + } + + public int Count + { + get { return _original.Count; } + } + + public bool IsReadOnly + { + get { return true; } + } + + public void Add(T item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(T item) + { + return _original.Contains(item); + } + + public void CopyTo(T[] array, int arrayIndex) + { + _original.CopyTo(array, arrayIndex); + } + + public bool Remove(T item) + { + throw new NotSupportedException(); + } + + public IEnumerator GetEnumerator() + { + foreach (T t in _original) + { + yield return t; + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/ModelBindingTestHelper.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/ModelBindingTestHelper.cs index 0efe73a0ba..10cb130843 100644 --- a/test/Microsoft.AspNet.Mvc.IntegrationTests/ModelBindingTestHelper.cs +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/ModelBindingTestHelper.cs @@ -50,14 +50,20 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests public static DefaultControllerActionArgumentBinder GetArgumentBinder() { - var options = new TestMvcOptions(); - options.Options.MaxModelValidationErrors = 5; var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); return new DefaultControllerActionArgumentBinder( metadataProvider, - new DefaultObjectValidator( + GetObjectValidator()); + } + + public static IObjectModelValidator GetObjectValidator() + { + var options = new TestMvcOptions(); + options.Options.MaxModelValidationErrors = 5; + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + return new DefaultObjectValidator( options.Options.ValidationExcludeFilters, - metadataProvider)); + metadataProvider); } private static void InitializeServices(HttpContext httpContext, Action updateOptions = null) diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/TryUpdateModelIntegrationTest.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/TryUpdateModelIntegrationTest.cs new file mode 100644 index 0000000000..8c50faafe2 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/TryUpdateModelIntegrationTest.cs @@ -0,0 +1,583 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Mvc.ModelBinding; +using Xunit; + +namespace Microsoft.AspNet.Mvc.IntegrationTests +{ + public class TryUpdateModelIntegrationTest + { + private class Address + { + public string Street { get; set; } + } + + [Fact(Skip = "Extra entries in model state dictionary. #2466")] + public async Task TryUpdateModel_ExistingModel_EmptyPrefix_GetsOverWritten() + { + // Arrange + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = QueryString.Create("Street", "SomeStreet"); + }); + + var modelState = new ModelStateDictionary(); + var model = new Address { Street = "DefaultStreet" }; + var oldModel = model; + + // Act + var result = await TryUpdateModel(model, string.Empty, operationContext, modelState); + + // Assert + Assert.True(result); + + // Model + Assert.Same(oldModel, model); + Assert.Equal("SomeStreet", model.Street); + + // 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); + } + + [Fact(Skip = "Extra entries in model state dictionary. #2466")] + public async Task TryUpdateModel_ExistingModel_EmptyPrefix_GetsBound() + { + // Arrange + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = QueryString.Create("Street", "SomeStreet"); + }); + + var modelState = new ModelStateDictionary(); + var model = new Address(); + // Act + var result = await TryUpdateModel(model, string.Empty, operationContext, modelState); + + // Assert + Assert.True(result); + + // Model + Assert.Equal("SomeStreet", model.Street); + + // 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); + } + + private class Person2 + { + public List
Address { get; set; } + } + + [Fact(Skip = "Extra entries in model state dictionary. #2466")] + public async Task TryUpdateModel_SettableCollectionModel_EmptyPrefix_GetsBound() + { + // Arrange + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = QueryString.Create("Address[0].Street", "SomeStreet"); + }); + + var modelState = new ModelStateDictionary(); + var model = new Person2(); + // Act + var result = await TryUpdateModel(model, string.Empty, operationContext, modelState); + + // Assert + Assert.True(result); + + // Model + Assert.NotNull(model.Address); + Assert.Equal(1, model.Address.Count); + Assert.Equal("SomeStreet", model.Address[0].Street); + + // 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); + } + + private class Person3 + { + public Person3() + { + Address = new List
(); + } + + public List
Address { get; } + } + + [Fact(Skip = "Extra entries in model state dictionary. #2466")] + public async Task TryUpdateModel_NonSettableCollectionModel_EmptyPrefix_GetsBound() + { + // Arrange + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = QueryString.Create("Address[0].Street", "SomeStreet"); + }); + + var modelState = new ModelStateDictionary(); + var model = new Person3(); + // Act + var result = await TryUpdateModel(model, string.Empty, operationContext, modelState); + + // Assert + Assert.True(result); + + // Model + Assert.NotNull(model.Address); + Assert.Equal(1, model.Address.Count); + Assert.Equal("SomeStreet", model.Address[0].Street); + + // 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); + } + + private class Person6 + { + public CustomReadOnlyCollection
Address { get; set; } + } + + [Fact(Skip = "Extra entries in model state dictionary. #2466")] + public async Task TryUpdateModel_ReadOnlyCollectionModel_EmptyPrefix_DoesNotGetBound() + { + // Arrange + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = QueryString.Create("Address[0].Street", "SomeStreet"); + }); + + var modelState = new ModelStateDictionary(); + var model = new Person6(); + // Act + var result = await TryUpdateModel(model, string.Empty, operationContext, modelState); + + // Assert + Assert.True(result); + + // Model + Assert.NotNull(model.Address); + + // Arrays should not be updated. + Assert.Equal(0, model.Address.Count()); + + // ModelState + Assert.True(modelState.IsValid); + Assert.Empty(modelState.Keys); + } + + private class Person4 + { + public Address[] Address { get; set; } + } + + [Fact(Skip = "Extra entries in model state dictionary. #2466")] + public async Task TryUpdateModel_SettableArrayModel_EmptyPrefix_GetsBound() + { + // Arrange + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = QueryString.Create("Address[0].Street", "SomeStreet"); + }); + + var modelState = new ModelStateDictionary(); + var model = new Person4(); + // Act + var result = await TryUpdateModel(model, string.Empty, operationContext, modelState); + + // Assert + Assert.True(result); + + // Model + Assert.NotNull(model.Address); + Assert.Equal(1, model.Address.Count()); + Assert.Equal("SomeStreet", model.Address[0].Street); + + // 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); + } + + private class Person5 + { + public Address[] Address { get; } = new Address[] { }; + } + + [Fact(Skip = "Extra entries in model state dictionary. #2466")] + public async Task TryUpdateModel_NonSettableArrayModel_EmptyPrefix_GetsBound() + { + // Arrange + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = QueryString.Create("Address[0].Street", "SomeStreet"); + }); + + var modelState = new ModelStateDictionary(); + var model = new Person5(); + // Act + var result = await TryUpdateModel(model, string.Empty, operationContext, modelState); + + // Assert + Assert.True(result); + + // Model + Assert.NotNull(model.Address); + + // Arrays should not be updated. + Assert.Equal(0, model.Address.Count()); + + // ModelState + Assert.True(modelState.IsValid); + Assert.Empty(modelState.Keys); + } + + + [Fact(Skip = "Extra entries in model state dictionary. #2466")] + public async Task TryUpdateModel_ExistingModel_WithPrefix_GetsOverWritten() + { + // Arrange + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = QueryString.Create("prefix.Street", "SomeStreet"); + }); + + var modelState = new ModelStateDictionary(); + var model = new Address { Street = "DefaultStreet" }; + var oldModel = model; + + // Act + var result = await TryUpdateModel(model, "prefix", operationContext, modelState); + + // Assert + Assert.True(result); + + // Model + Assert.Same(oldModel, model); + Assert.Equal("SomeStreet", model.Street); + + // 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); + } + + [Fact(Skip = "Extra entries in model state dictionary. #2466")] + public async Task TryUpdateModel_ExistingModel_WithPrefix_GetsBound() + { + // Arrange + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = QueryString.Create("prefix.Street", "SomeStreet"); + }); + + var modelState = new ModelStateDictionary(); + var model = new Address(); + // Act + var result = await TryUpdateModel(model, "prefix", operationContext, modelState); + + // Assert + Assert.True(result); + + // Model + Assert.Equal("SomeStreet", model.Street); + + // 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); + } + + [Fact(Skip = "Extra entries in model state dictionary. #2466")] + public async Task TryUpdateModel_SettableCollectionModel_WithPrefix_GetsBound() + { + // Arrange + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = QueryString.Create("prefix.Address[0].Street", "SomeStreet"); + }); + + var modelState = new ModelStateDictionary(); + var model = new Person2(); + // Act + var result = await TryUpdateModel(model, string.Empty, operationContext, modelState); + + // Assert + Assert.True(result); + + // Model + Assert.NotNull(model.Address); + Assert.Equal(1, model.Address.Count); + Assert.Equal("SomeStreet", model.Address[0].Street); + + // 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); + } + + [Fact(Skip = "Extra entries in model state dictionary. #2466")] + public async Task TryUpdateModel_NonSettableCollectionModel_WithPrefix_GetsBound() + { + // Arrange + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = QueryString.Create("prefix.Address[0].Street", "SomeStreet"); + }); + + var modelState = new ModelStateDictionary(); + var model = new Person3(); + // Act + var result = await TryUpdateModel(model, "prefix", operationContext, modelState); + + // Assert + Assert.True(result); + + // Model + Assert.NotNull(model.Address); + Assert.Equal(1, model.Address.Count); + Assert.Equal("SomeStreet", model.Address[0].Street); + + // 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); + } + + [Fact(Skip = "Extra entries in model state dictionary. #2466")] + public async Task TryUpdateModel_ReadOnlyCollectionModel_WithPrefix_DoesNotGetBound() + { + // Arrange + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = QueryString.Create("prefix.Address[0].Street", "SomeStreet"); + }); + + var modelState = new ModelStateDictionary(); + var model = new Person6(); + // Act + var result = await TryUpdateModel(model, "prefix", operationContext, modelState); + + // Assert + Assert.True(result); + + // Model + Assert.NotNull(model.Address); + + // Arrays should not be updated. + Assert.Equal(0, model.Address.Count()); + + // ModelState + Assert.True(modelState.IsValid); + Assert.Empty(modelState.Keys); + } + + [Fact(Skip = "Extra entries in model state dictionary. #2466")] + public async Task TryUpdateModel_SettableArrayModel_WithPrefix_GetsBound() + { + // Arrange + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = QueryString.Create("prefix.Address[0].Street", "SomeStreet"); + }); + + var modelState = new ModelStateDictionary(); + var model = new Person4(); + // Act + var result = await TryUpdateModel(model, "prefix", operationContext, modelState); + + // Assert + Assert.True(result); + + // Model + Assert.NotNull(model.Address); + Assert.Equal(1, model.Address.Count()); + Assert.Equal("SomeStreet", model.Address[0].Street); + + // 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); + } + + [Fact(Skip = "Extra entries in model state dictionary. #2466")] + public async Task TryUpdateModel_NonSettableArrayModel_WithPrefix_DoesNotGetBound() + { + // Arrange + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = QueryString.Create("prefix.Address[0].Street", "SomeStreet"); + }); + + var modelState = new ModelStateDictionary(); + var model = new Person5(); + // Act + var result = await TryUpdateModel(model, "prefix", operationContext, modelState); + + // Assert + Assert.True(result); + + // Model + Assert.NotNull(model.Address); + + // Arrays should not be updated. + Assert.Equal(0, model.Address.Count()); + + // ModelState + Assert.True(modelState.IsValid); + Assert.Empty(modelState.Keys); + } + + private class CustomReadOnlyCollection : ICollection, IReadOnlyCollection + { + private ICollection _original; + + public CustomReadOnlyCollection() : this(new List()) + { + } + + public CustomReadOnlyCollection(ICollection original) + { + _original = original; + } + + public int Count + { + get { return _original.Count; } + } + + public bool IsReadOnly + { + get { return true; } + } + + public void Add(T item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(T item) + { + return _original.Contains(item); + } + + public void CopyTo(T[] array, int arrayIndex) + { + _original.CopyTo(array, arrayIndex); + } + + public bool Remove(T item) + { + throw new NotSupportedException(); + } + + public IEnumerator GetEnumerator() + { + foreach (T t in _original) + { + yield return t; + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + private Task TryUpdateModel( + object model, + string prefix, + OperationBindingContext operationContext, + ModelStateDictionary modelState) + { + return ModelBindingHelper.TryUpdateModelAsync( + model, + model.GetType(), + prefix, + operationContext.HttpContext, + modelState, + operationContext.MetadataProvider, + operationContext.ModelBinder, + operationContext.ValueProvider, + ModelBindingTestHelper.GetObjectValidator(), + operationContext.ValidatorProvider); + } + } +} \ No newline at end of file