// 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.Linq; using System.Collections.Generic; using System.IO; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Internal; using Microsoft.Extensions.Primitives; using Xunit; namespace Microsoft.AspNetCore.Mvc.IntegrationTests { public class TryUpdateModelIntegrationTest { private class Address { public string Street { get; set; } public string City { get; set; } } [Fact] public async Task TryUpdateModel_ExistingModel_EmptyPrefix_OverwritesBoundValues() { // Arrange var testContext = ModelBindingTestHelper.GetTestContext(request => { request.QueryString = QueryString.Create("Street", "SomeStreet"); }); var modelState = testContext.ModelState; var model = new Address { Street = "DefaultStreet", City = "Toronto", }; var oldModel = model; // Act var result = await TryUpdateModelAsync(model, string.Empty, testContext); // Assert Assert.True(result); // Model Assert.Same(oldModel, model); Assert.Equal("SomeStreet", model.Street); Assert.Equal("Toronto", model.City); // ModelState Assert.True(modelState.IsValid); var entry = Assert.Single(modelState); Assert.Equal("Street", entry.Key); var state = entry.Value; Assert.Equal("SomeStreet", state.AttemptedValue); Assert.Equal("SomeStreet", state.RawValue); Assert.Empty(state.Errors); Assert.Equal(ModelValidationState.Valid, state.ValidationState); } [Fact] public async Task TryUpdateModel_ExistingModel_EmptyPrefix_GetsBound() { // Arrange var testContext = ModelBindingTestHelper.GetTestContext(request => { request.QueryString = QueryString.Create("Street", "SomeStreet"); }); var modelState = testContext.ModelState; var model = new Address(); // Act var result = await TryUpdateModelAsync(model, string.Empty, testContext); // Assert Assert.True(result); // Model Assert.Equal("SomeStreet", model.Street); Assert.Null(model.City); // ModelState Assert.True(modelState.IsValid); var entry = Assert.Single(modelState); Assert.Equal("Street", entry.Key); var state = entry.Value; Assert.Equal("SomeStreet", state.AttemptedValue); Assert.Equal("SomeStreet", state.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_TopLevelCollection_EmptyPrefix_BindsAfterClearing() { // Arrange var testContext = ModelBindingTestHelper.GetTestContext(request => { request.QueryString = QueryString.Create(new Dictionary { { "[0].Name", "One Name" }, { "[1].Address.Street", "Two Street" }, }); }); var modelState = testContext.ModelState; var model = new List { new Person1 { Name = "One", Address = new Address { Street = "DefaultStreet", City = "Toronto", }, }, new Person1 { Name = "Two" }, new Person1 { Name = "Three" }, }; // Act var result = await TryUpdateModelAsync(model, string.Empty, testContext); // Assert Assert.True(result); // Model Assert.Collection( model, element => { Assert.Equal("One Name", element.Name); Assert.Null(element.Address); }, element => { Assert.Null(element.Name); Assert.NotNull(element.Address); Assert.Equal("Two Street", element.Address.Street); Assert.Null(element.Address.City); }); // ModelState Assert.True(modelState.IsValid); Assert.Equal(2, modelState.Count); Assert.NotNull(modelState["[0].Name"]); Assert.NotNull(modelState["[1].Address.Street"]); } [Fact] public async Task TryUpdateModel_NestedPoco_EmptyPrefix_DoesNotTrounceUnboundValues() { // Arrange var testContext = ModelBindingTestHelper.GetTestContext(request => { request.QueryString = QueryString.Create("Address.Street", "SomeStreet"); }); var modelState = testContext.ModelState; var model = new Person1 { Name = "Joe", Address = new Address { Street = "DefaultStreet", City = "Toronto", }, }; var oldModel = model; // Act var result = await TryUpdateModelAsync(model, string.Empty, testContext); // 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.Equal("SomeStreet", state.AttemptedValue); Assert.Equal("SomeStreet", state.RawValue); Assert.Empty(state.Errors); Assert.Equal(ModelValidationState.Valid, state.ValidationState); } private class Person2 { public List
Address { get; set; } } [Fact] public async Task TryUpdateModel_SettableCollectionModel_EmptyPrefix_CreatesCollection() { // Arrange var testContext = ModelBindingTestHelper.GetTestContext(request => { request.QueryString = QueryString.Create("Address[0].Street", "SomeStreet"); }); var modelState = testContext.ModelState; var model = new Person2(); // Act var result = await TryUpdateModelAsync(model, string.Empty, testContext); // Assert Assert.True(result); // Model Assert.NotNull(model.Address); Assert.Single(model.Address); 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.Equal("SomeStreet", state.AttemptedValue); Assert.Equal("SomeStreet", state.RawValue); Assert.Empty(state.Errors); Assert.Equal(ModelValidationState.Valid, state.ValidationState); } [Fact] public async Task TryUpdateModel_SettableCollectionModel_EmptyPrefix_MaintainsCollectionIfNonNull() { // Arrange var testContext = ModelBindingTestHelper.GetTestContext(request => { request.QueryString = QueryString.Create("Address[0].Street", "SomeStreet"); }); var modelState = testContext.ModelState; var model = new Person2 { Address = new List
(), }; var collection = model.Address; // Act var result = await TryUpdateModelAsync(model, string.Empty, testContext); // Assert Assert.True(result); // Model Assert.NotNull(model.Address); Assert.Same(collection, model.Address); Assert.Single(model.Address); 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.Equal("SomeStreet", state.AttemptedValue); Assert.Equal("SomeStreet", state.RawValue); Assert.Empty(state.Errors); Assert.Equal(ModelValidationState.Valid, state.ValidationState); } private class Person3 { public Person3() { Address = new List
(); } public List
Address { get; } } [Fact] public async Task TryUpdateModel_NonSettableCollectionModel_EmptyPrefix_GetsBound() { // Arrange var testContext = ModelBindingTestHelper.GetTestContext(request => { request.QueryString = QueryString.Create("Address[0].Street", "SomeStreet"); }); var modelState = testContext.ModelState; var model = new Person3 { Address = { new Address { Street = "Old street", City = "Redmond", }, new Address { Street = "Older street", City = "Toronto", }, }, }; // Act var result = await TryUpdateModelAsync(model, string.Empty, testContext); // Assert Assert.True(result); // Model (collection is cleared and new members created from scratch). Assert.NotNull(model.Address); Assert.Single(model.Address); 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.Equal("SomeStreet", state.AttemptedValue); Assert.Equal("SomeStreet", state.RawValue); Assert.Empty(state.Errors); Assert.Equal(ModelValidationState.Valid, state.ValidationState); } private class Person6 { public CustomReadOnlyCollection
Address { get; set; } } [Fact] public async Task TryUpdateModel_ReadOnlyCollectionModel_EmptyPrefix_DoesNotGetBound() { // Arrange var testContext = ModelBindingTestHelper.GetTestContext(request => { request.QueryString = QueryString.Create("Address[0].Street", "SomeStreet"); }); var modelState = testContext.ModelState; var model = new Person6(); // Act var result = await TryUpdateModelAsync(model, string.Empty, testContext); // Assert Assert.False(result); // ModelState Assert.False(modelState.IsValid); var entry = Assert.Single(modelState); Assert.Equal("Address[0].Street", entry.Key); var state = entry.Value; Assert.NotNull(state); Assert.Equal(ModelValidationState.Unvalidated, state.ValidationState); Assert.Equal("SomeStreet", state.RawValue); Assert.Equal("SomeStreet", state.AttemptedValue); } [Fact] public async Task TryUpdateModel_ReadOnlyCollectionModel_WithPrefix_DoesNotGetBound() { // Arrange var testContext = ModelBindingTestHelper.GetTestContext(request => { request.QueryString = QueryString.Create("prefix.Address[0].Street", "SomeStreet"); }); var modelState = testContext.ModelState; var model = new Person6(); // Act var result = await TryUpdateModelAsync(model, "prefix", testContext); // Assert Assert.False(result); // ModelState Assert.False(modelState.IsValid); var entry = Assert.Single(modelState); Assert.Equal("prefix.Address[0].Street", entry.Key); var state = entry.Value; Assert.NotNull(state); Assert.Equal(ModelValidationState.Unvalidated, state.ValidationState); Assert.Equal("SomeStreet", state.RawValue); Assert.Equal("SomeStreet", state.AttemptedValue); } private class Person4 { public Address[] Address { get; set; } } [Fact] public async Task TryUpdateModel_SettableArrayModel_EmptyPrefix_CreatesArray() { // Arrange var testContext = ModelBindingTestHelper.GetTestContext(request => { request.QueryString = QueryString.Create("Address[0].Street", "SomeStreet"); }); var modelState = testContext.ModelState; var model = new Person4(); // Act var result = await TryUpdateModelAsync(model, string.Empty, testContext); // Assert Assert.True(result); // Model Assert.NotNull(model.Address); Assert.Single(model.Address); 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.Equal("SomeStreet", state.AttemptedValue); Assert.Equal("SomeStreet", state.RawValue); Assert.Empty(state.Errors); Assert.Equal(ModelValidationState.Valid, state.ValidationState); } [Fact] public async Task TryUpdateModel_SettableArrayModel_EmptyPrefix_OverwritesArray() { // Arrange var testContext = ModelBindingTestHelper.GetTestContext(request => { request.QueryString = QueryString.Create("Address[0].Street", "SomeStreet"); }); var modelState = testContext.ModelState; var model = new Person4 { Address = new Address[] { new Address { Street = "Old street", City = "Toronto", }, }, }; var collection = model.Address; // Act var result = await TryUpdateModelAsync(model, string.Empty, testContext); // Assert Assert.True(result); // Model Assert.NotNull(model.Address); Assert.NotSame(collection, model.Address); Assert.Single(model.Address); 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.Equal("SomeStreet", state.AttemptedValue); Assert.Equal("SomeStreet", state.RawValue); Assert.Empty(state.Errors); Assert.Equal(ModelValidationState.Valid, state.ValidationState); } private class Person5 { public Address[] Address { get; } = new Address[] { }; } [Fact] public async Task TryUpdateModel_NonSettableArrayModel_EmptyPrefix_IsNotBound() { // Arrange var testContext = ModelBindingTestHelper.GetTestContext(request => { request.QueryString = QueryString.Create("Address[0].Street", "SomeStreet"); }); var modelState = testContext.ModelState; var model = new Person5(); // Act var result = await TryUpdateModelAsync(model, string.Empty, testContext); // Assert Assert.True(result); // Model Assert.NotNull(model.Address); // Arrays should not be updated. Assert.Empty(model.Address); // ModelState Assert.True(modelState.IsValid); Assert.Empty(modelState); } private class Person7 { public IEnumerable
Address { get; } = new Address[] { new Address() { City = "Redmond", Street = "One Microsoft Way" } }; } [Fact] public async Task TryUpdateModel_NonSettableIEnumerableModel_EmptyPrefix_IsNotBound() { // Arrange var testContext = ModelBindingTestHelper.GetTestContext(request => { request.QueryString = QueryString.Create("Address[0].Street", "SomeStreet"); }); var modelState = testContext.ModelState; var model = new Person7(); // Act var result = await TryUpdateModelAsync(model, string.Empty, testContext); // Assert Assert.True(result); // Model Assert.NotNull(model.Address); // Arrays should not be updated. Assert.Single(model.Address); Assert.Collection( model.Address, (a) => { Assert.Equal("Redmond", a.City); Assert.Equal("One Microsoft Way", a.Street); }); // ModelState Assert.True(modelState.IsValid); } private class Person8 { public ICollection
Address { get; } = new Address[] { new Address() { City = "Redmond", Street = "One Microsoft Way" } }; } [Fact] public async Task TryUpdateModel_NonSettableICollectionModel_EmptyPrefix_IsNotBound() { // Arrange var testContext = ModelBindingTestHelper.GetTestContext(request => { request.QueryString = QueryString.Create("Address[0].Street", "SomeStreet"); }); var modelState = testContext.ModelState; var model = new Person8(); // Act var result = await TryUpdateModelAsync(model, string.Empty, testContext); // Assert Assert.True(result); // Model Assert.NotNull(model.Address); // Arrays should not be updated. Assert.Single(model.Address); Assert.Collection( model.Address, (a) => { Assert.Equal("Redmond", a.City); Assert.Equal("One Microsoft Way", a.Street); }); // ModelState Assert.True(modelState.IsValid); } [Fact] public async Task TryUpdateModel_ExistingModel_WithPrefix_ValuesGetOverwritten() { // Arrange var testContext = ModelBindingTestHelper.GetTestContext(request => { request.QueryString = QueryString.Create("prefix.Street", "SomeStreet"); }); var modelState = testContext.ModelState; var model = new Address { Street = "DefaultStreet", City = "Toronto", }; var oldModel = model; // Act var result = await TryUpdateModelAsync(model, "prefix", testContext); // Assert Assert.True(result); // Model Assert.Same(oldModel, model); Assert.Equal("SomeStreet", model.Street); Assert.Equal("Toronto", model.City); // ModelState Assert.True(modelState.IsValid); var entry = Assert.Single(modelState); Assert.Equal("prefix.Street", entry.Key); var state = entry.Value; Assert.Equal("SomeStreet", state.AttemptedValue); Assert.Equal("SomeStreet", state.RawValue); Assert.Empty(state.Errors); Assert.Equal(ModelValidationState.Valid, state.ValidationState); } [Fact] public async Task TryUpdateModel_ExistingModel_WithPrefix_GetsBound() { // Arrange var testContext = ModelBindingTestHelper.GetTestContext(request => { request.QueryString = QueryString.Create("prefix.Street", "SomeStreet"); }); var modelState = testContext.ModelState; var model = new Address(); // Act var result = await TryUpdateModelAsync(model, "prefix", testContext); // Assert Assert.True(result); // Model Assert.Equal("SomeStreet", model.Street); Assert.Null(model.City); // ModelState Assert.True(modelState.IsValid); var entry = Assert.Single(modelState); Assert.Equal("prefix.Street", entry.Key); var state = entry.Value; Assert.Equal("SomeStreet", state.AttemptedValue); Assert.Equal("SomeStreet", state.RawValue); Assert.Empty(state.Errors); Assert.Equal(ModelValidationState.Valid, state.ValidationState); } [Fact] public async Task TryUpdateModel_TopLevelCollection_WithPrefix_BindsAfterClearing() { // Arrange var testContext = ModelBindingTestHelper.GetTestContext(request => { request.QueryString = QueryString.Create(new Dictionary { { "prefix[0].Name", "One Name" }, { "prefix[1].Address.Street", "Two Street" }, }); }); var modelState = testContext.ModelState; var model = new List { new Person1 { Name = "One", Address = new Address { Street = "DefaultStreet", City = "Toronto", }, }, new Person1 { Name = "Two" }, new Person1 { Name = "Three" }, }; // Act var result = await TryUpdateModelAsync(model, "prefix", testContext); // Assert Assert.True(result); // Model Assert.Collection( model, element => { Assert.Equal("One Name", element.Name); Assert.Null(element.Address); }, element => { Assert.Null(element.Name); Assert.NotNull(element.Address); Assert.Equal("Two Street", element.Address.Street); Assert.Null(element.Address.City); }); // ModelState Assert.True(modelState.IsValid); Assert.Equal(2, modelState.Count); Assert.NotNull(modelState["prefix[0].Name"]); Assert.NotNull(modelState["prefix[1].Address.Street"]); } [Fact] public async Task TryUpdateModel_NestedPoco_WithPrefix_DoesNotTrounceUnboundValues() { // Arrange var testContext = ModelBindingTestHelper.GetTestContext(request => { request.QueryString = QueryString.Create("prefix.Address.Street", "SomeStreet"); }); var modelState = testContext.ModelState; var model = new Person1 { Name = "Joe", Address = new Address { Street = "DefaultStreet", City = "Toronto", }, }; var oldModel = model; // Act var result = await TryUpdateModelAsync(model, "prefix", testContext); // 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.Equal("SomeStreet", state.AttemptedValue); Assert.Equal("SomeStreet", state.RawValue); Assert.Empty(state.Errors); Assert.Equal(ModelValidationState.Valid, state.ValidationState); } [Fact] public async Task TryUpdateModel_SettableCollectionModel_WithPrefix_CreatesCollection() { // Arrange var testContext = ModelBindingTestHelper.GetTestContext(request => { request.QueryString = QueryString.Create("prefix.Address[0].Street", "SomeStreet"); }); var modelState = testContext.ModelState; var model = new Person2(); // Act var result = await TryUpdateModelAsync(model, "prefix", testContext); // Assert Assert.True(result); // Model Assert.NotNull(model.Address); Assert.Single(model.Address); 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.Equal("SomeStreet", state.AttemptedValue); Assert.Equal("SomeStreet", state.RawValue); Assert.Empty(state.Errors); Assert.Equal(ModelValidationState.Valid, state.ValidationState); } [Fact] public async Task TryUpdateModel_SettableCollectionModel_WithPrefix_MaintainsCollectionIfNonNull() { // Arrange var testContext = ModelBindingTestHelper.GetTestContext(request => { request.QueryString = QueryString.Create("prefix.Address[0].Street", "SomeStreet"); }); var modelState = testContext.ModelState; var model = new Person2 { Address = new List
(), }; var collection = model.Address; // Act var result = await TryUpdateModelAsync(model, "prefix", testContext); // Assert Assert.True(result); // Model Assert.NotNull(model.Address); Assert.Same(collection, model.Address); Assert.Single(model.Address); 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.Equal("SomeStreet", state.AttemptedValue); Assert.Equal("SomeStreet", state.RawValue); Assert.Empty(state.Errors); Assert.Equal(ModelValidationState.Valid, state.ValidationState); } [Fact] public async Task TryUpdateModel_NonSettableCollectionModel_WithPrefix_GetsBound() { // Arrange var testContext = ModelBindingTestHelper.GetTestContext(request => { request.QueryString = QueryString.Create("prefix.Address[0].Street", "SomeStreet"); }); var modelState = testContext.ModelState; var model = new Person3 { Address = { new Address { Street = "Old street", City = "Redmond", }, new Address { Street = "Older street", City = "Toronto", }, }, }; // Act var result = await TryUpdateModelAsync(model, "prefix", testContext); // Assert Assert.True(result); // Model (collection is cleared and new members created from scratch). Assert.NotNull(model.Address); Assert.Single(model.Address); 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.Equal("SomeStreet", state.AttemptedValue); Assert.Equal("SomeStreet", state.RawValue); Assert.Empty(state.Errors); Assert.Equal(ModelValidationState.Valid, state.ValidationState); } [Fact] public async Task TryUpdateModel_SettableArrayModel_WithPrefix_CreatesArray() { // Arrange var testContext = ModelBindingTestHelper.GetTestContext(request => { request.QueryString = QueryString.Create("prefix.Address[0].Street", "SomeStreet"); }); var modelState = testContext.ModelState; var model = new Person4(); // Act var result = await TryUpdateModelAsync(model, "prefix", testContext); // Assert Assert.True(result); // Model Assert.NotNull(model.Address); Assert.Single(model.Address); 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.Equal("SomeStreet", state.AttemptedValue); Assert.Equal("SomeStreet", state.RawValue); Assert.Empty(state.Errors); Assert.Equal(ModelValidationState.Valid, state.ValidationState); } [Fact] public async Task TryUpdateModel_SettableArrayModel_WithPrefix_OverwritesArray() { // Arrange var testContext = ModelBindingTestHelper.GetTestContext(request => { request.QueryString = QueryString.Create("prefix.Address[0].Street", "SomeStreet"); }); var modelState = testContext.ModelState; var model = new Person4 { Address = new Address[] { new Address { Street = "Old street", City = "Toronto", }, }, }; var collection = model.Address; // Act var result = await TryUpdateModelAsync(model, "prefix", testContext); // Assert Assert.True(result); // Model Assert.NotNull(model.Address); Assert.NotSame(collection, model.Address); Assert.Single(model.Address); 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.Equal("SomeStreet", state.AttemptedValue); Assert.Equal("SomeStreet", state.RawValue); Assert.Empty(state.Errors); Assert.Equal(ModelValidationState.Valid, state.ValidationState); } [Fact] public async Task TryUpdateModel_NonSettableArrayModel_WithPrefix_GetsBound() { // Arrange var testContext = ModelBindingTestHelper.GetTestContext(request => { request.QueryString = QueryString.Create("prefix.Address[0].Street", "SomeStreet"); }); var modelState = testContext.ModelState; var model = new Person5(); // Act var result = await TryUpdateModelAsync(model, "prefix", testContext); // Assert Assert.True(result); // Model Assert.NotNull(model.Address); // Arrays should not be updated. Assert.Empty(model.Address); // ModelState Assert.True(modelState.IsValid); Assert.Empty(modelState); } [Fact] public async Task TryUpdateModelAsync_TopLevelFormFileCollection_IsBound() { // Arrange var data = "some data"; var testContext = ModelBindingTestHelper.GetTestContext( request => UpdateRequest(request, data, "files")); var modelState = testContext.ModelState; var model = new List { new FormFile(new MemoryStream(), baseStreamOffset: 0, length: 0, name: "file", fileName: "file1"), new FormFile(new MemoryStream(), baseStreamOffset: 0, length: 0, name: "file", fileName: "file2"), new FormFile(new MemoryStream(), baseStreamOffset: 0, length: 0, name: "file", fileName: "file3"), }; // Act var result = await TryUpdateModelAsync(model, prefix: "files", testContext: testContext); // Assert Assert.True(result); // Model var file = Assert.Single(model); Assert.Equal("form-data; name=files; filename=text.txt", file.ContentDisposition); using (var reader = new StreamReader(file.OpenReadStream())) { Assert.Equal(data, reader.ReadToEnd()); } // ModelState Assert.True(modelState.IsValid); var kvp = Assert.Single(modelState); Assert.Equal("files", kvp.Key); var modelStateEntry = kvp.Value; Assert.NotNull(modelStateEntry); Assert.Empty(modelStateEntry.Errors); Assert.Equal(ModelValidationState.Valid, modelStateEntry.ValidationState); Assert.Null(modelStateEntry.AttemptedValue); Assert.Null(modelStateEntry.RawValue); } private class AddressWithNoParameterlessConstructor { private readonly int _id; public AddressWithNoParameterlessConstructor(int id) { _id = id; } public string Street { get; set; } public string City { get; set; } } [Fact] public async Task TryUpdateModel_ExistingModelWithNoParameterlessConstructor_OverwritesBoundValues() { // Arrange var testContext = ModelBindingTestHelper.GetTestContext(request => { request.QueryString = QueryString.Create("Street", "SomeStreet"); }); var modelState = testContext.ModelState; var model = new AddressWithNoParameterlessConstructor(10) { Street = "DefaultStreet", City = "Toronto", }; var oldModel = model; // Act var result = await TryUpdateModelAsync(model, string.Empty, testContext); // Assert Assert.True(result); // Model Assert.Same(oldModel, model); Assert.Equal("SomeStreet", model.Street); Assert.Equal("Toronto", model.City); // ModelState Assert.True(modelState.IsValid); var entry = Assert.Single(modelState); Assert.Equal("Street", entry.Key); var state = entry.Value; Assert.Equal("SomeStreet", state.AttemptedValue); Assert.Equal("SomeStreet", state.RawValue); Assert.Empty(state.Errors); Assert.Equal(ModelValidationState.Valid, state.ValidationState); } private void UpdateRequest(HttpRequest request, string data, string name) { const string fileName = "text.txt"; var fileCollection = new FormFileCollection(); var formCollection = new FormCollection(new Dictionary(), fileCollection); request.Form = formCollection; request.ContentType = "multipart/form-data; boundary=----WebKitFormBoundarymx2fSWqWSd0OxQqq"; request.Headers["Content-Disposition"] = $"form-data; name={name}; filename={fileName}"; var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(data)); fileCollection.Add(new FormFile(memoryStream, 0, data.Length, name, fileName) { Headers = request.Headers }); } private class CustomReadOnlyCollection : ICollection { 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 async Task TryUpdateModelAsync( object model, string prefix, ModelBindingTestContext testContext) { var valueProvider = await CompositeValueProvider.CreateAsync(testContext); return await ModelBindingHelper.TryUpdateModelAsync( model, model.GetType(), prefix, testContext, testContext.MetadataProvider, TestModelBinderFactory.CreateDefault(), valueProvider, ModelBindingTestHelper.GetObjectValidator(testContext.MetadataProvider)); } } }