ModelBinding: Remove IsReadOnly checks and add/update tests
This commit is contained in:
parent
be5deef584
commit
ac98417398
|
|
@ -18,9 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
// We don't support binding readonly properties of arrays because we can't resize the
|
||||
// existing value.
|
||||
if (context.Metadata.ModelType.IsArray && !context.Metadata.IsReadOnly)
|
||||
if (context.Metadata.ModelType.IsArray)
|
||||
{
|
||||
var elementType = context.Metadata.ElementMetadata.ModelType;
|
||||
var elementBinder = context.CreateBinder(context.Metadata.ElementMetadata);
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
}
|
||||
|
||||
var modelType = context.Metadata.ModelType;
|
||||
|
||||
|
||||
// Arrays are handled by another binder.
|
||||
if (modelType.IsArray)
|
||||
{
|
||||
|
|
@ -43,10 +43,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
// If the model type is IEnumerable<> then we need to know if we can assign a List<> to it, since
|
||||
// that's what we would create. (The cases handled here are IEnumerable<>, IReadOnlyColection<> and
|
||||
// IReadOnlyList<>).
|
||||
//
|
||||
// We need to check IsReadOnly because we need to know if we can SET the property.
|
||||
var enumerableType = ClosedGenericMatcher.ExtractGenericInterface(modelType, typeof(IEnumerable<>));
|
||||
if (enumerableType != null && !context.Metadata.IsReadOnly)
|
||||
if (enumerableType != null)
|
||||
{
|
||||
var listType = typeof(List<>).MakeGenericType(enumerableType.GenericTypeArguments);
|
||||
if (modelType.GetTypeInfo().IsAssignableFrom(listType.GetTypeInfo()))
|
||||
|
|
|
|||
|
|
@ -29,9 +29,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
throw new ArgumentNullException(nameof(bindingContext));
|
||||
}
|
||||
|
||||
var createFileCollection =
|
||||
bindingContext.ModelType == typeof(IFormFileCollection) &&
|
||||
!bindingContext.ModelMetadata.IsReadOnly;
|
||||
var createFileCollection = bindingContext.ModelType == typeof(IFormFileCollection);
|
||||
if (!createFileCollection && !ModelBindingHelper.CanGetCompatibleCollection<IFormFile>(bindingContext))
|
||||
{
|
||||
// Silently fail if unable to create an instance or use the current instance.
|
||||
|
|
|
|||
|
|
@ -509,19 +509,19 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Internal
|
|||
{
|
||||
var model = bindingContext.Model;
|
||||
var modelType = bindingContext.ModelType;
|
||||
var writeable = !bindingContext.ModelMetadata.IsReadOnly;
|
||||
|
||||
if (typeof(T).IsAssignableFrom(modelType))
|
||||
{
|
||||
// Scalar case. Existing model is not relevant and property must always be set. Will use a List<T>
|
||||
// intermediate and set property to first element, if any.
|
||||
return writeable;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (modelType == typeof(T[]))
|
||||
{
|
||||
// Can't change the length of an existing array or replace it. Will use a List<T> intermediate and set
|
||||
// property to an array created from that.
|
||||
return writeable;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!typeof(IEnumerable<T>).IsAssignableFrom(modelType))
|
||||
|
|
@ -542,12 +542,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Internal
|
|||
// public IEnumerable<T> Property { get; set; } = new T[0];
|
||||
if (modelType.IsAssignableFrom(typeof(List<T>)))
|
||||
{
|
||||
return writeable;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Will we be able to activate an instance and use that?
|
||||
return writeable &&
|
||||
modelType.GetTypeInfo().IsClass &&
|
||||
return modelType.GetTypeInfo().IsClass &&
|
||||
!modelType.GetTypeInfo().IsAbstract &&
|
||||
typeof(ICollection<T>).IsAssignableFrom(modelType);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
[InlineData(typeof(IList<int>))]
|
||||
[InlineData(typeof(List<int>))]
|
||||
[InlineData(typeof(Collection<int>))]
|
||||
[InlineData(typeof(IEnumerable<int>))]
|
||||
[InlineData(typeof(IReadOnlyCollection<int>))]
|
||||
[InlineData(typeof(IReadOnlyList<int>))]
|
||||
public void Create_ForSupportedTypes_ReturnsBinder(Type modelType)
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -66,46 +69,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
Assert.IsType<CollectionModelBinder<int>>(result);
|
||||
}
|
||||
|
||||
// These aren't ICollection<> - we can handle them by creating a List<> - but in this case
|
||||
// we can't set the property so we can't bind.
|
||||
[Theory]
|
||||
[InlineData(nameof(ReadOnlyProperties.Enumerable))]
|
||||
[InlineData(nameof(ReadOnlyProperties.ReadOnlyCollection))]
|
||||
[InlineData(nameof(ReadOnlyProperties.ReadOnlyList))]
|
||||
public void Create_ForNonICollectionTypes_ReadOnlyProperty_ReturnsNull(string propertyName)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new CollectionModelBinderProvider();
|
||||
|
||||
var metadataProvider = TestModelBinderProviderContext.CachedMetadataProvider;
|
||||
|
||||
var metadata = metadataProvider.GetMetadataForProperty(typeof(ReadOnlyProperties), propertyName);
|
||||
Assert.NotNull(metadata);
|
||||
Assert.True(metadata.IsReadOnly);
|
||||
|
||||
var context = new TestModelBinderProviderContext(metadata, bindingInfo: null);
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
private class Person
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public int Age { get; set; }
|
||||
}
|
||||
|
||||
private class ReadOnlyProperties
|
||||
{
|
||||
public IEnumerable<int> Enumerable { get; }
|
||||
|
||||
public IReadOnlyCollection<int> ReadOnlyCollection { get; }
|
||||
|
||||
public IReadOnlyList<int> ReadOnlyList { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FormFileModelBinder_ReturnsFailedResult_ForReadOnlyDestination()
|
||||
public async Task FormFileModelBinder_ReturnsResult_ForReadOnlyDestination()
|
||||
{
|
||||
// Arrange
|
||||
var binder = new FormFileModelBinder();
|
||||
|
|
@ -217,8 +217,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
await binder.BindModelAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.False(bindingContext.Result.IsModelSet);
|
||||
Assert.Null(bindingContext.Result.Model);
|
||||
Assert.True(bindingContext.Result.IsModelSet);
|
||||
Assert.NotNull(bindingContext.Result.Model);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HeaderBinder_ReturnsFailedResult_ForReadOnlyDestination()
|
||||
public async Task HeaderBinder_ReturnsResult_ForReadOnlyDestination()
|
||||
{
|
||||
// Arrange
|
||||
var header = "Accept";
|
||||
|
|
@ -116,8 +116,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
await binder.BindModelAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.False(bindingContext.Result.IsModelSet);
|
||||
Assert.Null(bindingContext.Result.Model);
|
||||
Assert.True(bindingContext.Result.IsModelSet);
|
||||
Assert.NotNull(bindingContext.Result.Model);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -1412,7 +1412,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
[InlineData(nameof(ModelWithReadOnlyAndSpecialCaseProperties.ListProperty))]
|
||||
[InlineData(nameof(ModelWithReadOnlyAndSpecialCaseProperties.ScalarProperty))]
|
||||
[InlineData(nameof(ModelWithReadOnlyAndSpecialCaseProperties.ScalarPropertyWithValue))]
|
||||
public void CanGetCompatibleCollection_ReturnsFalse_IfReadOnly(string propertyName)
|
||||
public void CanGetCompatibleCollection_ReturnsTrue_IfReadOnly(string propertyName)
|
||||
{
|
||||
// Arrange
|
||||
var bindingContext = GetBindingContextForProperty(propertyName);
|
||||
|
|
@ -1421,7 +1421,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var result = ModelBindingHelper.CanGetCompatibleCollection<int>(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -41,11 +42,50 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
Assert.Equal("John's biography content", user.Biography);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UploadMultipleFiles()
|
||||
{
|
||||
// Arrange
|
||||
var content = new MultipartFormDataContent();
|
||||
content.Add(new StringContent("Phone"), "Name");
|
||||
content.Add(new StringContent("camera"), "Specs[0].Key");
|
||||
content.Add(new StringContent("camera spec1 file contents"), "Specs[0].Value", "camera_spec1.txt");
|
||||
content.Add(new StringContent("camera spec2 file contents"), "Specs[0].Value", "camera_spec2.txt");
|
||||
content.Add(new StringContent("battery"), "Specs[1].Key");
|
||||
content.Add(new StringContent("battery spec1 file contents"), "Specs[1].Value", "battery_spec1.txt");
|
||||
content.Add(new StringContent("battery spec2 file contents"), "Specs[1].Value", "battery_spec2.txt");
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/UploadProductSpecs");
|
||||
request.Content = content;
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var product = await response.Content.ReadAsAsync<Product>();
|
||||
Assert.NotNull(product);
|
||||
Assert.Equal("Phone", product.Name);
|
||||
Assert.NotNull(product.Specs);
|
||||
Assert.Equal(2, product.Specs.Count);
|
||||
Assert.True(product.Specs.ContainsKey("camera"));
|
||||
Assert.Equal(new[] { "camera_spec1.txt", "camera_spec2.txt" }, product.Specs["camera"]);
|
||||
Assert.True(product.Specs.ContainsKey("battery"));
|
||||
Assert.Equal(new[] { "battery_spec1.txt", "battery_spec2.txt" }, product.Specs["battery"]);
|
||||
}
|
||||
|
||||
private class User
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public int Age { get; set; }
|
||||
public string Biography { get; set; }
|
||||
}
|
||||
|
||||
private class Product
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public Dictionary<string, List<string>> Specs { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -327,5 +327,47 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
Assert.Equal(0, modelState.ErrorCount);
|
||||
Assert.True(modelState.IsValid);
|
||||
}
|
||||
|
||||
private class PersonWithReadOnlyAndInitializedProperty
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public string[] Aliases { get; } = new[] { "Alias1", "Alias2" };
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ArrayModelBinder_BindsArrayOfComplexTypeHavingInitializedData_WithPrefix_Success_ReadOnly()
|
||||
{
|
||||
// Arrange
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
var parameter = new ParameterDescriptor()
|
||||
{
|
||||
Name = "parameter",
|
||||
ParameterType = typeof(PersonWithReadOnlyAndInitializedProperty)
|
||||
};
|
||||
|
||||
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||
{
|
||||
request.QueryString = new QueryString("?parameter.Name=James¶meter.Aliases[0]=bill¶meter.Aliases[1]=william");
|
||||
});
|
||||
|
||||
var modelState = testContext.ModelState;
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, testContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(modelBindingResult.IsModelSet);
|
||||
|
||||
Assert.True(modelState.IsValid);
|
||||
|
||||
var model = Assert.IsType<PersonWithReadOnlyAndInitializedProperty>(modelBindingResult.Model);
|
||||
Assert.Equal("James", model.Name);
|
||||
Assert.NotNull(model.Aliases);
|
||||
Assert.Collection(
|
||||
model.Aliases,
|
||||
(e) => Assert.Equal("Alias1", e),
|
||||
(e) => Assert.Equal("Alias2", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -743,6 +743,16 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
},
|
||||
typeof(List<string>)
|
||||
},
|
||||
{
|
||||
typeof(IReadOnlyCollection<string>),
|
||||
new Dictionary<string, StringValues>
|
||||
{
|
||||
{ "index", new[] { "low", "high" } },
|
||||
{ "[low]", new[] { "hello" } },
|
||||
{ "[high]", new[] { "world" } },
|
||||
},
|
||||
typeof(List<string>)
|
||||
},
|
||||
{
|
||||
typeof(IList<string>),
|
||||
new Dictionary<string, StringValues>
|
||||
|
|
@ -752,6 +762,15 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
},
|
||||
typeof(List<string>)
|
||||
},
|
||||
{
|
||||
typeof(IReadOnlyList<string>),
|
||||
new Dictionary<string, StringValues>
|
||||
{
|
||||
{ "[0]", new[] { "hello" } },
|
||||
{ "[1]", new[] { "world" } },
|
||||
},
|
||||
typeof(List<string>)
|
||||
},
|
||||
{
|
||||
typeof(List<string>),
|
||||
new Dictionary<string, StringValues>
|
||||
|
|
|
|||
|
|
@ -299,6 +299,62 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
Assert.Empty(modelState.Keys);
|
||||
}
|
||||
|
||||
private class Car1
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public FormFileCollection Specs { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindProperty_WithData_WithPrefix_GetsBound()
|
||||
{
|
||||
// Arrange
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
var parameter = new ParameterDescriptor
|
||||
{
|
||||
Name = "p",
|
||||
BindingInfo = new BindingInfo(),
|
||||
ParameterType = typeof(Car1)
|
||||
};
|
||||
|
||||
var data = "Some Data Is Better Than No Data.";
|
||||
var testContext = ModelBindingTestHelper.GetTestContext(
|
||||
request =>
|
||||
{
|
||||
request.QueryString = QueryString.Create("p.Name", "Accord");
|
||||
UpdateRequest(request, data, "p.Specs");
|
||||
});
|
||||
|
||||
var modelState = testContext.ModelState;
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, testContext);
|
||||
|
||||
// Assert
|
||||
|
||||
// ModelBindingResult
|
||||
Assert.True(modelBindingResult.IsModelSet);
|
||||
|
||||
// Model
|
||||
var car = Assert.IsType<Car1>(modelBindingResult.Model);
|
||||
Assert.NotNull(car.Specs);
|
||||
var file = Assert.Single(car.Specs);
|
||||
Assert.Equal("form-data; name=p.Specs; filename=text.txt", file.ContentDisposition);
|
||||
var reader = new StreamReader(file.OpenReadStream());
|
||||
Assert.Equal(data, reader.ReadToEnd());
|
||||
|
||||
// ModelState
|
||||
Assert.True(modelState.IsValid);
|
||||
Assert.Equal(2, modelState.Count);
|
||||
|
||||
var entry = Assert.Single(modelState, e => e.Key == "p.Name").Value;
|
||||
Assert.Equal("Accord", entry.AttemptedValue);
|
||||
Assert.Equal("Accord", entry.RawValue);
|
||||
|
||||
Assert.Single(modelState, e => e.Key == "p.Specs");
|
||||
}
|
||||
|
||||
private void UpdateRequest(HttpRequest request, string data, string name)
|
||||
{
|
||||
const string fileName = "text.txt";
|
||||
|
|
|
|||
|
|
@ -498,5 +498,49 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
Assert.Equal(0, modelState.ErrorCount);
|
||||
Assert.True(modelState.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task KeyValuePairModelBinder_BindsKeyValuePairOfArray_Success()
|
||||
{
|
||||
// Arrange
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
var parameter = new ParameterDescriptor()
|
||||
{
|
||||
Name = "p",
|
||||
ParameterType = typeof(KeyValuePair<string, string[]>)
|
||||
};
|
||||
|
||||
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||
{
|
||||
request.QueryString = new QueryString("?p.Key=key1&p.Value[0]=value1&p.Value[1]=value2");
|
||||
});
|
||||
|
||||
var modelState = testContext.ModelState;
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, testContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(modelBindingResult.IsModelSet);
|
||||
var model = Assert.IsType<KeyValuePair<string, string[]>>(modelBindingResult.Model);
|
||||
Assert.Equal("key1", model.Key);
|
||||
Assert.Equal(new[] { "value1", "value2" }, model.Value);
|
||||
|
||||
Assert.Equal(3, modelState.Count);
|
||||
Assert.Equal(0, modelState.ErrorCount);
|
||||
Assert.True(modelState.IsValid);
|
||||
|
||||
var entry = Assert.Single(modelState, kvp => kvp.Key == "p.Key").Value;
|
||||
Assert.Equal("key1", entry.AttemptedValue);
|
||||
Assert.Equal("key1", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, kvp => kvp.Key == "p.Value[0]").Value;
|
||||
Assert.Equal("value1", entry.AttemptedValue);
|
||||
Assert.Equal("value1", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, kvp => kvp.Key == "p.Value[1]").Value;
|
||||
Assert.Equal("value2", entry.AttemptedValue);
|
||||
Assert.Equal("value2", entry.RawValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1122,6 +1122,328 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
Assert.True(modelState.IsValid);
|
||||
}
|
||||
|
||||
// Dictionary property with an IEnumerable<> value type
|
||||
private class Car1
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public Dictionary<string, IEnumerable<SpecDoc>> Specs { get; set; }
|
||||
}
|
||||
|
||||
// Dictionary property with an Array value type
|
||||
private class Car2
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public Dictionary<string, SpecDoc[]> Specs { get; set; }
|
||||
}
|
||||
|
||||
private class Car3
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public IEnumerable<KeyValuePair<string, IEnumerable<SpecDoc>>> Specs { get; set; }
|
||||
}
|
||||
|
||||
private class SpecDoc
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MutableObjectModelBinder_BindsDictionaryProperty_WithIEnumerableComplexTypeValue_Success()
|
||||
{
|
||||
// Arrange
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
var parameter = new ParameterDescriptor()
|
||||
{
|
||||
Name = "p",
|
||||
ParameterType = typeof(Car1)
|
||||
};
|
||||
|
||||
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||
{
|
||||
var queryString = "?p.Name=Accord"
|
||||
+ "&p.Specs[0].Key=camera_specs"
|
||||
+ "&p.Specs[0].Value[0].Name=camera_spec1.txt"
|
||||
+ "&p.Specs[0].Value[1].Name=camera_spec2.txt"
|
||||
+ "&p.Specs[1].Key=tyre_specs"
|
||||
+ "&p.Specs[1].Value[0].Name=tyre_spec1.txt"
|
||||
+ "&p.Specs[1].Value[1].Name=tyre_spec2.txt";
|
||||
request.QueryString = new QueryString(queryString);
|
||||
});
|
||||
|
||||
var modelState = testContext.ModelState;
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, testContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(modelBindingResult.IsModelSet);
|
||||
|
||||
var model = Assert.IsType<Car1>(modelBindingResult.Model);
|
||||
Assert.Equal("Accord", model.Name);
|
||||
|
||||
Assert.Collection(
|
||||
model.Specs,
|
||||
(e) =>
|
||||
{
|
||||
Assert.Equal("camera_specs", e.Key);
|
||||
Assert.Collection(
|
||||
e.Value,
|
||||
(s) =>
|
||||
{
|
||||
Assert.Equal("camera_spec1.txt", s.Name);
|
||||
},
|
||||
(s) =>
|
||||
{
|
||||
Assert.Equal("camera_spec2.txt", s.Name);
|
||||
});
|
||||
},
|
||||
(e) =>
|
||||
{
|
||||
Assert.Equal("tyre_specs", e.Key);
|
||||
Assert.Collection(
|
||||
e.Value,
|
||||
(s) =>
|
||||
{
|
||||
Assert.Equal("tyre_spec1.txt", s.Name);
|
||||
},
|
||||
(s) =>
|
||||
{
|
||||
Assert.Equal("tyre_spec2.txt", s.Name);
|
||||
});
|
||||
});
|
||||
|
||||
Assert.Equal(7, modelState.Count);
|
||||
Assert.Equal(0, modelState.ErrorCount);
|
||||
Assert.True(modelState.IsValid);
|
||||
|
||||
var entry = Assert.Single(modelState, e => e.Key == "p.Name").Value;
|
||||
Assert.Equal("Accord", entry.AttemptedValue);
|
||||
Assert.Equal("Accord", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, e => e.Key == "p.Specs[0].Key").Value;
|
||||
Assert.Equal("camera_specs", entry.AttemptedValue);
|
||||
Assert.Equal("camera_specs", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, e => e.Key == "p.Specs[0].Value[0].Name").Value;
|
||||
Assert.Equal("camera_spec1.txt", entry.AttemptedValue);
|
||||
Assert.Equal("camera_spec1.txt", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, e => e.Key == "p.Specs[0].Value[1].Name").Value;
|
||||
Assert.Equal("camera_spec2.txt", entry.AttemptedValue);
|
||||
Assert.Equal("camera_spec2.txt", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, e => e.Key == "p.Specs[1].Key").Value;
|
||||
Assert.Equal("tyre_specs", entry.AttemptedValue);
|
||||
Assert.Equal("tyre_specs", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, e => e.Key == "p.Specs[1].Value[0].Name").Value;
|
||||
Assert.Equal("tyre_spec1.txt", entry.AttemptedValue);
|
||||
Assert.Equal("tyre_spec1.txt", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, e => e.Key == "p.Specs[1].Value[1].Name").Value;
|
||||
Assert.Equal("tyre_spec2.txt", entry.AttemptedValue);
|
||||
Assert.Equal("tyre_spec2.txt", entry.RawValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MutableObjectModelBinder_BindsDictionaryProperty_WithArrayOfComplexTypeValue_Success()
|
||||
{
|
||||
// Arrange
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
var parameter = new ParameterDescriptor()
|
||||
{
|
||||
Name = "p",
|
||||
ParameterType = typeof(Car2)
|
||||
};
|
||||
|
||||
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||
{
|
||||
var queryString = "?p.Name=Accord"
|
||||
+ "&p.Specs[0].Key=camera_specs"
|
||||
+ "&p.Specs[0].Value[0].Name=camera_spec1.txt"
|
||||
+ "&p.Specs[0].Value[1].Name=camera_spec2.txt"
|
||||
+ "&p.Specs[1].Key=tyre_specs"
|
||||
+ "&p.Specs[1].Value[0].Name=tyre_spec1.txt"
|
||||
+ "&p.Specs[1].Value[1].Name=tyre_spec2.txt";
|
||||
request.QueryString = new QueryString(queryString);
|
||||
});
|
||||
|
||||
var modelState = testContext.ModelState;
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, testContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(modelBindingResult.IsModelSet);
|
||||
|
||||
var model = Assert.IsType<Car2>(modelBindingResult.Model);
|
||||
Assert.Equal("Accord", model.Name);
|
||||
|
||||
Assert.Collection(
|
||||
model.Specs,
|
||||
(e) =>
|
||||
{
|
||||
Assert.Equal("camera_specs", e.Key);
|
||||
Assert.Collection(
|
||||
e.Value,
|
||||
(s) =>
|
||||
{
|
||||
Assert.Equal("camera_spec1.txt", s.Name);
|
||||
},
|
||||
(s) =>
|
||||
{
|
||||
Assert.Equal("camera_spec2.txt", s.Name);
|
||||
});
|
||||
},
|
||||
(e) =>
|
||||
{
|
||||
Assert.Equal("tyre_specs", e.Key);
|
||||
Assert.Collection(
|
||||
e.Value,
|
||||
(s) =>
|
||||
{
|
||||
Assert.Equal("tyre_spec1.txt", s.Name);
|
||||
},
|
||||
(s) =>
|
||||
{
|
||||
Assert.Equal("tyre_spec2.txt", s.Name);
|
||||
});
|
||||
});
|
||||
|
||||
Assert.Equal(7, modelState.Count);
|
||||
Assert.Equal(0, modelState.ErrorCount);
|
||||
Assert.True(modelState.IsValid);
|
||||
|
||||
var entry = Assert.Single(modelState, e => e.Key == "p.Name").Value;
|
||||
Assert.Equal("Accord", entry.AttemptedValue);
|
||||
Assert.Equal("Accord", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, e => e.Key == "p.Specs[0].Key").Value;
|
||||
Assert.Equal("camera_specs", entry.AttemptedValue);
|
||||
Assert.Equal("camera_specs", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, e => e.Key == "p.Specs[0].Value[0].Name").Value;
|
||||
Assert.Equal("camera_spec1.txt", entry.AttemptedValue);
|
||||
Assert.Equal("camera_spec1.txt", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, e => e.Key == "p.Specs[0].Value[1].Name").Value;
|
||||
Assert.Equal("camera_spec2.txt", entry.AttemptedValue);
|
||||
Assert.Equal("camera_spec2.txt", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, e => e.Key == "p.Specs[1].Key").Value;
|
||||
Assert.Equal("tyre_specs", entry.AttemptedValue);
|
||||
Assert.Equal("tyre_specs", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, e => e.Key == "p.Specs[1].Value[0].Name").Value;
|
||||
Assert.Equal("tyre_spec1.txt", entry.AttemptedValue);
|
||||
Assert.Equal("tyre_spec1.txt", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, e => e.Key == "p.Specs[1].Value[1].Name").Value;
|
||||
Assert.Equal("tyre_spec2.txt", entry.AttemptedValue);
|
||||
Assert.Equal("tyre_spec2.txt", entry.RawValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MutableObjectModelBinder_BindsDictionaryProperty_WithIEnumerableOfKeyValuePair_Success()
|
||||
{
|
||||
// Arrange
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
var parameter = new ParameterDescriptor()
|
||||
{
|
||||
Name = "p",
|
||||
ParameterType = typeof(Car3)
|
||||
};
|
||||
|
||||
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||
{
|
||||
var queryString = "?p.Name=Accord"
|
||||
+ "&p.Specs[0].Key=camera_specs"
|
||||
+ "&p.Specs[0].Value[0].Name=camera_spec1.txt"
|
||||
+ "&p.Specs[0].Value[1].Name=camera_spec2.txt"
|
||||
+ "&p.Specs[1].Key=tyre_specs"
|
||||
+ "&p.Specs[1].Value[0].Name=tyre_spec1.txt"
|
||||
+ "&p.Specs[1].Value[1].Name=tyre_spec2.txt";
|
||||
request.QueryString = new QueryString(queryString);
|
||||
});
|
||||
|
||||
var modelState = testContext.ModelState;
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, testContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(modelBindingResult.IsModelSet);
|
||||
|
||||
var model = Assert.IsType<Car3>(modelBindingResult.Model);
|
||||
Assert.Equal("Accord", model.Name);
|
||||
|
||||
Assert.Collection(
|
||||
model.Specs,
|
||||
(e) =>
|
||||
{
|
||||
Assert.Equal("camera_specs", e.Key);
|
||||
Assert.Collection(
|
||||
e.Value,
|
||||
(s) =>
|
||||
{
|
||||
Assert.Equal("camera_spec1.txt", s.Name);
|
||||
},
|
||||
(s) =>
|
||||
{
|
||||
Assert.Equal("camera_spec2.txt", s.Name);
|
||||
});
|
||||
},
|
||||
(e) =>
|
||||
{
|
||||
Assert.Equal("tyre_specs", e.Key);
|
||||
Assert.Collection(
|
||||
e.Value,
|
||||
(s) =>
|
||||
{
|
||||
Assert.Equal("tyre_spec1.txt", s.Name);
|
||||
},
|
||||
(s) =>
|
||||
{
|
||||
Assert.Equal("tyre_spec2.txt", s.Name);
|
||||
});
|
||||
});
|
||||
|
||||
Assert.Equal(7, modelState.Count);
|
||||
Assert.Equal(0, modelState.ErrorCount);
|
||||
Assert.True(modelState.IsValid);
|
||||
|
||||
var entry = Assert.Single(modelState, e => e.Key == "p.Name").Value;
|
||||
Assert.Equal("Accord", entry.AttemptedValue);
|
||||
Assert.Equal("Accord", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, e => e.Key == "p.Specs[0].Key").Value;
|
||||
Assert.Equal("camera_specs", entry.AttemptedValue);
|
||||
Assert.Equal("camera_specs", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, e => e.Key == "p.Specs[0].Value[0].Name").Value;
|
||||
Assert.Equal("camera_spec1.txt", entry.AttemptedValue);
|
||||
Assert.Equal("camera_spec1.txt", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, e => e.Key == "p.Specs[0].Value[1].Name").Value;
|
||||
Assert.Equal("camera_spec2.txt", entry.AttemptedValue);
|
||||
Assert.Equal("camera_spec2.txt", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, e => e.Key == "p.Specs[1].Key").Value;
|
||||
Assert.Equal("tyre_specs", entry.AttemptedValue);
|
||||
Assert.Equal("tyre_specs", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, e => e.Key == "p.Specs[1].Value[0].Name").Value;
|
||||
Assert.Equal("tyre_spec1.txt", entry.AttemptedValue);
|
||||
Assert.Equal("tyre_spec1.txt", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, e => e.Key == "p.Specs[1].Value[1].Name").Value;
|
||||
Assert.Equal("tyre_spec2.txt", entry.AttemptedValue);
|
||||
Assert.Equal("tyre_spec2.txt", entry.RawValue);
|
||||
}
|
||||
|
||||
private class Order8
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
|
@ -1294,6 +1616,90 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
Assert.True(modelState.IsValid);
|
||||
}
|
||||
|
||||
private class Car4
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public KeyValuePair<string, Dictionary<string, string>> Specs { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Foo_MutableObjectModelBinder_BindsKeyValuePairProperty_WithPrefix_Success()
|
||||
{
|
||||
// Arrange
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
var parameter = new ParameterDescriptor()
|
||||
{
|
||||
Name = "p",
|
||||
ParameterType = typeof(Car4)
|
||||
};
|
||||
|
||||
// Need to have a key here so that the MutableObjectModelBinder will recurse to bind elements.
|
||||
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||
{
|
||||
var queryString = "?p.Name=Accord"
|
||||
+ "&p.Specs.Key=camera_specs"
|
||||
+ "&p.Specs.Value[0].Key=spec1"
|
||||
+ "&p.Specs.Value[0].Value=spec1.txt"
|
||||
+ "&p.Specs.Value[1].Key=spec2"
|
||||
+ "&p.Specs.Value[1].Value=spec2.txt";
|
||||
|
||||
request.QueryString = new QueryString(queryString);
|
||||
});
|
||||
|
||||
var modelState = testContext.ModelState;
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, testContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(modelBindingResult.IsModelSet);
|
||||
|
||||
var model = Assert.IsType<Car4>(modelBindingResult.Model);
|
||||
Assert.Equal("Accord", model.Name);
|
||||
|
||||
Assert.Collection(
|
||||
model.Specs.Value,
|
||||
(e) =>
|
||||
{
|
||||
Assert.Equal("spec1", e.Key);
|
||||
Assert.Equal("spec1.txt", e.Value);
|
||||
},
|
||||
(e) =>
|
||||
{
|
||||
Assert.Equal("spec2", e.Key);
|
||||
Assert.Equal("spec2.txt", e.Value);
|
||||
});
|
||||
|
||||
Assert.Equal(6, modelState.Count);
|
||||
Assert.Equal(0, modelState.ErrorCount);
|
||||
Assert.True(modelState.IsValid);
|
||||
|
||||
var entry = Assert.Single(modelState, e => e.Key == "p.Name").Value;
|
||||
Assert.Equal("Accord", entry.AttemptedValue);
|
||||
Assert.Equal("Accord", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, e => e.Key == "p.Specs.Key").Value;
|
||||
Assert.Equal("camera_specs", entry.AttemptedValue);
|
||||
Assert.Equal("camera_specs", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, e => e.Key == "p.Specs.Value[0].Key").Value;
|
||||
Assert.Equal("spec1", entry.AttemptedValue);
|
||||
Assert.Equal("spec1", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, e => e.Key == "p.Specs.Value[0].Value").Value;
|
||||
Assert.Equal("spec1.txt", entry.AttemptedValue);
|
||||
Assert.Equal("spec1.txt", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, e => e.Key == "p.Specs.Value[1].Key").Value;
|
||||
Assert.Equal("spec2", entry.AttemptedValue);
|
||||
Assert.Equal("spec2", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, e => e.Key == "p.Specs.Value[1].Value").Value;
|
||||
Assert.Equal("spec2.txt", entry.AttemptedValue);
|
||||
Assert.Equal("spec2.txt", entry.RawValue);
|
||||
}
|
||||
|
||||
private class Order9
|
||||
{
|
||||
public Person9 Customer { get; set; }
|
||||
|
|
@ -2126,6 +2532,121 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
|
||||
}
|
||||
|
||||
private class Product
|
||||
{
|
||||
public int ProductId { get; set; }
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public IList<string> Aliases { get; }
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("?parameter.ProductId=10")]
|
||||
[InlineData("?parameter.ProductId=10¶meter.Name=Camera")]
|
||||
[InlineData("?parameter.ProductId=10¶meter.Name=Camera¶meter.Aliases[0]=Camera1")]
|
||||
public async Task ComplexTypeModelBinder_BindsSettableProperties(string queryString)
|
||||
{
|
||||
// Arrange
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
var parameter = new ParameterDescriptor()
|
||||
{
|
||||
Name = "parameter",
|
||||
ParameterType = typeof(Product)
|
||||
};
|
||||
|
||||
// Need to have a key here so that the ComplexTypeModelBinder will recurse to bind elements.
|
||||
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||
{
|
||||
request.QueryString = new QueryString(queryString);
|
||||
SetJsonBodyContent(request, AddressBodyContent);
|
||||
});
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, testContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(modelBindingResult.IsModelSet);
|
||||
|
||||
var model = Assert.IsType<Product>(modelBindingResult.Model);
|
||||
Assert.NotNull(model);
|
||||
Assert.Equal(10, model.ProductId);
|
||||
Assert.Null(model.Name);
|
||||
Assert.Null(model.Aliases);
|
||||
}
|
||||
|
||||
private class Photo
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
public KeyValuePair<string, LocationInfo> Info { get; set; }
|
||||
}
|
||||
|
||||
private class LocationInfo
|
||||
{
|
||||
[FromHeader]
|
||||
public string GpsCoordinates { get; set; }
|
||||
|
||||
public int Zipcode { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MutableObjectModelBinder_BindsKeyValuePairProperty_HavingFromHeaderProperty_Success()
|
||||
{
|
||||
// Arrange
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
var parameter = new ParameterDescriptor()
|
||||
{
|
||||
Name = "parameter",
|
||||
ParameterType = typeof(Photo)
|
||||
};
|
||||
|
||||
// Need to have a key here so that the MutableObjectModelBinder will recurse to bind elements.
|
||||
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||
{
|
||||
request.Headers.Add("GpsCoordinates", "10,20");
|
||||
request.QueryString = new QueryString("?Id=1&Info.Key=location1&Info.Value.Zipcode=98052");
|
||||
});
|
||||
|
||||
var modelState = testContext.ModelState;
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, testContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(modelBindingResult.IsModelSet);
|
||||
|
||||
// Model
|
||||
var model = Assert.IsType<Photo>(modelBindingResult.Model);
|
||||
Assert.Equal("1", model.Id);
|
||||
Assert.NotNull(model.Info);
|
||||
Assert.Equal("location1", model.Info.Key);
|
||||
Assert.NotNull(model.Info.Value);
|
||||
Assert.Equal("10,20", model.Info.Value.GpsCoordinates);
|
||||
Assert.Equal(98052, model.Info.Value.Zipcode);
|
||||
|
||||
// ModelState
|
||||
Assert.Equal(4, modelState.Count);
|
||||
Assert.Equal(0, modelState.ErrorCount);
|
||||
Assert.True(modelState.IsValid);
|
||||
|
||||
var entry = Assert.Single(modelState, e => e.Key == "Id").Value;
|
||||
Assert.Equal("1", entry.AttemptedValue);
|
||||
Assert.Equal("1", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, e => e.Key == "Info.Key").Value;
|
||||
Assert.Equal("location1", entry.AttemptedValue);
|
||||
Assert.Equal("location1", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, e => e.Key == "Info.Value.Zipcode").Value;
|
||||
Assert.Equal("98052", entry.AttemptedValue);
|
||||
Assert.Equal("98052", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, e => e.Key == "Info.Value.GpsCoordinates").Value;
|
||||
Assert.Equal("10,20", entry.AttemptedValue);
|
||||
Assert.Equal(new[] { "10", "20" }, entry.RawValue);
|
||||
}
|
||||
|
||||
private static void SetJsonBodyContent(HttpRequest request, string content)
|
||||
{
|
||||
var stream = new MemoryStream(new UTF8Encoding(encoderShouldEmitUTF8Identifier: false).GetBytes(content));
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// 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;
|
||||
|
|
@ -356,6 +357,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
public CustomReadOnlyCollection<Address> Address { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryUpdateModel_ReadOnlyCollectionModel_EmptyPrefix_DoesNotGetBound()
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -371,22 +373,46 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
var result = await TryUpdateModelAsync(model, string.Empty, testContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.False(result);
|
||||
|
||||
// Model
|
||||
Assert.NotNull(model.Address);
|
||||
|
||||
// Read-only collection should not be updated.
|
||||
Assert.Empty(model.Address);
|
||||
|
||||
// ModelState (data is valid but is not copied into Address).
|
||||
Assert.True(modelState.IsValid);
|
||||
// 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.Valid, state.ValidationState);
|
||||
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
|
||||
|
|
@ -484,7 +510,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryUpdateModel_NonSettableArrayModel_EmptyPrefix_GetsBound()
|
||||
public async Task TryUpdateModel_NonSettableArrayModel_EmptyPrefix_IsNotBound()
|
||||
{
|
||||
// Arrange
|
||||
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||
|
|
@ -512,6 +538,99 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
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.Equal(1, model.Address.Count());
|
||||
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.Equal(1, model.Address.Count());
|
||||
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()
|
||||
|
|
@ -818,39 +937,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
Assert.Equal(ModelValidationState.Valid, state.ValidationState);
|
||||
}
|
||||
|
||||
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.True(result);
|
||||
|
||||
// Model
|
||||
Assert.NotNull(model.Address);
|
||||
|
||||
// Read-only collection should not be updated.
|
||||
Assert.Empty(model.Address);
|
||||
|
||||
// ModelState (data is valid but is not copied into Address).
|
||||
Assert.True(modelState.IsValid);
|
||||
var entry = Assert.Single(modelState);
|
||||
Assert.Equal("prefix.Address[0].Street", entry.Key);
|
||||
var state = entry.Value;
|
||||
Assert.NotNull(state);
|
||||
Assert.Equal(ModelValidationState.Valid, state.ValidationState);
|
||||
Assert.Equal("SomeStreet", state.RawValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryUpdateModel_SettableArrayModel_WithPrefix_CreatesArray()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using FilesWebSite.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
|
@ -21,5 +23,22 @@ namespace FilesWebSite.Controllers
|
|||
|
||||
return Json(resultUser);
|
||||
}
|
||||
|
||||
[HttpPost("UploadProductSpecs")]
|
||||
public IActionResult ProductSpecs(Product product)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var files = new Dictionary<string, List<string>>();
|
||||
foreach (var keyValuePair in product.Specs)
|
||||
{
|
||||
files.Add(keyValuePair.Key, keyValuePair.Value?.Select(formFile => formFile?.FileName).ToList());
|
||||
}
|
||||
|
||||
return Json(new { Name = product.Name, Specs = files });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace FilesWebSite.Models
|
||||
{
|
||||
public class Product
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public IDictionary<string, IEnumerable<IFormFile>> Specs { get; set; }
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue