// 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.IO; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features.Internal; using Microsoft.AspNetCore.Http.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding; 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 operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = QueryString.Create("Street", "SomeStreet"); }); var modelState = operationContext.ActionContext.ModelState; var model = new Address { Street = "DefaultStreet", City = "Toronto", }; var oldModel = model; // Act var result = await TryUpdateModel(model, string.Empty, operationContext); // 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 operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = QueryString.Create("Street", "SomeStreet"); }); var modelState = operationContext.ActionContext.ModelState; var model = new Address(); // Act var result = await TryUpdateModel(model, string.Empty, operationContext); // 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 operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = QueryString.Create(new Dictionary { { "[0].Name", "One Name" }, { "[1].Address.Street", "Two Street" }, }); }); var modelState = operationContext.ActionContext.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 TryUpdateModel(model, string.Empty, operationContext); // 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 operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = QueryString.Create("Address.Street", "SomeStreet"); }); var modelState = operationContext.ActionContext.ModelState; 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); // 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 operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = QueryString.Create("Address[0].Street", "SomeStreet"); }); var modelState = operationContext.ActionContext.ModelState; var model = new Person2(); // Act var result = await TryUpdateModel(model, string.Empty, operationContext); // Assert Assert.True(result); // Model 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); 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 operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = QueryString.Create("Address[0].Street", "SomeStreet"); }); var modelState = operationContext.ActionContext.ModelState; var model = new Person2 { Address = new List
(), }; var collection = model.Address; // Act var result = await TryUpdateModel(model, string.Empty, operationContext); // 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.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 operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = QueryString.Create("Address[0].Street", "SomeStreet"); }); var modelState = operationContext.ActionContext.ModelState; 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); // Assert Assert.True(result); // 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); 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; } } public async Task TryUpdateModel_ReadOnlyCollectionModel_EmptyPrefix_DoesNotGetBound() { // Arrange var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = QueryString.Create("Address[0].Street", "SomeStreet"); }); var modelState = operationContext.ActionContext.ModelState; var model = new Person6(); // Act var result = await TryUpdateModel(model, string.Empty, operationContext); // Assert Assert.True(result); // Model Assert.NotNull(model.Address); // Read-only collection should not be updated. Assert.Empty(model.Address); // ModelState (data is valid but is not copied into Address). Assert.True(modelState.IsValid); var entry = Assert.Single(modelState); Assert.Equal("Address[0].Street", entry.Key); var state = entry.Value; Assert.NotNull(state); Assert.Equal(ModelValidationState.Valid, state.ValidationState); Assert.Equal("SomeStreet", state.RawValue); } private class Person4 { public Address[] Address { get; set; } } [Fact] public async Task TryUpdateModel_SettableArrayModel_EmptyPrefix_CreatesArray() { // Arrange var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = QueryString.Create("Address[0].Street", "SomeStreet"); }); var modelState = operationContext.ActionContext.ModelState; var model = new Person4(); // Act var result = await TryUpdateModel(model, string.Empty, operationContext); // Assert Assert.True(result); // Model Assert.NotNull(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.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 operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = QueryString.Create("Address[0].Street", "SomeStreet"); }); var modelState = operationContext.ActionContext.ModelState; 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); // 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.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_GetsBound() { // Arrange var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = QueryString.Create("Address[0].Street", "SomeStreet"); }); var modelState = operationContext.ActionContext.ModelState; var model = new Person5(); // Act var result = await TryUpdateModel(model, string.Empty, operationContext); // Assert Assert.True(result); // Model Assert.NotNull(model.Address); // Arrays should not be updated. Assert.Equal(0, model.Address.Length); // ModelState Assert.True(modelState.IsValid); Assert.Empty(modelState); } [Fact] public async Task TryUpdateModel_ExistingModel_WithPrefix_ValuesGetOverwritten() { // Arrange var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = QueryString.Create("prefix.Street", "SomeStreet"); }); var modelState = operationContext.ActionContext.ModelState; var model = new Address { Street = "DefaultStreet", City = "Toronto", }; var oldModel = model; // Act var result = await TryUpdateModel(model, "prefix", operationContext); // 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 operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = QueryString.Create("prefix.Street", "SomeStreet"); }); var modelState = operationContext.ActionContext.ModelState; var model = new Address(); // Act var result = await TryUpdateModel(model, "prefix", operationContext); // 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 operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = QueryString.Create(new Dictionary { { "prefix[0].Name", "One Name" }, { "prefix[1].Address.Street", "Two Street" }, }); }); var modelState = operationContext.ActionContext.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 TryUpdateModel(model, "prefix", operationContext); // 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 operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = QueryString.Create("prefix.Address.Street", "SomeStreet"); }); var modelState = operationContext.ActionContext.ModelState; var model = new Person1 { Name = "Joe", Address = new Address { Street = "DefaultStreet", City = "Toronto", }, }; var oldModel = model; // Act var result = await TryUpdateModel(model, "prefix", operationContext); // 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 operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = QueryString.Create("prefix.Address[0].Street", "SomeStreet"); }); var modelState = operationContext.ActionContext.ModelState; var model = new Person2(); // Act var result = await TryUpdateModel(model, "prefix", operationContext); // Assert Assert.True(result); // Model 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); 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 operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = QueryString.Create("prefix.Address[0].Street", "SomeStreet"); }); var modelState = operationContext.ActionContext.ModelState; var model = new Person2 { Address = new List
(), }; var collection = model.Address; // Act var result = await TryUpdateModel(model, "prefix", operationContext); // 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.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 operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = QueryString.Create("prefix.Address[0].Street", "SomeStreet"); }); var modelState = operationContext.ActionContext.ModelState; 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); // Assert Assert.True(result); // 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); 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); } public async Task TryUpdateModel_ReadOnlyCollectionModel_WithPrefix_DoesNotGetBound() { // Arrange var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = QueryString.Create("prefix.Address[0].Street", "SomeStreet"); }); var modelState = operationContext.ActionContext.ModelState; var model = new Person6(); // Act var result = await TryUpdateModel(model, "prefix", operationContext); // Assert Assert.True(result); // Model Assert.NotNull(model.Address); // Read-only collection should not be updated. Assert.Empty(model.Address); // ModelState (data is valid but is not copied into Address). Assert.True(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.Valid, state.ValidationState); Assert.Equal("SomeStreet", state.RawValue); } [Fact] public async Task TryUpdateModel_SettableArrayModel_WithPrefix_CreatesArray() { // Arrange var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = QueryString.Create("prefix.Address[0].Street", "SomeStreet"); }); var modelState = operationContext.ActionContext.ModelState; var model = new Person4(); // Act var result = await TryUpdateModel(model, "prefix", operationContext); // Assert Assert.True(result); // Model Assert.NotNull(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.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 operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = QueryString.Create("prefix.Address[0].Street", "SomeStreet"); }); var modelState = operationContext.ActionContext.ModelState; 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); // 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.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 operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = QueryString.Create("prefix.Address[0].Street", "SomeStreet"); }); var modelState = operationContext.ActionContext.ModelState; var model = new Person5(); // Act var result = await TryUpdateModel(model, "prefix", operationContext); // Assert Assert.True(result); // Model Assert.NotNull(model.Address); // Arrays should not be updated. Assert.Equal(0, model.Address.Length); // ModelState Assert.True(modelState.IsValid); Assert.Empty(modelState); } [Fact] public async Task TryUpdateModelAsync_TopLevelFormFileCollection_IsBound() { // Arrange var data = "some data"; var operationContext = ModelBindingTestHelper.GetOperationBindingContext( request => UpdateRequest(request, data, "files")); var modelState = operationContext.ActionContext.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 TryUpdateModel(model, prefix: "files", operationContext: operationContext); // 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.Skipped, modelStateEntry.ValidationState); Assert.Null(modelStateEntry.AttemptedValue); Assert.Null(modelStateEntry.RawValue); } 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 Task TryUpdateModel( object model, string prefix, OperationBindingContext operationContext) { return ModelBindingHelper.TryUpdateModelAsync( model, model.GetType(), prefix, operationContext.ActionContext, operationContext.MetadataProvider, operationContext.ModelBinder, operationContext.ValueProvider, operationContext.InputFormatters, ModelBindingTestHelper.GetObjectValidator(operationContext.MetadataProvider), operationContext.ValidatorProvider); } } }