Bind POCO model correctly; fallback to empty prefix despite exact name match

- #1865
- change `MutableObjectModelBinder` to ignore exact match in value providers
 - had an incorrect assumption: don't want exact model name to match since
   this binder supports only complex objects
 - also ignored `BinderModelName`, value provider filtering, et cetera
- reduces over-binding e.g. `[Required]` validation within missing properties

also add more tests of #2129 scenarios
This commit is contained in:
Doug Bunting 2015-03-20 12:44:34 -07:00
parent 94e326f953
commit 533474d07c
6 changed files with 438 additions and 52 deletions

View File

@ -92,16 +92,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return true;
}
// 3. The model name is not prefixed and a value provider can directly provide a value for the model name.
// The fact that it is not prefixed means that the containsPrefixAsync call checks for the exact
// model name instead of doing a prefix match.
if (!bindingContext.ModelName.Contains(".") &&
await bindingContext.ValueProvider.ContainsPrefixAsync(bindingContext.ModelName))
{
return true;
}
// 4. Any of the model properties can be bound using a value provider.
// 3. Any of the model properties can be bound using a value provider.
if (await CanValueBindAnyModelProperties(context))
{
return true;

View File

@ -718,7 +718,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
}
[Fact]
public async Task ModelBinding_LimitsErrorsToMaxErrorCount()
public async Task ModelBinding_LimitsErrorsToMaxErrorCount_DoesNotValidateMembersOfMissingProperties()
{
// Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
@ -730,20 +730,21 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
//Assert
var json = JsonConvert.DeserializeObject<Dictionary<string, string>>(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 Field1 field is required.", json["Field1"]);
Assert.Equal("The Field2 field is required.", json["Field2"]);
Assert.Equal("The Field3 field is required.", json["Field3"]);
Assert.Equal("The Field4 field is required.", json["Field4"]);
Assert.Equal("The Field5 field is required.", json["Field5"]);
Assert.Equal("The Field6 field is required.", json["Field6"]);
Assert.Equal("The Field7 field is required.", json["Field7"]);
Assert.Equal("The maximum number of allowed model errors has been reached.", json[""]);
}
[Fact]
public async Task ModelBinding_ValidatesAllPropertiesInModel()
public async Task ModelBinding_FallsBackAndValidatesAllPropertiesInModel()
{
// Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
@ -752,12 +753,72 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
// Act
var response = await client.GetStringAsync("http://localhost/Home/ModelWithFewValidationErrors?model=");
//Assert
// Assert
var json = JsonConvert.DeserializeObject<Dictionary<string, string>>(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"]);
Assert.Equal("The Field1 field is required.", json["Field1"]);
Assert.Equal("The Field2 field is required.", json["Field2"]);
Assert.Equal("The Field3 field is required.", json["Field3"]);
}
[Fact]
public async Task ModelBinding_FallsBackAndSuccessfullyBindsStructCollection()
{
// Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient();
var contentDictionary = new Dictionary<string, string>
{
{ "[0]", "23" },
{ "[1]", "97" },
{ "[2]", "103" },
};
var requestContent = new FormUrlEncodedContent(contentDictionary);
// Act
var response = await client.PostAsync("http://localhost/integers", requestContent);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var responseContent = await response.Content.ReadAsStringAsync();
var array = JsonConvert.DeserializeObject<int[]>(responseContent);
Assert.Equal(3, array.Length);
Assert.Equal(23, array[0]);
Assert.Equal(97, array[1]);
Assert.Equal(103, array[2]);
}
[Fact]
public async Task ModelBinding_FallsBackAndSuccessfullyBindsPOCOCollection()
{
// Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient();
var contentDictionary = new Dictionary<string, string>
{
{ "[0].CityCode", "YYZ" },
{ "[0].CityName", "Toronto" },
{ "[1].CityCode", "SEA" },
{ "[1].CityName", "Seattle" },
};
var requestContent = new FormUrlEncodedContent(contentDictionary);
// Act
var response = await client.PostAsync("http://localhost/cities", requestContent);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var responseContent = await response.Content.ReadAsStringAsync();
var list = JsonConvert.DeserializeObject<List<City>>(responseContent);
Assert.Equal(2, list.Count);
Assert.Equal(contentDictionary["[0].CityCode"], list[0].CityCode);
Assert.Equal(contentDictionary["[0].CityName"], list[0].CityName);
Assert.Equal(contentDictionary["[1].CityCode"], list[1].CityCode);
Assert.Equal(contentDictionary["[1].CityName"], list[1].CityName);
}
[Fact]
@ -1918,7 +1979,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
}
[Fact]
public async Task BindModelAsync_WithIncorrectlyFormattedNestedCollectionValue()
public async Task BindModelAsync_WithIncorrectlyFormattedNestedCollectionValue_BindsSingleNullEntry()
{
// Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
@ -1935,9 +1996,13 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
// Assert
var result = await ReadValue<UserWithAddress>(response);
// Though posted content did not contain any valid Addresses, it is bound as a single-element List
// containing null. Slightly odd behavior is specific to this unusual error case: CollectionModelBinder
// attempted to bind a comma-separated string as a collection and Address lacks a from-string conversion.
// MutableObjectModelBinder does not create model when value providers have no data (unless at top level).
var address = Assert.Single(result.Addresses);
Assert.Null(address.AddressLines);
Assert.Null(address.ZipCode);
Assert.Null(address);
}
[Fact]
@ -1979,7 +2044,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
}
[Fact]
public async Task BindModelAsync_WithNestedCollectionContainingRecursiveRelation_WithMalformedValue()
public async Task
BindModelAsync_WithNestedCollectionContainingRecursiveRelation_WithMalformedValue_BindsSingleNullEntry()
{
// Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
@ -1996,9 +2062,13 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
// Assert
var result = await ReadValue<PeopleModel>(response);
// Though posted content did not contain any valid People, it is bound as a single-element List
// containing null. Slightly odd behavior is specific to this unusual error case: CollectionModelBinder
// attempted to bind a comma-separated string as a collection and Address lacks a from-string conversion.
// MutableObjectModelBinder does not create model when value providers have no data (unless at top level).
var person = Assert.Single(result.People);
Assert.Null(person.Name);
Assert.Null(person.Parent);
Assert.Null(person);
}
[Theory]

