1243 lines
41 KiB
C#
1243 lines
41 KiB
C#
// 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<string, string>
|
|
{
|
|
{ "[0].Name", "One Name" },
|
|
{ "[1].Address.Street", "Two Street" },
|
|
});
|
|
});
|
|
|
|
var modelState = testContext.ModelState;
|
|
var model = new List<Person1>
|
|
{
|
|
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> 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<Address>(),
|
|
};
|
|
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<Address>();
|
|
}
|
|
|
|
public List<Address> 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> 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> 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> 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<string, string>
|
|
{
|
|
{ "prefix[0].Name", "One Name" },
|
|
{ "prefix[1].Address.Street", "Two Street" },
|
|
});
|
|
});
|
|
|
|
var modelState = testContext.ModelState;
|
|
var model = new List<Person1>
|
|
{
|
|
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<Address>(),
|
|
};
|
|
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<IFormFile>
|
|
{
|
|
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<string, StringValues>(), 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<T> : ICollection<T>
|
|
{
|
|
private ICollection<T> _original;
|
|
|
|
public CustomReadOnlyCollection()
|
|
: this(new List<T>())
|
|
{
|
|
}
|
|
|
|
public CustomReadOnlyCollection(ICollection<T> 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<T> GetEnumerator()
|
|
{
|
|
foreach (T t in _original)
|
|
{
|
|
yield return t;
|
|
}
|
|
}
|
|
|
|
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
|
{
|
|
return GetEnumerator();
|
|
}
|
|
}
|
|
|
|
private async Task<bool> 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));
|
|
}
|
|
}
|
|
} |