`CompositeModelBinder.TryBind()` should return `null` more often
- #2129 - do not propagate results with `!IsModelSet`, allowing empty prefix fallback - adjust `ComplexModelDtoModelBinder` to at least fake-bind all properties - default values not consistently picked up otherwise nit: correct 2 test names in `KeyValuePairModelBinderTest`
This commit is contained in:
parent
bcb2fa6ba2
commit
94e326f953
|
|
@ -15,27 +15,29 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
ModelBindingHelper.ValidateBindingContext(bindingContext,
|
ModelBindingHelper.ValidateBindingContext(bindingContext, typeof(ComplexModelDto), allowNullModel: false);
|
||||||
typeof(ComplexModelDto),
|
|
||||||
allowNullModel: false);
|
|
||||||
|
|
||||||
var dto = (ComplexModelDto)bindingContext.Model;
|
var dto = (ComplexModelDto)bindingContext.Model;
|
||||||
foreach (var propertyMetadata in dto.PropertyMetadata)
|
foreach (var propertyMetadata in dto.PropertyMetadata)
|
||||||
{
|
{
|
||||||
var propertyModelName = ModelBindingHelper.CreatePropertyModelName(
|
var propertyModelName = ModelBindingHelper.CreatePropertyModelName(
|
||||||
bindingContext.ModelName,
|
bindingContext.ModelName,
|
||||||
propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName);
|
propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName);
|
||||||
|
|
||||||
var propertyBindingContext = ModelBindingContext.GetChildModelBindingContext(
|
var propertyBindingContext = ModelBindingContext.GetChildModelBindingContext(
|
||||||
bindingContext,
|
bindingContext,
|
||||||
propertyModelName,
|
propertyModelName,
|
||||||
propertyMetadata);
|
propertyMetadata);
|
||||||
|
|
||||||
// bind and propagate the values
|
|
||||||
// If we can't bind then leave the result missing (don't add a null).
|
|
||||||
var modelBindingResult =
|
var modelBindingResult =
|
||||||
await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(propertyBindingContext);
|
await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(propertyBindingContext);
|
||||||
if (modelBindingResult != null)
|
if (modelBindingResult == null)
|
||||||
|
{
|
||||||
|
// Could not bind. Add a result so MutableObjectModelBinder will check for [DefaultValue].
|
||||||
|
dto.Results[propertyMetadata] =
|
||||||
|
new ModelBindingResult(model: null, key: propertyModelName, isModelSet: false);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
dto.Results[propertyMetadata] = modelBindingResult;
|
dto.Results[propertyMetadata] = modelBindingResult;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var result = await binder.BindModelAsync(bindingContext);
|
var result = await binder.BindModelAsync(bindingContext);
|
||||||
if (result != null)
|
if (result != null)
|
||||||
{
|
{
|
||||||
return result;
|
// Use returned ModelBindingResult if it either indicates the model was set or is related to a
|
||||||
|
// ModelState entry. The second condition is necessary because the ModelState entry would never be
|
||||||
|
// validated if caller fell back to the empty prefix, leading to an possibly-incorrect !IsValid.
|
||||||
|
//
|
||||||
|
// In most (hopefully all) cases, the ModelState entry exists because some binders add errors
|
||||||
|
// before returning a result with !IsModelSet. Those binders often cannot run twice anyhow.
|
||||||
|
if (result.IsModelSet || bindingContext.ModelState.ContainsKey(bindingContext.ModelName))
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current binder should have been able to bind value but found nothing. Exit loop in a way that
|
||||||
|
// tells caller to fall back to the empty prefix, if appropriate. Do not return result because it
|
||||||
|
// means only "other binders are not applicable".
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -410,7 +410,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// for each property that was bound, call the setter, recording exceptions as necessary
|
// For each property that ComplexModelDtoModelBinder attempted to bind, call the setter, recording
|
||||||
|
// exceptions as necessary.
|
||||||
foreach (var entry in dto.Results)
|
foreach (var entry in dto.Results)
|
||||||
{
|
{
|
||||||
var dtoResult = entry.Value;
|
var dtoResult = entry.Value;
|
||||||
|
|
|
||||||
|
|
@ -112,10 +112,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ModelBinder_ReturnsTrue_WithoutSettingValue()
|
public async Task ModelBinder_ReturnsNull_IfBinderMatchesButDoesNotSetModel()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
||||||
var bindingContext = new ModelBindingContext
|
var bindingContext = new ModelBindingContext
|
||||||
{
|
{
|
||||||
FallbackToEmptyPrefix = true,
|
FallbackToEmptyPrefix = true,
|
||||||
|
|
@ -135,7 +134,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var modelBinder = new Mock<IModelBinder>();
|
var modelBinder = new Mock<IModelBinder>();
|
||||||
modelBinder
|
modelBinder
|
||||||
.Setup(mb => mb.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
.Setup(mb => mb.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||||
.Returns(Task.FromResult(new ModelBindingResult(null, "someName", false)));
|
.Returns(Task.FromResult(new ModelBindingResult(model: null, key: "someName", isModelSet: false)));
|
||||||
|
|
||||||
var composite = CreateCompositeBinder(modelBinder.Object);
|
var composite = CreateCompositeBinder(modelBinder.Object);
|
||||||
|
|
||||||
|
|
@ -143,10 +142,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var result = await composite.BindModelAsync(bindingContext);
|
var result = await composite.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.NotNull(result);
|
Assert.Null(result);
|
||||||
Assert.False(result.IsModelSet);
|
|
||||||
Assert.Equal("someName", result.Key);
|
|
||||||
Assert.Null(result.Model);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
public class KeyValuePairModelBinderTest
|
public class KeyValuePairModelBinderTest
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task BindModel_MissingKey_ReturnsTrue_AndAddsModelValidationError()
|
public async Task BindModel_MissingKey_ReturnsResult_AndAddsModelValidationError()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var valueProvider = new SimpleHttpValueProvider();
|
var valueProvider = new SimpleHttpValueProvider();
|
||||||
|
|
@ -36,7 +36,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task BindModel_MissingValue_ReturnsTrue_AndAddsModelValidationError()
|
public async Task BindModel_MissingValue_ReturnsResult_AndAddsModelValidationError()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var valueProvider = new SimpleHttpValueProvider();
|
var valueProvider = new SimpleHttpValueProvider();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue