// 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.Linq.Expressions; using System.Net; using System.Net.Http; using System.Reflection; using System.Text; using System.Threading.Tasks; using Microsoft.AspNet.Builder; using Microsoft.AspNet.TestHost; using ModelBindingWebSite.Models; using ModelBindingWebSite.ViewModels; using Newtonsoft.Json; using Xunit; namespace Microsoft.AspNet.Mvc.FunctionalTests { public class ModelBindingTest { private readonly IServiceProvider _services = TestHelper.CreateServices("ModelBindingWebSite"); private readonly Action _app = new ModelBindingWebSite.Startup().Configure; [Fact] public async Task TypeBasedExclusion_ForBodyAndNonBodyBoundModels() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Make sure the body object gets created with an invalid zip. var input = "{\"OfficeAddress.Zip\":\"45\"}"; var content = new StringContent(input, Encoding.UTF8, "application/json"); // Act // Make sure the non body based object gets created with an invalid zip. var response = await client.PostAsync( "http://localhost/Validation/SkipValidation?ShippingAddresses[0].Zip=45&HomeAddress.Zip=46", content); // Assert var stringValue = await response.Content.ReadAsStringAsync(); var isModelStateValid = JsonConvert.DeserializeObject(stringValue); Assert.True(isModelStateValid); } [Fact] public async Task ModelValidation_DoesNotValidate_AnAlreadyValidatedObject() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetAsync( "http://localhost/Validation/AvoidRecursive?Name=selfish"); // Assert var stringValue = await response.Content.ReadAsStringAsync(); var isModelStateValid = JsonConvert.DeserializeObject(stringValue); Assert.True(isModelStateValid); } [Theory] [InlineData("RestrictValueProvidersUsingFromRoute", "valueFromRoute")] [InlineData("RestrictValueProvidersUsingFromQuery", "valueFromQuery")] [InlineData("RestrictValueProvidersUsingFromForm", "valueFromForm")] public async Task CompositeModelBinder_Restricts_ValueProviders(string actionName, string expectedValue) { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Provide all three values, it should bind based on the attribute on the action method. var request = new HttpRequestMessage(HttpMethod.Post, string.Format("http://localhost/CompositeTest/{0}/valueFromRoute?param=valueFromQuery", actionName)); var nameValueCollection = new List> { new KeyValuePair("param", "valueFromForm"), }; request.Content = new FormUrlEncodedContent(nameValueCollection); // Act var response = await client.SendAsync(request); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(expectedValue, await response.Content.ReadAsStringAsync()); } [Fact] public async Task TryUpdateModel_WithAPropertyFromBody() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // the name would be of customer.Department.Name // and not for the top level customer object. var input = "{\"Name\":\"RandomDepartment\"}"; var content = new StringContent(input, Encoding.UTF8, "application/json"); // Act var response = await client.PostAsync("http://localhost/Home/GetCustomer?Id=1234", content); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var customer = JsonConvert.DeserializeObject( await response.Content.ReadAsStringAsync()); Assert.NotNull(customer.Department); Assert.Equal("RandomDepartment", customer.Department.Name); Assert.Equal(1234, customer.Id); Assert.Equal(25, customer.Age); Assert.Equal("dummy", customer.Name); } [Fact] public async Task CanModelBindServiceToAnArgument() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetAsync("http://localhost/FromServices_Calculator/Add?left=1234&right=1"); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal("1235", await response.Content.ReadAsStringAsync()); } [Fact] public async Task CanModelBindServiceToAProperty() { var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetAsync( "http://localhost/FromServices_Calculator/Calculate?Left=10&Right=5&Operator=*"); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal("50", await response.Content.ReadAsStringAsync()); } [Fact] public async Task CanModelBindServiceToAProperty_OnBaseType() { var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetAsync( "http://localhost/FromServices_Calculator/CalculateWithPrecision?Left=10&Right=5&Operator=*"); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal("50", await response.Content.ReadAsStringAsync()); } [Fact] public async Task MultipleParametersMarkedWithFromBody_Throws() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetAsync("http://localhost/FromAttributes/FromBodyParametersThrows"); // Assert var exception = response.GetServerException(); Assert.Equal(typeof(InvalidOperationException).FullName, exception.ExceptionType); Assert.Equal( "More than one parameter and/or property is bound to the HTTP request's content.", exception.ExceptionMessage); } [Fact] public async Task MultipleParameterAndPropertiesMarkedWithFromBody_Throws() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetAsync("http://localhost/FromAttributes/FromBodyParameterAndPropertyThrows"); // Assert var exception = response.GetServerException(); Assert.Equal(typeof(InvalidOperationException).FullName, exception.ExceptionType); Assert.Equal( "More than one parameter and/or property is bound to the HTTP request's content.", exception.ExceptionMessage); } [Fact] public async Task MultipleParametersMarkedWith_FromFormAndFromBody_Throws() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetAsync("http://localhost/FromAttributes/FormAndBody_AsParameters_Throws"); // Assert var exception = response.GetServerException(); Assert.Equal(typeof(InvalidOperationException).FullName, exception.ExceptionType); Assert.Equal( "More than one parameter and/or property is bound to the HTTP request's content.", exception.ExceptionMessage); } [Fact] public async Task MultipleParameterAndPropertiesMarkedWith_FromFormAndFromBody_Throws() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetAsync("http://localhost/FromAttributes/FormAndBody_Throws"); // Assert var exception = response.GetServerException(); Assert.Equal(typeof(InvalidOperationException).FullName, exception.ExceptionType); Assert.Equal( "More than one parameter and/or property is bound to the HTTP request's content.", exception.ExceptionMessage); } [Fact] public async Task CanBind_MultipleParameters_UsingFromForm() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/FromAttributes/MultipleFromFormParameters"); var nameValueCollection = new List> { new KeyValuePair("homeAddress.Street", "1"), new KeyValuePair("homeAddress.State", "WA_Form_Home"), new KeyValuePair("homeAddress.Zip", "2"), new KeyValuePair("officeAddress.Street", "3"), new KeyValuePair("officeAddress.State", "WA_Form_Office"), new KeyValuePair("officeAddress.Zip", "4"), }; request.Content = new FormUrlEncodedContent(nameValueCollection); // Act var response = await client.SendAsync(request); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var user = JsonConvert.DeserializeObject( await response.Content.ReadAsStringAsync()); Assert.Equal("WA_Form_Home", user.HomeAddress.State); Assert.Equal(1, user.HomeAddress.Street); Assert.Equal(2, user.HomeAddress.Zip); Assert.Equal("WA_Form_Office", user.OfficeAddress.State); Assert.Equal(3, user.OfficeAddress.Street); Assert.Equal(4, user.OfficeAddress.Zip); } [Fact] public async Task CanBind_MultipleProperties_UsingFromForm() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/FromAttributes/MultipleFromFormParameterAndProperty"); var nameValueCollection = new List> { new KeyValuePair("Street", "1"), new KeyValuePair("State", "WA_Form_Home"), new KeyValuePair("Zip", "2"), new KeyValuePair("officeAddress.Street", "3"), new KeyValuePair("officeAddress.State", "WA_Form_Office"), new KeyValuePair("officeAddress.Zip", "4"), }; request.Content = new FormUrlEncodedContent(nameValueCollection); // Act var response = await client.SendAsync(request); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var user = JsonConvert.DeserializeObject( await response.Content.ReadAsStringAsync()); Assert.Equal("WA_Form_Home", user.HomeAddress.State); Assert.Equal(1, user.HomeAddress.Street); Assert.Equal(2, user.HomeAddress.Zip); Assert.Equal("WA_Form_Office", user.OfficeAddress.State); Assert.Equal(3, user.OfficeAddress.Street); Assert.Equal(4, user.OfficeAddress.Zip); } [Fact] public async Task CanBind_ComplexData_OnParameters_UsingFromAttributes() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Provide all three values, it should bind based on the attribute on the action method. var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/FromAttributes/GetUser/5/WA_Route/6" + "?Street=3&State=WA_Query&Zip=4"); var nameValueCollection = new List> { new KeyValuePair("Street", "1"), new KeyValuePair("State", "WA_Form"), new KeyValuePair("Zip", "2"), }; request.Content = new FormUrlEncodedContent(nameValueCollection); // Act var response = await client.SendAsync(request); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var user = JsonConvert.DeserializeObject( await response.Content.ReadAsStringAsync()); // Assert FromRoute Assert.Equal("WA_Route", user.HomeAddress.State); Assert.Equal(5, user.HomeAddress.Street); Assert.Equal(6, user.HomeAddress.Zip); // Assert FromForm Assert.Equal("WA_Form", user.OfficeAddress.State); Assert.Equal(1, user.OfficeAddress.Street); Assert.Equal(2, user.OfficeAddress.Zip); // Assert FromQuery Assert.Equal("WA_Query", user.ShippingAddress.State); Assert.Equal(3, user.ShippingAddress.Street); Assert.Equal(4, user.ShippingAddress.Zip); } [Fact] public async Task CanBind_ComplexData_OnProperties_UsingFromAttributes() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Provide all three values, it should bind based on the attribute on the action method. var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/FromAttributes/GetUser_FromForm/5/WA_Route/6" + "?ShippingAddress.Street=3&ShippingAddress.State=WA_Query&ShippingAddress.Zip=4"); var nameValueCollection = new List> { new KeyValuePair("OfficeAddress.Street", "1"), new KeyValuePair("OfficeAddress.State", "WA_Form"), new KeyValuePair("OfficeAddress.Zip", "2"), }; request.Content = new FormUrlEncodedContent(nameValueCollection); // Act var response = await client.SendAsync(request); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var user = JsonConvert.DeserializeObject( await response.Content.ReadAsStringAsync()); // Assert FromRoute Assert.Equal("WA_Route", user.HomeAddress.State); Assert.Equal(5, user.HomeAddress.Street); Assert.Equal(6, user.HomeAddress.Zip); // Assert FromForm Assert.Equal("WA_Form", user.OfficeAddress.State); Assert.Equal(1, user.OfficeAddress.Street); Assert.Equal(2, user.OfficeAddress.Zip); // Assert FromQuery Assert.Equal("WA_Query", user.ShippingAddress.State); Assert.Equal(3, user.ShippingAddress.Street); Assert.Equal(4, user.ShippingAddress.Zip); } [Fact] public async Task CanBind_ComplexData_OnProperties_UsingFromAttributes_WithBody() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Provide all three values, it should bind based on the attribute on the action method. var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/FromAttributes/GetUser_FromBody/5/WA_Route/6" + "?ShippingAddress.Street=3&ShippingAddress.State=WA_Query&ShippingAddress.Zip=4"); var input = "{\"State\":\"WA_Body\",\"Street\":1,\"Zip\":2}"; request.Content = new StringContent(input, Encoding.UTF8, "application/json"); // Act var response = await client.SendAsync(request); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var user = JsonConvert.DeserializeObject( await response.Content.ReadAsStringAsync()); // Assert FromRoute Assert.Equal("WA_Route", user.HomeAddress.State); Assert.Equal(5, user.HomeAddress.Street); Assert.Equal(6, user.HomeAddress.Zip); // Assert FromBody Assert.Equal("WA_Body", user.OfficeAddress.State); Assert.Equal(1, user.OfficeAddress.Street); Assert.Equal(2, user.OfficeAddress.Zip); // Assert FromQuery Assert.Equal("WA_Query", user.ShippingAddress.State); Assert.Equal(3, user.ShippingAddress.Street); Assert.Equal(4, user.ShippingAddress.Zip); } [Fact] public async Task NonExistingModelBinder_ForABinderMetadata_DoesNotRecurseInfinitely() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act & Assert var response = await client.GetStringAsync("http://localhost/WithBinderMetadata/EchoDocument"); var document = JsonConvert.DeserializeObject (response); Assert.NotNull(document); Assert.Null(document.Version); Assert.Null(document.SubDocument); } [Fact] public async Task ParametersWithNoValueProviderMetadataUseTheAvailableValueProviders() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetAsync("http://localhost/WithBinderMetadata" + "/ParametersWithNoValueProviderMetadataUseTheAvailableValueProviders" + "?Name=somename&Age=12"); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var emp = JsonConvert.DeserializeObject( await response.Content.ReadAsStringAsync()); Assert.Null(emp.Department); Assert.Equal("somename", emp.Name); Assert.Equal(12, emp.Age); } [Fact] public async Task ParametersAreAlwaysCreated_IfValuesAreProvidedWithoutModelName() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetAsync("http://localhost/WithoutBinderMetadata" + "/GetPersonParameter" + "?Name=somename&Age=12"); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var person = JsonConvert.DeserializeObject( await response.Content.ReadAsStringAsync()); Assert.NotNull(person); Assert.Equal("somename", person.Name); Assert.Equal(12, person.Age); } [Fact] public async Task ParametersAreAlwaysCreated_IfValueIsProvidedForModelName() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetAsync("http://localhost/WithoutBinderMetadata" + "/GetPersonParameter?p="); // here p is the model name. //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var person = JsonConvert.DeserializeObject( await response.Content.ReadAsStringAsync()); Assert.NotNull(person); Assert.Null(person.Name); Assert.Equal(0, person.Age); } [Fact] public async Task ParametersAreAlwaysCreated_IfNoValuesAreProvided() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetAsync("http://localhost/WithoutBinderMetadata" + "/GetPersonParameter"); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var person = JsonConvert.DeserializeObject( await response.Content.ReadAsStringAsync()); Assert.NotNull(person); Assert.Null(person.Name); Assert.Equal(0, person.Age); } [Fact] public async Task PropertiesAreBound_IfTheyAreProvidedByValueProviders() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetAsync("http://localhost/Properties" + "/GetCompany?Employees[0].Name=somename&Age=12"); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var company = JsonConvert.DeserializeObject( await response.Content.ReadAsStringAsync()); Assert.NotNull(company); Assert.NotNull(company.Employees); Assert.Equal(1, company.Employees.Count); Assert.NotNull(company.Employees[0].Name); } [Fact] public async Task PropertiesAreBound_IfTheyAreMarkedExplicitly() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetAsync("http://localhost/Properties" + "/GetCompany"); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var company = JsonConvert.DeserializeObject( await response.Content.ReadAsStringAsync()); Assert.NotNull(company); Assert.NotNull(company.CEO); Assert.Null(company.CEO.Name); } [Fact] public async Task PropertiesAreBound_IfTheyArePocoMetadataMarkedTypes() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetAsync("http://localhost/Properties" + "/GetCompany"); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var company = JsonConvert.DeserializeObject( await response.Content.ReadAsStringAsync()); Assert.NotNull(company); // Department property is not null because it was a marker poco. Assert.NotNull(company.Department); // beacause no value is provided. Assert.Null(company.Department.Name); } [Fact] public async Task PropertiesAreNotBound_ByDefault() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetAsync("http://localhost/Properties" + "/GetCompany"); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var company = JsonConvert.DeserializeObject( await response.Content.ReadAsStringAsync()); Assert.NotNull(company); Assert.Null(company.Employees); } [Theory] [InlineData("http://localhost/Home/ActionWithPersonFromUrlWithPrefix/Javier/26")] [InlineData("http://localhost/Home/ActionWithPersonFromUrlWithoutPrefix/Javier/26")] public async Task CanBind_ComplexData_FromRouteData(string url) { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetAsync(url); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadAsStringAsync(); Assert.NotNull(body); var person = JsonConvert.DeserializeObject(body); Assert.NotNull(person); Assert.Equal("Javier", person.Name); Assert.Equal(26, person.Age); } [Fact] public async Task ModelBindCancellationTokenParameteres() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetAsync("http://localhost/Home/ActionWithCancellationToken"); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal("true", await response.Content.ReadAsStringAsync()); } [Fact] public async Task ModelBindCancellationToken_ForProperties() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetAsync( "http://localhost/Home/ActionWithCancellationTokenModel?wrapper=bogusValue"); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal("true", await response.Content.ReadAsStringAsync()); } [Fact] public async Task ModelBindingBindsBase64StringsToByteArrays() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetAsync("http://localhost/Home/Index?byteValues=SGVsbG9Xb3JsZA=="); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal("HelloWorld", await response.Content.ReadAsStringAsync()); } [Fact] public async Task ModelBindingBindsEmptyStringsToByteArrays() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetAsync("http://localhost/Home/Index?byteValues="); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal("\0", await response.Content.ReadAsStringAsync()); } [Fact] public async Task ModelBinding_LimitsErrorsToMaxErrorCount() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var queryString = string.Join("=&", Enumerable.Range(0, 10).Select(i => "field" + i)); // Act var response = await client.GetStringAsync("http://localhost/Home/ModelWithTooManyValidationErrors?" + queryString); //Assert var json = JsonConvert.DeserializeObject>(response); // 8 is the value of MaxModelValidationErrors for the application being tested. Assert.Equal(8, json.Count); Assert.Equal("The Field1 field is required.", json["Field1.Field1"]); Assert.Equal("The Field2 field is required.", json["Field1.Field2"]); Assert.Equal("The Field3 field is required.", json["Field1.Field3"]); Assert.Equal("The Field1 field is required.", json["Field2.Field1"]); Assert.Equal("The Field2 field is required.", json["Field2.Field2"]); Assert.Equal("The Field3 field is required.", json["Field2.Field3"]); Assert.Equal("The Field1 field is required.", json["Field3.Field1"]); Assert.Equal("The maximum number of allowed model errors has been reached.", json[""]); } [Fact] public async Task ModelBinding_ValidatesAllPropertiesInModel() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetStringAsync("http://localhost/Home/ModelWithFewValidationErrors?model="); //Assert var json = JsonConvert.DeserializeObject>(response); Assert.Equal(3, json.Count); Assert.Equal("The Field1 field is required.", json["model.Field1"]); Assert.Equal("The Field2 field is required.", json["model.Field2"]); Assert.Equal("The Field3 field is required.", json["model.Field3"]); } [Fact] public async Task BindAttribute_Filters_UsingDefaultPropertyFilterProvider_WithExpressions() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetStringAsync("http://localhost/BindAttribute/" + "EchoUser" + "?user.UserName=someValue&user.RegisterationMonth=March&user.Id=123"); // Assert var json = JsonConvert.DeserializeObject(response); // Does not touch what is not in the included expression. Assert.Equal(0, json.Id); // Updates the included properties. Assert.Equal("someValue", json.UserName); Assert.Equal("March", json.RegisterationMonth); } [Fact] public async Task BindAttribute_Filters_UsingPropertyFilterProvider_UsingServices() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetStringAsync("http://localhost/BindAttribute/" + "EchoUserUsingServices" + "?user.UserName=someValue&user.RegisterationMonth=March&user.Id=123"); // Assert var json = JsonConvert.DeserializeObject(response); // Does not touch what is not in the included expression. Assert.Equal(0, json.Id); // Updates the included properties. Assert.Equal("someValue", json.UserName); Assert.Equal("March", json.RegisterationMonth); } [Fact] public async Task BindAttribute_Filters_UsingDefaultPropertyFilterProvider_WithPredicate() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetStringAsync("http://localhost/BindAttribute/" + "UpdateUserId_BlackListingAtEitherLevelDoesNotBind" + "?param1.LastName=someValue¶m2.Id=123"); // Assert var json = JsonConvert.DeserializeObject>(response); Assert.Equal(2, json.Count); Assert.Null(json["param1.LastName"]); Assert.Equal("0", json["param2.Id"]); } [Fact] public async Task BindAttribute_AppliesAtBothParameterAndTypeLevelTogether_BlacklistedAtEitherLevelIsNotBound() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetStringAsync("http://localhost/BindAttribute/" + "UpdateUserId_BlackListingAtEitherLevelDoesNotBind" + "?param1.LastName=someValue¶m2.Id=123"); // Assert var json = JsonConvert.DeserializeObject>(response); Assert.Equal(2, json.Count); Assert.Null(json["param1.LastName"]); Assert.Equal("0", json["param2.Id"]); } [Fact] public async Task BindAttribute_AppliesAtBothParameterAndTypeLevelTogether_IncludedAtBothLevelsIsBound() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetStringAsync("http://localhost/BindAttribute/" + "UpdateFirstName_IncludingAtBothLevelBinds" + "?param1.FirstName=someValue¶m2.Id=123"); // Assert var json = JsonConvert.DeserializeObject>(response); Assert.Equal(1, json.Count); Assert.Equal("someValue", json["param1.FirstName"]); } [Fact] public async Task BindAttribute_AppliesAtBothParameterAndTypeLevelTogether_IncludingAtOneLevelIsNotBound() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetStringAsync("http://localhost/BindAttribute/" + "UpdateIsAdmin_IncludingAtOnlyOneLevelDoesNotBind" + "?param1.FirstName=someValue¶m1.IsAdmin=true"); // Assert var json = JsonConvert.DeserializeObject>(response); Assert.Equal(2, json.Count); Assert.Equal("False", json["param1.IsAdmin"]); Assert.Null(json["param1.FirstName"]); } [Fact] public async Task BindAttribute_BindsUsingParameterPrefix() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetStringAsync("http://localhost/BindAttribute/" + "BindParameterUsingParameterPrefix" + "?randomPrefix.Value=someValue"); // Assert Assert.Equal("someValue", response); } [Fact] public async Task BindAttribute_DoesNotUseTypePrefix() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetStringAsync("http://localhost/BindAttribute/" + "TypePrefixIsNeverUsed" + "?param.Value=someValue"); // Assert Assert.Equal("someValue", response); } [Fact] public async Task BindAttribute_FallsBackOnEmptyPrefixIfNoParameterPrefixIsProvided() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetStringAsync("http://localhost/BindAttribute/" + "TypePrefixIsNeverUsed" + "?Value=someValue"); // Assert Assert.Equal("someValue", response); } [Fact] public async Task BindAttribute_DoesNotFallBackOnEmptyPrefixIfParameterPrefixIsProvided() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetAsync("http://localhost/BindAttribute/" + "BindParameterUsingParameterPrefix" + "?Value=someValue"); // Assert Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); Assert.Equal(string.Empty, await response.Content.ReadAsStringAsync()); } [Fact] public async Task TryUpdateModel_IncludeTopLevelProperty_IncludesAllSubProperties() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetStringAsync("http://localhost/TryUpdateModel/" + "GetUserAsync_IncludesAllSubProperties" + "?id=123&Key=34&RegistrationMonth=March&Address.Street=123&Address.Country.Name=USA&" + "Address.State=WA&Address.Country.Cities[0].CityName=Seattle&Address.Country.Cities[0].CityCode=SEA"); // Assert var user = JsonConvert.DeserializeObject(response); // Should update everything under Address. Assert.Equal(123, user.Address.Street); // Included by default as sub properties are included. Assert.Equal("WA", user.Address.State); // Included by default as sub properties of address are included. Assert.Equal("USA", user.Address.Country.Name); // Included by default. Assert.Equal("Seattle", user.Address.Country.Cities[0].CityName); // Included by default. Assert.Equal("SEA", user.Address.Country.Cities[0].CityCode); // Included by default. // Should not update Any property at the same level as address. // Key is id + 20. Assert.Equal(143, user.Key); Assert.Null(user.RegisterationMonth); } [Fact] public async Task TryUpdateModel_ChainedPropertyExpression_Throws() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); Expression> expression = model => model.Address.Country; var expected = string.Format( "The passed expression of expression node type '{0}' is invalid." + " Only simple member access expressions for model properties are supported.", expression.Body.NodeType); // Act var response = await client.GetAsync("http://localhost/TryUpdateModel/GetUserAsync_WithChainedProperties?id=123"); // Assert var exception = response.GetServerException(); Assert.Equal(typeof(InvalidOperationException).FullName, exception.ExceptionType); Assert.Equal(expected, exception.ExceptionMessage); } [Fact] public async Task TryUpdateModel_FailsToUpdateProperties() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetStringAsync("http://localhost/TryUpdateModel/" + "TryUpdateModelFails" + "?id=123&RegisterationMonth=March&Key=123&UserName=SomeName"); // Assert var result = JsonConvert.DeserializeObject(response); // Act Assert.False(result); } [Fact] public async Task TryUpdateModel_IncludeExpression_WorksOnlyAtTopLevel() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetStringAsync("http://localhost/TryUpdateModel/" + "GetPerson" + "?Parent.Name=fatherName&Parent.Parent.Name=grandFatherName"); // Assert var person = JsonConvert.DeserializeObject(response); // Act Assert.Equal("fatherName", person.Parent.Name); // Includes this as there is data from value providers, the include filter // only works for top level objects. Assert.Equal("grandFatherName", person.Parent.Parent.Name); } [Fact] public async Task TryUpdateModel_Validates_ForTopLevelNotIncludedProperties() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetStringAsync("http://localhost/TryUpdateModel/" + "CreateAndUpdateUser" + "?RegistedeburationMonth=March"); // Assert var result = JsonConvert.DeserializeObject(response); Assert.False(result); } [Fact] public async Task TryUpdateModelExcludeSpecific_Properties() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetStringAsync("http://localhost/TryUpdateModel/" + "GetUserAsync_ExcludeSpecificProperties" + "?id=123&RegisterationMonth=March&Key=123&UserName=SomeName"); // Assert var user = JsonConvert.DeserializeObject(response); // Should not update excluded properties. Assert.NotEqual(123, user.Key); // Should Update all explicitly included properties. Assert.Equal("March", user.RegisterationMonth); Assert.Equal("SomeName", user.UserName); } [Fact] public async Task TryUpdateModelIncludeSpecific_Properties() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetStringAsync("http://localhost/TryUpdateModel/" + "GetUserAsync_IncludeSpecificProperties" + "?id=123&RegisterationMonth=March&Key=123&UserName=SomeName"); // Assert var user = JsonConvert.DeserializeObject(response); // Should not update any not explicitly mentioned properties. Assert.NotEqual("SomeName", user.UserName); Assert.NotEqual(123, user.Key); // Should Update all included properties. Assert.Equal("March", user.RegisterationMonth); } [Fact] public async Task TryUpdateModelIncludesAllProperties_ByDefault() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetStringAsync("http://localhost/TryUpdateModel/" + "GetUserAsync_IncludeAllByDefault" + "?id=123&RegisterationMonth=March&Key=123&UserName=SomeName"); // Assert var user = JsonConvert.DeserializeObject(response); // Should not update any not explicitly mentioned properties. Assert.Equal("SomeName", user.UserName); Assert.Equal(123, user.Key); // Should Update all included properties. Assert.Equal("March", user.RegisterationMonth); } [Fact] public async Task UpdateVehicle_WithJson_ProducesModelStateErrors() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var content = new { Year = 3012, InspectedDates = new[] { new DateTime(4065, 10, 10) }, Make = "Volttrax", Model = "Epsum" }; // Act var response = await client.PutAsJsonAsync("http://localhost/api/vehicles/520", content); // Assert Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); var body = await response.Content.ReadAsStringAsync(); var modelStateErrors = JsonConvert.DeserializeObject>>(body); Assert.Equal(2, modelStateErrors.Count); Assert.Equal(new[] { "The field Year must be between 1980 and 2034.", "Year is invalid" }, modelStateErrors["model.Year"]); var vinError = Assert.Single(modelStateErrors["model.Vin"]); Assert.Equal("The Vin field is required.", vinError); } [Fact] public async Task UpdateVehicle_WithJson_DoesPropertyValidationPriorToValidationAtType() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var content = new { Year = 2007, InspectedDates = new[] { new DateTime(4065, 10, 10) }, Make = "Volttrax", Model = "Epsum", Vin = "Pqrs" }; client.DefaultRequestHeaders.TryAddWithoutValidation("X-TrackingId", "trackingid"); // Act var response = await client.PutAsJsonAsync("http://localhost/api/vehicles/520", content); // Assert Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); var body = await response.Content.ReadAsStringAsync(); var modelStateErrors = JsonConvert.DeserializeObject>>(body); var item = Assert.Single(modelStateErrors); Assert.Equal("model.InspectedDates", item.Key); var error = Assert.Single(item.Value); Assert.Equal("Inspection date cannot be later than year of manufacture.", error); } [Fact] public async Task UpdateVehicle_WithJson_BindsBodyAndServices() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var trackingId = Guid.NewGuid().ToString(); var postedContent = new { Year = 2010, InspectedDates = new List { new DateTime(2008, 10, 01), new DateTime(2009, 03, 01), }, Make = "Volttrax", Model = "Epsum", Vin = "PQRS" }; client.DefaultRequestHeaders.TryAddWithoutValidation("X-TrackingId", trackingId); // Act var response = await client.PutAsJsonAsync("http://localhost/api/vehicles/520", postedContent); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadAsStringAsync(); var actual = JsonConvert.DeserializeObject(body); Assert.Equal(postedContent.Vin, actual.Vin); Assert.Equal(postedContent.Make, actual.Make); Assert.Equal(postedContent.InspectedDates, actual.InspectedDates.Select(d => d.DateTime)); Assert.Equal(trackingId, actual.LastUpdatedTrackingId); } #if ASPNET50 [Fact] public async Task UpdateVehicle_WithXml_BindsBodyServicesAndHeaders() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var trackingId = Guid.NewGuid().ToString(); var postedContent = new VehicleViewModel { Year = 2010, InspectedDates = new DateTimeOffset[] { new DateTimeOffset(2008, 10, 01, 8, 3, 1, TimeSpan.Zero), new DateTime(2009, 03, 01), }, Make = "Volttrax", Model = "Epsum", Vin = "PQRS" }; client.DefaultRequestHeaders.TryAddWithoutValidation("X-TrackingId", trackingId); // Act var response = await client.PutAsXmlAsync("http://localhost/api/vehicles/520", postedContent); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadAsStringAsync(); var actual = JsonConvert.DeserializeObject(body); Assert.Equal(postedContent.Vin, actual.Vin); Assert.Equal(postedContent.Make, actual.Make); Assert.Equal(postedContent.InspectedDates, actual.InspectedDates); Assert.Equal(trackingId, actual.LastUpdatedTrackingId); } #endif // Simulates a browser based client that does a Ajax post for partial page updates. [Fact] public async Task UpdateDealerVehicle_PopulatesPropertyErrorsInViews() { // Arrange var expectedContent = await GetType().GetTypeInfo().Assembly.ReadResourceAsStringAsync( "compiler/resources/UpdateDealerVehicle_PopulatesPropertyErrorsInViews.txt"); var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var postedContent = new { Year = 9001, InspectedDates = new List { new DateTime(2008, 01, 01) }, Make = "Acme", Model = "Epsum", Vin = "LongerThan8Chars", }; var url = "http://localhost/dealers/32/update-vehicle?dealer.name=TestCarDealer&dealer.location=SE"; // Act var response = await client.PostAsJsonAsync(url, postedContent); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadAsStringAsync(); Assert.Equal(expectedContent, body); } [Fact] public async Task UpdateDealerVehicle_PopulatesValidationSummary() { // Arrange var expectedContent = await GetType().GetTypeInfo().Assembly.ReadResourceAsStringAsync( "compiler/resources/UpdateDealerVehicle_PopulatesValidationSummary.txt"); var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var postedContent = new { Year = 2013, InspectedDates = new List { new DateTime(2008, 01, 01) }, Make = "Acme", Model = "Epsum", Vin = "8chars", }; var url = "http://localhost/dealers/43/update-vehicle?dealer.name=TestCarDealer&dealer.location=SE"; // Act var response = await client.PostAsJsonAsync(url, postedContent); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadAsStringAsync(); Assert.Equal(expectedContent, body); } [Fact] public async Task UpdateDealerVehicle_UsesDefaultValuesForOptionalProperties() { // Arrange var expectedContent = await GetType().GetTypeInfo().Assembly.ReadResourceAsStringAsync( "compiler/resources/UpdateDealerVehicle_UpdateSuccessful.txt"); var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var postedContent = new { Year = 2013, InspectedDates = new DateTimeOffset[] { new DateTime(2008, 11, 01) }, Make = "RealSlowCars", Model = "Epsum", Vin = "8chars", }; var url = "http://localhost/dealers/43/update-vehicle?dealer.name=TestCarDealer&dealer.location=NE"; // Act var response = await client.PostAsJsonAsync(url, postedContent); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadAsStringAsync(); Assert.Equal(expectedContent, body); } [Fact] public async Task FormFileModelBinder_CanBind_SingleFile() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var url = "http://localhost/FileUpload/UploadSingle"; var formData = new MultipartFormDataContent("Upload----"); formData.Add(new StringContent("Test Content"), "file", "test.txt"); // Act var response = await client.PostAsync(url, formData); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var fileDetails = JsonConvert.DeserializeObject( await response.Content.ReadAsStringAsync()); Assert.Equal("test.txt", fileDetails.Filename); Assert.Equal("Test Content", fileDetails.Content); } [Fact] public async Task FormFileModelBinder_CanBind_MultipleFiles() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var url = "http://localhost/FileUpload/UploadMultiple"; var formData = new MultipartFormDataContent("Upload----"); formData.Add(new StringContent("Test Content 1"), "files", "test1.txt"); formData.Add(new StringContent("Test Content 2"), "files", "test2.txt"); // Act var response = await client.PostAsync(url, formData); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var fileDetailsArray = JsonConvert.DeserializeObject( await response.Content.ReadAsStringAsync()); Assert.Equal(2, fileDetailsArray.Length); Assert.Equal("test1.txt", fileDetailsArray[0].Filename); Assert.Equal("Test Content 1", fileDetailsArray[0].Content); Assert.Equal("test2.txt", fileDetailsArray[1].Filename); Assert.Equal("Test Content 2", fileDetailsArray[1].Content); } [Fact] public async Task FormFileModelBinder_CanBind_MultipleListOfFiles() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var url = "http://localhost/FileUpload/UploadMultipleList"; var formData = new MultipartFormDataContent("Upload----"); formData.Add(new StringContent("Test Content 1"), "filelist1", "test1.txt"); formData.Add(new StringContent("Test Content 2"), "filelist1", "test2.txt"); formData.Add(new StringContent("Test Content 3"), "filelist2", "test3.txt"); formData.Add(new StringContent("Test Content 4"), "filelist2", "test4.txt"); // Act var response = await client.PostAsync(url, formData); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var fileDetailsLookup = JsonConvert.DeserializeObject>>( await response.Content.ReadAsStringAsync()); Assert.Equal(2, fileDetailsLookup.Count); var fileDetailsList1 = fileDetailsLookup["filelist1"]; var fileDetailsList2 = fileDetailsLookup["filelist2"]; Assert.Equal(2, fileDetailsList1.Count); Assert.Equal(2, fileDetailsList2.Count); Assert.Equal("test1.txt", fileDetailsList1[0].Filename); Assert.Equal("Test Content 1", fileDetailsList1[0].Content); Assert.Equal("test2.txt", fileDetailsList1[1].Filename); Assert.Equal("Test Content 2", fileDetailsList1[1].Content); Assert.Equal("test3.txt", fileDetailsList2[0].Filename); Assert.Equal("Test Content 3", fileDetailsList2[0].Content); Assert.Equal("test4.txt", fileDetailsList2[1].Filename); Assert.Equal("Test Content 4", fileDetailsList2[1].Content); } [Fact] public async Task FormFileModelBinder_CanBind_FileInsideModel() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var url = "http://localhost/FileUpload/UploadModelWithFile"; var formData = new MultipartFormDataContent("Upload----"); formData.Add(new StringContent("Test Book"), "Name"); formData.Add(new StringContent("Test Content"), "File", "test.txt"); // Act var response = await client.PostAsync(url, formData); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var book = JsonConvert.DeserializeObject>( await response.Content.ReadAsStringAsync()); var bookName = book.Key; var fileDetails = book.Value; Assert.Equal("Test Book", bookName); Assert.Equal("test.txt", fileDetails.Filename); Assert.Equal("Test Content", fileDetails.Content); } [Fact] public async Task TryUpdateModel_ReturnDerivedAndBaseProperties() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetStringAsync("http://localhost/TryUpdateModel/" + "GetEmployeeAsync_BindToBaseDeclaredType" + "?Parent.Name=fatherName&Parent.Parent.Name=grandFatherName&Department=Sales"); // Assert var employee = JsonConvert.DeserializeObject(response); Assert.Equal("fatherName", employee.Parent.Name); Assert.Equal("Sales", employee.Department); // Round-tripped value includes descendent instances for all properties with data in the request. Assert.Equal("grandFatherName", employee.Parent.Parent.Name); } [Fact] public async Task HtmlHelper_DisplayFor_ShowsPropertiesInModelMetadataOrder() { // Arrange var expectedContent = await GetType().GetTypeInfo().Assembly.ReadResourceAsStringAsync( "compiler/resources/ModelBindingWebSite.Vehicle.Details.html"); var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var url = "http://localhost/vehicles/42"; // Act var response = await client.GetAsync(url); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadAsStringAsync(); Assert.Equal(expectedContent, body); } [Fact] public async Task HtmlHelper_EditorFor_ShowsPropertiesInModelMetadataOrder() { // Arrange var expectedContent = await GetType().GetTypeInfo().Assembly.ReadResourceAsStringAsync( "compiler/resources/ModelBindingWebSite.Vehicle.Edit.html"); var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var url = "http://localhost/vehicles/42/edit"; // Act var response = await client.GetAsync(url); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadAsStringAsync(); Assert.Equal(expectedContent, body); } [Fact] public async Task HtmlHelper_EditorFor_ShowsPropertiesAndErrorsInModelMetadataOrder() { // Arrange var expectedContent = await GetType().GetTypeInfo().Assembly.ReadResourceAsStringAsync( "compiler/resources/ModelBindingWebSite.Vehicle.Edit.Invalid.html"); var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var url = "http://localhost/vehicles/42/edit"; var contentDictionary = new Dictionary { { "Make", "Fast Cars" }, { "Model", "the Fastener" }, { "InspectedDates[0]", "14/10/1979 00:00:00 -08:00" }, { "InspectedDates[1]", "16/10/1979 00:00:00 -08:00" }, { "InspectedDates[2]", "02/11/1979 00:00:00 -08:00" }, { "InspectedDates[3]", "13/11/1979 00:00:00 -08:00" }, { "InspectedDates[4]", "05/12/1979 00:00:00 -08:00" }, { "InspectedDates[5]", "12/12/1979 00:00:00 -08:00" }, { "InspectedDates[6]", "19/12/1979 00:00:00 -08:00" }, { "InspectedDates[7]", "26/12/1979 00:00:00 -08:00" }, { "InspectedDates[8]", "28/12/1979 00:00:00 -08:00" }, { "InspectedDates[9]", "29/12/1979 00:00:00 -08:00" }, { "InspectedDates[10]", "01/04/1980 00:00:00 -08:00" }, { "Vin", "8765432112345678" }, { "Year", "1979" }, }; var requestContent = new FormUrlEncodedContent(contentDictionary); // Act var response = await client.PostAsync(url, requestContent); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadAsStringAsync(); Assert.Equal(expectedContent, body); } [Fact] public async Task ModelBinder_FormatsDontMatch_ThrowsUserFriendlyException() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var url = "http://localhost/Home/GetErrorMessage"; var nameValueCollection = new List> { new KeyValuePair("birthdate", "random string"), }; var formData = new FormUrlEncodedContent(nameValueCollection); // Act var response = await client.PostAsync(url, formData); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var result = await response.Content.ReadAsStringAsync(); Assert.Equal("The value 'random string' is not valid for birthdate.", result); } [Fact] public async Task OverriddenMetadataProvider_CanChangeAdditionalValues() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var url = "http://localhost/AdditionalValues"; var expectedDictionary = new Dictionary { { "key1", "7d6d0de2-8d59-49ac-99cc-881423b75a76" }, { "key2", "value2" }, }; // Act var response = await client.GetAsync(url); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var responseContent = await response.Content.ReadAsStringAsync(); var dictionary = JsonConvert.DeserializeObject>(responseContent); Assert.Equal(expectedDictionary, dictionary); } [Fact] public async Task OverriddenMetadataProvider_CanUseAttributesToChangeAdditionalValues() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var url = "http://localhost/GroupNames"; var expectedDictionary = new Dictionary { { "Model", "MakeAndModelGroup" }, { "Make", "MakeAndModelGroup" }, { "Vin", null }, { "Year", null }, { "InspectedDates", null }, { "LastUpdatedTrackingId", "TrackingIdGroup" }, }; // Act var response = await client.GetAsync(url); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var responseContent = await response.Content.ReadAsStringAsync(); var dictionary = JsonConvert.DeserializeObject>(responseContent); Assert.Equal(expectedDictionary, dictionary); } [Fact] public async Task TryUpdateModelNonGeneric_IncludesAllProperties_CanBind() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetStringAsync("http://localhost/TryUpdateModel/" + "GetUserAsync_ModelType_IncludeAll" + "?id=123&RegisterationMonth=March&Key=123&UserName=SomeName"); // Assert var user = JsonConvert.DeserializeObject(response); // Should not update any not explicitly mentioned properties. Assert.Equal("SomeName", user.UserName); Assert.Equal(123, user.Key); // Should Update all included properties. Assert.Equal("March", user.RegisterationMonth); } [Fact] public async Task FormCollectionModelBinder_CanBind_FormValues() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var url = "http://localhost/FormCollection/ReturnValuesAsList"; var nameValueCollection = new List> { new KeyValuePair("field1", "value1"), new KeyValuePair("field2", "value2"), }; var formData = new FormUrlEncodedContent(nameValueCollection); // Act var response = await client.PostAsync(url, formData); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var valuesList = JsonConvert.DeserializeObject>( await response.Content.ReadAsStringAsync()); Assert.Equal(new List { "value1", "value2" }, valuesList); } [Fact] public async Task FormCollectionModelBinder_CanBind_FormValuesWithDuplicateKeys() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var url = "http://localhost/FormCollection/ReturnValuesAsList"; var nameValueCollection = new List> { new KeyValuePair("field1", "value1"), new KeyValuePair("field2", "value2"), new KeyValuePair("field1", "value3"), }; var formData = new FormUrlEncodedContent(nameValueCollection); // Act var response = await client.PostAsync(url, formData); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var valuesList = JsonConvert.DeserializeObject>( await response.Content.ReadAsStringAsync()); Assert.Equal(new List { "value1,value3", "value2" }, valuesList); } [Fact] public async Task FormCollectionModelBinder_CannotBind_NonFormValues() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var url = "http://localhost/FormCollection/ReturnCollectionCount"; var data = new StringContent("Non form content"); // Act var response = await client.PostAsync(url, data); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var collectionCount = JsonConvert.DeserializeObject( await response.Content.ReadAsStringAsync()); Assert.Equal(0, collectionCount); } [Fact] public async Task FormCollectionModelBinder_CanBind_FormWithFile() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var url = "http://localhost/FormCollection/ReturnFileContent"; var expectedContent = "Test Content"; var formData = new MultipartFormDataContent("Upload----"); formData.Add(new StringContent(expectedContent), "File", "test.txt"); // Act var response = await client.PostAsync(url, formData); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var fileContent = await response.Content.ReadAsStringAsync(); Assert.Equal(expectedContent, fileContent); } [Fact] public async Task TryUpdateModelNonGenericIncludesAllProperties_ByDefault() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetStringAsync("http://localhost/TryUpdateModel/" + "GetUserAsync_ModelType_IncludeAllByDefault" + "?id=123&RegisterationMonth=March&Key=123&UserName=SomeName"); // Assert var user = JsonConvert.DeserializeObject(response); // Should not update any not explicitly mentioned properties. Assert.Equal("SomeName", user.UserName); Assert.Equal(123, user.Key); // Should Update all included properties. Assert.Equal("March", user.RegisterationMonth); } public static TheoryData ModelStateHasErrorsForValueAndReferenceTypesData { get { return new TheoryData() { { "{}", new[] { ":Required property 'Id' not found in JSON", "rectangle.Lines:The Lines field is required." } }, { "{\"Id\":10}", new[] { "rectangle.Lines:The Lines field is required." } }, { "{\"Id\":10,\"Lines\":[{}]}", new [] { "Lines[0]:Required property 'Start' not found in JSON", "Lines[0]:Required property 'End' not found in JSON" } }, { "{\"Id\":10,\"Lines\":[{\"Start\":{\"X\":10,\"Y\":10},\"End\":{\"X\":10}}]}", new [] { "Lines[0].End:Required property 'Y' not found in JSON" } } }; } } [Theory] [MemberData(nameof(ModelStateHasErrorsForValueAndReferenceTypesData))] public async Task ModelState_HasErrors_ForValueAndReferenceTypes( string input, IEnumerable expectedModelStateErrorMessages) { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var content = new StringContent(input, Encoding.UTF8, "text/json"); // Act var response = await client.PostAsync( "http://localhost/Validation/CreateRectangle", content); // Assert var data = await response.Content.ReadAsStringAsync(); var actualModelStateErrorMessages = JsonConvert.DeserializeObject>(data); Assert.NotNull(actualModelStateErrorMessages); Assert.Equal(expectedModelStateErrorMessages.Count(), actualModelStateErrorMessages.Count()); foreach (var expectedErrorMessage in expectedModelStateErrorMessages) { Assert.Contains( actualModelStateErrorMessages, (actualErrorMessage) => actualErrorMessage.StartsWith(expectedErrorMessage)); } } [Fact] public async Task BindModelAsync_WithCollection() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var content = new Dictionary { { "AddressLines[0].Line", "Street Address 0" }, { "AddressLines[1].Line", "Street Address 1" }, { "ZipCode", "98052" }, }; var url = "http://localhost/Person_CollectionBinder/CollectionType"; var formData = new FormUrlEncodedContent(content); // Act var response = await client.PutAsync(url, formData); // Assert var address = await ReadValue(response); Assert.Equal(2, address.AddressLines.Count); Assert.Equal("Street Address 0", address.AddressLines[0].Line); Assert.Equal("Street Address 1", address.AddressLines[1].Line); Assert.Equal("98052", address.ZipCode); } [Fact] public async Task BindModelAsync_WithCollection_SpecifyingIndex() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var content = new[] { new KeyValuePair("AddressLines.index", "3"), new KeyValuePair("AddressLines.index", "10000"), new KeyValuePair("AddressLines[3].Line", "Street Address 0"), new KeyValuePair("AddressLines[10000].Line", "Street Address 1"), }; var url = "http://localhost/Person_CollectionBinder/CollectionType"; var formData = new FormUrlEncodedContent(content); // Act var response = await client.PutAsync(url, formData); // Assert var address = await ReadValue(response); Assert.Equal(2, address.AddressLines.Count); Assert.Equal("Street Address 0", address.AddressLines[0].Line); Assert.Equal("Street Address 1", address.AddressLines[1].Line); } [Fact] public async Task BindModelAsync_WithNestedCollection() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var content = new Dictionary { { "Addresses[0].AddressLines[0].Line", "Street Address 00" }, { "Addresses[0].AddressLines[1].Line", "Street Address 01" }, { "Addresses[0].ZipCode", "98052" }, { "Addresses[1].AddressLines[0].Line", "Street Address 10" }, { "Addresses[1].AddressLines[3].Line", "Street Address 13" }, }; var url = "http://localhost/Person_CollectionBinder/NestedCollectionType"; var formData = new FormUrlEncodedContent(content); // Act var response = await client.PostAsync(url, formData); // Assert var result = await ReadValue(response); Assert.Equal(2, result.Addresses.Count); var address = result.Addresses[0]; Assert.Equal(2, address.AddressLines.Count); Assert.Equal("Street Address 00", address.AddressLines[0].Line); Assert.Equal("Street Address 01", address.AddressLines[1].Line); Assert.Equal("98052", address.ZipCode); address = result.Addresses[1]; Assert.Single(address.AddressLines); Assert.Equal("Street Address 10", address.AddressLines[0].Line); Assert.Null(address.ZipCode); } [Fact] public async Task BindModelAsync_WithIncorrectlyFormattedNestedCollectionValue() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var content = new Dictionary { { "Addresses", "Street Address 00" }, }; var url = "http://localhost/Person_CollectionBinder/NestedCollectionType"; var formData = new FormUrlEncodedContent(content); // Act var response = await client.PostAsync(url, formData); // Assert var result = await ReadValue(response); var address = Assert.Single(result.Addresses); Assert.Null(address.AddressLines); Assert.Null(address.ZipCode); } [Fact] public async Task BindModelAsync_WithNestedCollectionContainingRecursiveRelation() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var content = new Dictionary { { "People[0].Name", "Person 0" }, { "People[0].Parent.Name", "Person 0 Parent" }, { "People[1].Parent.Name", "Person 1 Parent" }, { "People[2].Parent", "Person 2 Parent" }, { "People[1000].Name", "Person 1000 Parent" }, }; var url = "http://localhost/Person_CollectionBinder/NestedCollectionOfRecursiveTypes"; var formData = new FormUrlEncodedContent(content); // Act var response = await client.PostAsync(url, formData); // Assert var result = await ReadValue(response); Assert.Equal(3, result.People.Count); var person = result.People[0]; Assert.Equal("Person 0", person.Name); Assert.Equal("Person 0 Parent", person.Parent.Name); Assert.Null(person.Parent.Parent); person = result.People[1]; Assert.Equal("Person 1 Parent", person.Parent.Name); Assert.Null(person.Parent.Parent); person = result.People[2]; Assert.Null(person.Name); Assert.Null(person.Parent); } [Fact] public async Task BindModelAsync_WithNestedCollectionContainingRecursiveRelation_WithMalformedValue() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var content = new Dictionary { { "People", "Person 0" }, }; var url = "http://localhost/Person_CollectionBinder/NestedCollectionOfRecursiveTypes"; var formData = new FormUrlEncodedContent(content); // Act var response = await client.PostAsync(url, formData); // Assert var result = await ReadValue(response); var person = Assert.Single(result.People); Assert.Null(person.Name); Assert.Null(person.Parent); } [Theory] [InlineData("true", "false", true)] [InlineData("false", "true", false)] public async Task BindModelAsync_MultipleCheckBoxesWithSameKey_BindsFirstValue(string firstValue, string secondValue, bool expectedResult) { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var content = new List> { new KeyValuePair("isValid", firstValue), new KeyValuePair("isValid", secondValue), }; var url = "http://localhost/Person_CollectionBinder/PostCheckBox"; var formData = new FormUrlEncodedContent(content); // Act var response = await client.PostAsync(url, formData); // Assert var result = await ReadValue(response); Assert.Equal(expectedResult, result); } [Fact] public async Task BindModelAsync_CheckBoxesList_BindSuccessful() { // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); var content = new List> { new KeyValuePair("userPreferences[0].Id", "1"), new KeyValuePair("userPreferences[0].Checked", "true"), new KeyValuePair("userPreferences[1].Id", "2"), new KeyValuePair("userPreferences[1].Checked", "false"), }; var url = "http://localhost/Person_CollectionBinder/PostCheckBoxList"; var formData = new FormUrlEncodedContent(content); // Act var response = await client.PostAsync(url, formData); // Assert var result = await ReadValue>(response); Assert.True(result[0].Checked); Assert.False(result[1].Checked); } private async Task ReadValue(HttpResponseMessage response) { Assert.True(response.IsSuccessStatusCode); return JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); } } }