View File

@ -145,6 +145,127 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.Null(result);
}
[Fact]
public async Task ModelBinder_FallsBackToEmpty_IfBinderMatchesButDoesNotSetModel()
{
// Arrange
var bindingContext = new ModelBindingContext
{
FallbackToEmptyPrefix = true,
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(List<int>)),
ModelName = "someName",
ModelState = new ModelStateDictionary(),
ValueProvider = new SimpleHttpValueProvider
{
{ "someOtherName", "dummyValue" }
},
OperationBindingContext = new OperationBindingContext
{
ValidatorProvider = GetValidatorProvider()
}
};
var count = 0;
var modelBinder = new Mock<IModelBinder>();
modelBinder
.Setup(mb => mb.BindModelAsync(It.IsAny<ModelBindingContext>()))
.Callback<ModelBindingContext>(context =>
{
// Expect two calls; the second with empty ModelName.
Assert.InRange(count, 0, 1);
count++;
if (count == 1)
{
Assert.Equal("someName", context.ModelName);
}
else
{
Assert.Empty(context.ModelName);
}
})
.Returns(Task.FromResult(new ModelBindingResult(model: null, key: "someName", isModelSet: false)));
var composite = CreateCompositeBinder(modelBinder.Object);
// Act & Assert
var result = await composite.BindModelAsync(bindingContext);
}
[Fact]
public async Task ModelBinder_DoesNotFallBackToEmpty_IfFallbackToEmptyPrefixFalse()
{
// Arrange
var bindingContext = new ModelBindingContext
{
FallbackToEmptyPrefix = false,
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(List<int>)),
ModelName = "someName",
ModelState = new ModelStateDictionary(),
ValueProvider = new SimpleHttpValueProvider
{
{ "someOtherName", "dummyValue" }
},
OperationBindingContext = new OperationBindingContext
{
ValidatorProvider = GetValidatorProvider()
}
};
var modelBinder = new Mock<IModelBinder>();
modelBinder
.Setup(mb => mb.BindModelAsync(It.IsAny<ModelBindingContext>()))
.Callback<ModelBindingContext>(context =>
{
Assert.Equal("someName", context.ModelName);
})
.Returns(Task.FromResult(new ModelBindingResult(model: null, key: "someName", isModelSet: false)))
.Verifiable();
var composite = CreateCompositeBinder(modelBinder.Object);
// Act & Assert
var result = await composite.BindModelAsync(bindingContext);
modelBinder.Verify(mb => mb.BindModelAsync(It.IsAny<ModelBindingContext>()), Times.Once);
}
[Fact]
public async Task ModelBinder_DoesNotFallBackToEmpty_IfErrorsAreAdded()
{
// Arrange
var bindingContext = new ModelBindingContext
{
FallbackToEmptyPrefix = false,
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(List<int>)),
ModelName = "someName",
ModelState = new ModelStateDictionary(),
ValueProvider = new SimpleHttpValueProvider
{
{ "someOtherName", "dummyValue" }
},
OperationBindingContext = new OperationBindingContext
{
ValidatorProvider = GetValidatorProvider()
}
};
var modelBinder = new Mock<IModelBinder>();
modelBinder
.Setup(mb => mb.BindModelAsync(It.IsAny<ModelBindingContext>()))
.Callback<ModelBindingContext>(context =>
{
Assert.Equal("someName", context.ModelName);
context.ModelState.AddModelError(context.ModelName, "this is an error message");
})
.Returns(Task.FromResult(new ModelBindingResult(model: null, key: "someName", isModelSet: false)))
.Verifiable();
var composite = CreateCompositeBinder(modelBinder.Object);
// Act & Assert
var result = await composite.BindModelAsync(bindingContext);
modelBinder.Verify(mb => mb.BindModelAsync(It.IsAny<ModelBindingContext>()), Times.Once);
}
[Fact]
public async Task ModelBinder_ReturnsTrue_SetsNullValue_SetsModelStateKey()
{

View File

@ -193,6 +193,46 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.True(retModel);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task CanCreateModel_ReturnsTrue_ForNonTopLevelModel_BasedOnValueAvailability(bool valueAvailable)
{
// Arrange
var mockValueProvider = new Mock<IValueProvider>(MockBehavior.Strict);
mockValueProvider
.Setup(provider => provider.ContainsPrefixAsync("SimpleContainer.Simple.Name"))
.Returns(Task.FromResult(valueAvailable));
var typeMetadata = GetMetadataForType(typeof(SimpleContainer));
var modelMetadata = typeMetadata.Properties[nameof(SimpleContainer.Simple)];
var bindingContext = new MutableObjectBinderContext
{
ModelBindingContext = new ModelBindingContext
{
ModelMetadata = modelMetadata,
ModelName = "SimpleContainer.Simple",
OperationBindingContext = new OperationBindingContext
{
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
ValueProvider = mockValueProvider.Object,
MetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(),
},
ValueProvider = mockValueProvider.Object,
},
PropertyMetadata = modelMetadata.Properties,
};
var mutableBinder = new MutableObjectModelBinder();
// Act
var result = await mutableBinder.CanCreateModel(bindingContext);
// Assert
// Result matches whether first Simple property can bind.
Assert.Equal(valueAvailable, result);
}
[Theory]
[InlineData(typeof(TypeWithNoBinderMetadata), false)]
[InlineData(typeof(TypeWithNoBinderMetadata), true)]
@ -603,14 +643,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Arrange
var expectedPropertyNames = new[]
{
"DateOfBirth",
"DateOfDeath",
"ValueTypeRequired",
"FirstName",
"LastName",
"PropertyWithDefaultValue",
"PropertyWithInitializedValue",
"PropertyWithInitializedValueAndDefault",
nameof(Person.DateOfBirth),
nameof(Person.DateOfDeath),
nameof(Person.ValueTypeRequired),
nameof(Person.ValueTypeRequiredWithDefaultValue),
nameof(Person.FirstName),
nameof(Person.LastName),
nameof(Person.PropertyWithDefaultValue),
nameof(Person.PropertyWithInitializedValue),
nameof(Person.PropertyWithInitializedValueAndDefault),
};
var bindingContext = new ModelBindingContext
{
@ -707,6 +748,28 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.Equal(new[] { "Never" }, validationInfo.SkipProperties);
}
[Fact]
public void GetPropertyValidationInfo_WithIndexerProperties_Succeeds()
{
// Arrange
var bindingContext = new ModelBindingContext
{
// Any type, even an otherwise-simple POCO with an indexer property, would do here.
ModelMetadata = GetMetadataForType(typeof(List<Person>)),
OperationBindingContext = new OperationBindingContext
{
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
},
};
// Act
var validationInfo = MutableObjectModelBinder.GetPropertyValidationInfo(bindingContext);
// Assert
Assert.Equal(Enumerable.Empty<string>(), validationInfo.RequiredProperties);
Assert.Equal(Enumerable.Empty<string>(), validationInfo.SkipProperties);
}
[Fact]
[ReplaceCulture]
public void ProcessDto_BindRequiredFieldMissing_RaisesModelError()
@ -842,7 +905,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Check Age error.
ModelState modelState;
Assert.True(modelStateDictionary.TryGetValue("theModel.Age", out modelState));
Assert.True(modelStateDictionary.TryGetValue("theModel." + nameof(ModelWithRequired.Age), out modelState));
var modelError = Assert.Single(modelState.Errors);
Assert.Null(modelError.Exception);
@ -851,7 +914,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.Equal(expected, modelError.ErrorMessage);
// Check City error.
Assert.True(modelStateDictionary.TryGetValue("theModel.City", out modelState));
Assert.True(modelStateDictionary.TryGetValue("theModel." + nameof(ModelWithRequired.City), out modelState));
modelError = Assert.Single(modelState.Errors);
Assert.Null(modelError.Exception);
@ -895,7 +958,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Check City error.
ModelState modelState;
Assert.True(modelStateDictionary.TryGetValue("theModel.City", out modelState));
Assert.True(modelStateDictionary.TryGetValue("theModel." + nameof(ModelWithRequired.City), out modelState));
var modelError = Assert.Single(modelState.Errors);
Assert.Null(modelError.Exception);
@ -922,53 +985,156 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Assert
var modelStateDictionary = bindingContext.ModelState;
Assert.False(modelStateDictionary.IsValid);
Assert.Single(modelStateDictionary);
Assert.Equal(2, modelStateDictionary.Count);
// Check ValueTypeRequired error.
ModelState modelState;
Assert.True(modelStateDictionary.TryGetValue("theModel.ValueTypeRequired", out modelState));
var modelStateEntry = Assert.Single(
modelStateDictionary,
entry => entry.Key == "theModel." + nameof(Person.ValueTypeRequired));
Assert.Equal("theModel." + nameof(Person.ValueTypeRequired), modelStateEntry.Key);
var modelState = modelStateEntry.Value;
Assert.Equal(ModelValidationState.Invalid, modelState.ValidationState);
var modelError = Assert.Single(modelState.Errors);
Assert.Null(modelError.Exception);
Assert.NotNull(modelError.ErrorMessage);
Assert.Equal("Sample message", modelError.ErrorMessage);
// Check ValueTypeRequiredWithDefaultValue error.
modelStateEntry = Assert.Single(
modelStateDictionary,
entry => entry.Key == "theModel." + nameof(Person.ValueTypeRequiredWithDefaultValue));
Assert.Equal("theModel." + nameof(Person.ValueTypeRequiredWithDefaultValue), modelStateEntry.Key);
modelState = modelStateEntry.Value;
Assert.Equal(ModelValidationState.Invalid, modelState.ValidationState);
modelError = Assert.Single(modelState.Errors);
Assert.Null(modelError.Exception);
Assert.NotNull(modelError.ErrorMessage);
Assert.Equal("Another sample message", modelError.ErrorMessage);
}
[Fact]
public void ProcessDto_RequiredFieldNull_RaisesModelErrorWithMessage()
[Theory]
[InlineData(false)]
[InlineData(true)]
public void ProcessDto_RequiredFieldNull_RaisesModelErrorWithMessage(bool isModelSet)
{
// Arrange
var model = new Person();
var containerMetadata = GetMetadataForType(model.GetType());
var bindingContext = CreateContext(containerMetadata, model);
var modelStateDictionary = bindingContext.ModelState;
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
var testableBinder = new TestableMutableObjectModelBinder();
// ValueTypeRequiredWithDefaultValue value comes from [DefaultValue] when !isModelSet.
var expectedValue = isModelSet ? 0 : 42;
// Make ValueTypeRequired invalid.
var propertyMetadata = dto.PropertyMetadata.Single(p => p.PropertyName == "ValueTypeRequired");
var propertyMetadata = dto.PropertyMetadata.Single(p => p.PropertyName == nameof(Person.ValueTypeRequired));
dto.Results[propertyMetadata] = new ModelBindingResult(
null,
isModelSet: true,
key: "theModel.ValueTypeRequired");
isModelSet: isModelSet,
key: "theModel." + nameof(Person.ValueTypeRequired));
// Make ValueTypeRequiredWithDefaultValue invalid
propertyMetadata = dto.PropertyMetadata
.Single(p => p.PropertyName == nameof(Person.ValueTypeRequiredWithDefaultValue));
dto.Results[propertyMetadata] = new ModelBindingResult(
model: null,
isModelSet: isModelSet,
key: "theModel." + nameof(Person.ValueTypeRequiredWithDefaultValue));
// Act
testableBinder.ProcessDto(bindingContext, dto);
// Assert
var modelStateDictionary = bindingContext.ModelState;
Assert.False(modelStateDictionary.IsValid);
Assert.Single(modelStateDictionary);
// Check ValueTypeRequired error.
ModelState modelState;
Assert.True(modelStateDictionary.TryGetValue("theModel.ValueTypeRequired", out modelState));
var modelStateEntry = Assert.Single(
modelStateDictionary,
entry => entry.Key == "theModel." + nameof(Person.ValueTypeRequired));
Assert.Equal("theModel." + nameof(Person.ValueTypeRequired), modelStateEntry.Key);
var modelState = modelStateEntry.Value;
Assert.Equal(ModelValidationState.Invalid, modelState.ValidationState);
var modelError = Assert.Single(modelState.Errors);
Assert.Null(modelError.Exception);
Assert.NotNull(modelError.ErrorMessage);
Assert.Equal("Sample message", modelError.ErrorMessage);
// Check ValueTypeRequiredWithDefaultValue error.
modelStateEntry = Assert.Single(
modelStateDictionary,
entry => entry.Key == "theModel." + nameof(Person.ValueTypeRequiredWithDefaultValue));
Assert.Equal("theModel." + nameof(Person.ValueTypeRequiredWithDefaultValue), modelStateEntry.Key);
modelState = modelStateEntry.Value;
Assert.Equal(ModelValidationState.Invalid, modelState.ValidationState);
modelError = Assert.Single(modelState.Errors);
Assert.Null(modelError.Exception);
Assert.NotNull(modelError.ErrorMessage);
Assert.Equal("Another sample message", modelError.ErrorMessage);
Assert.Equal(0, model.ValueTypeRequired);
Assert.Equal(expectedValue, model.ValueTypeRequiredWithDefaultValue);
}
[Fact]
public void ProcessDto_ProvideRequiredFields_Success()
{
// Arrange
var model = new Person();
var containerMetadata = GetMetadataForType(model.GetType());
var bindingContext = CreateContext(containerMetadata, model);
var modelStateDictionary = bindingContext.ModelState;
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
var testableBinder = new TestableMutableObjectModelBinder();
// Make ValueTypeRequired valid.
var propertyMetadata = dto.PropertyMetadata
.Single(p => p.PropertyName == nameof(Person.ValueTypeRequired));
dto.Results[propertyMetadata] = new ModelBindingResult(
41,
isModelSet: true,
key: "theModel." + nameof(Person.ValueTypeRequired));
// Make ValueTypeRequiredWithDefaultValue valid.
propertyMetadata = dto.PropertyMetadata
.Single(p => p.PropertyName == nameof(Person.ValueTypeRequiredWithDefaultValue));
dto.Results[propertyMetadata] = new ModelBindingResult(
model: 57,
isModelSet: true,
key: "theModel." + nameof(Person.ValueTypeRequiredWithDefaultValue));
// Also remind ProcessDto about PropertyWithDefaultValue -- as ComplexModelDtoModelBinder would.
propertyMetadata = dto.PropertyMetadata
.Single(p => p.PropertyName == nameof(Person.PropertyWithDefaultValue));
dto.Results[propertyMetadata] = new ModelBindingResult(
model: null,
isModelSet: false,
key: "theModel." + nameof(Person.PropertyWithDefaultValue));
// Act
testableBinder.ProcessDto(bindingContext, dto);
// Assert
Assert.True(modelStateDictionary.IsValid);
Assert.Empty(modelStateDictionary);
// Model gets provided values.
Assert.Equal(41, model.ValueTypeRequired);
Assert.Equal(57, model.ValueTypeRequiredWithDefaultValue);
Assert.Equal(123.456m, model.PropertyWithDefaultValue); // from [DefaultValue]
}
[Fact]
@ -1426,6 +1592,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
[Required(ErrorMessage = "Sample message")]
public int ValueTypeRequired { get; set; }
[Required(ErrorMessage = "Another sample message")]
[DefaultValue(42)]
public int ValueTypeRequiredWithDefaultValue { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string NonUpdateableProperty { get; private set; }
@ -1593,6 +1763,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
}
public class SimpleContainer
{
public Simple Simple { get; set; }
}
public class Simple
{
public string Name { get; set; }
}
private IServiceProvider CreateServices()
{
var services = new Mock<IServiceProvider>(MockBehavior.Strict);

View File

@ -18,6 +18,18 @@ namespace ModelBindingWebSite.Controllers
return Content(System.Text.Encoding.UTF8.GetString(byteValues));
}
[HttpPost("/integers")]
public IActionResult CollectionToJson(int[] model)
{
return Json(model);
}
[HttpPost("/cities")]
public IActionResult PocoCollectionToJson(List<City> model)
{
return Json(model);
}
public object ModelWithTooManyValidationErrors(LargeModelWithValidation model)
{
return CreateValidationDictionary();

View File

@ -18,5 +18,17 @@ namespace ModelBindingWebSite.Models
[Required]
public ModelWithValidation Field4 { get; set; }
[Required]
public ModelWithValidation Field5 { get; set; }
[Required]
public ModelWithValidation Field6 { get; set; }
[Required]
public ModelWithValidation Field7 { get; set; }
[Required]
public ModelWithValidation Field8 { get; set; }
}
}