Fix for #1681 - Model state errors are wrong with attributes like [FromHeader]

The issue here is that a model state error is added with the model name
'doubled'. This is on a fairly obscure code path and the code dates back
to the original WSR git checkin of webapi. There are no wsr tests that
verify this behavior.

The cause here is that our 'greedy' model binders (like
FromHeaderModelBinder) return 'true' whether or not they successfully
found a model, because they don't want other model binders to run.

This also has an effect on the validation system. That means that
validators will run and attempt to validate the model (which may be null).
That's that rare case where we get to this code path.
This commit is contained in:
Ryan Nowak 2014-12-09 15:12:25 -08:00
parent b1b541404c
commit 1631ca1a47
3 changed files with 30 additions and 6 deletions

View File

@ -190,11 +190,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// If the Model at the current node is null and there is no parent, we cannot validate, and the
// DataAnnotationsModelValidator will throw. So we intercept here to provide a catch-all value-required
// validation error
var modelStateKey = ModelBindingHelper.CreatePropertyModelName(ModelStateKey,
ModelMetadata.GetDisplayName());
if (parentNode == null && ModelMetadata.Model == null)
{
modelState.TryAddModelError(modelStateKey, Resources.Validation_ValueNotFound);
modelState.TryAddModelError(ModelStateKey, Resources.Validation_ValueNotFound);
return;
}

View File

@ -96,10 +96,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Null(result.HeaderValue);
Assert.Null(result.HeaderValues);
// This is a bug - the model state error key is wrong here.
var error = Assert.Single(result.ModelStateErrors);
Assert.Equal("transactionId.transactionId", error);
Assert.Equal("transactionId", error);
}
// The action that this test hits will echo back the model-bound values

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#if ASPNET50
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
@ -245,6 +246,27 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.Empty(log);
}
[Fact]
public void Validate_ValidatesIfModelIsNull()
{
// Arrange
var modelMetadata = GetModelMetadata(typeof(ValidateAllPropertiesModel));
var node = new ModelValidationNode(modelMetadata, "theKey");
var context = CreateContext(modelMetadata);
// Act
node.Validate(context);
// Assert
var modelState = Assert.Single(context.ModelState);
Assert.Equal("theKey", modelState.Key);
Assert.Equal(ModelValidationState.Invalid, modelState.Value.ValidationState);
var error = Assert.Single(modelState.Value.Errors);
Assert.Equal("A value is required but was not present in the request.", error.ErrorMessage);
}
[Fact]
[ReplaceCulture]
public void Validate_ValidateAllProperties_AddsValidationErrors()
@ -321,6 +343,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return new DataAnnotationsModelMetadataProvider().GetMetadataForType(() => o, o.GetType());
}
private static ModelMetadata GetModelMetadata(Type type)
{
return new DataAnnotationsModelMetadataProvider().GetMetadataForType(modelAccessor: null, modelType: type);
}
private static ModelValidationContext CreateContext(ModelMetadata metadata = null)
{
var providers = new IModelValidatorProvider[]