CompositeModelBinder should only validate the object graph at the top
level. DataAnnotationsModelValidator should prefer the container to Metadata.Model Fixes #247
This commit is contained in:
parent
18d3395a5e
commit
904c91d2b9
|
|
@ -15,7 +15,6 @@
|
|||
// See the Apache 2 License for the specific language governing
|
||||
// permissions and limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -44,7 +43,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
private IModelBinder[] Binders { get; set; }
|
||||
|
||||
public virtual async Task<bool> BindModelAsync(ModelBindingContext bindingContext)
|
||||
public virtual async Task<bool> BindModelAsync([NotNull] ModelBindingContext bindingContext)
|
||||
{
|
||||
var newBindingContext = CreateNewBindingContext(bindingContext,
|
||||
bindingContext.ModelName,
|
||||
|
|
@ -66,22 +65,30 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
return false; // something went wrong
|
||||
}
|
||||
|
||||
// run validation and return the model
|
||||
// If we fell back to an empty prefix above and are dealing with simple types,
|
||||
// propagate the non-blank model name through for user clarity in validation errors.
|
||||
// Complex types will reveal their individual properties as model names and do not require this.
|
||||
if (!newBindingContext.ModelMetadata.IsComplexType && String.IsNullOrEmpty(newBindingContext.ModelName))
|
||||
// Only perform validation at the root of the object graph. ValidationNode will recursively walk the graph.
|
||||
// Ignore ComplexModelDto since it essentially wraps the primary object.
|
||||
if (newBindingContext.ModelMetadata.ContainerType == null &&
|
||||
newBindingContext.ModelMetadata.ModelType != typeof(ComplexModelDto))
|
||||
{
|
||||
newBindingContext.ValidationNode = new ModelValidationNode(newBindingContext.ModelMetadata, bindingContext.ModelName);
|
||||
// run validation and return the model
|
||||
// If we fell back to an empty prefix above and are dealing with simple types,
|
||||
// propagate the non-blank model name through for user clarity in validation errors.
|
||||
// Complex types will reveal their individual properties as model names and do not require this.
|
||||
if (!newBindingContext.ModelMetadata.IsComplexType && string.IsNullOrEmpty(newBindingContext.ModelName))
|
||||
{
|
||||
newBindingContext.ValidationNode = new ModelValidationNode(newBindingContext.ModelMetadata,
|
||||
bindingContext.ModelName);
|
||||
}
|
||||
|
||||
var validationContext = new ModelValidationContext(bindingContext.MetadataProvider,
|
||||
bindingContext.ValidatorProviders,
|
||||
bindingContext.ModelState,
|
||||
bindingContext.ModelMetadata,
|
||||
containerMetadata: null);
|
||||
|
||||
newBindingContext.ValidationNode.Validate(validationContext, parentNode: null);
|
||||
}
|
||||
|
||||
var validationContext = new ModelValidationContext(bindingContext.MetadataProvider,
|
||||
bindingContext.ValidatorProviders,
|
||||
bindingContext.ModelState,
|
||||
bindingContext.ModelMetadata,
|
||||
containerMetadata: null);
|
||||
|
||||
newBindingContext.ValidationNode.Validate(validationContext, parentNode: null);
|
||||
|
||||
bindingContext.Model = newBindingContext.Model;
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,8 +40,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
{
|
||||
var metadata = validationContext.ModelMetadata;
|
||||
var memberName = metadata.PropertyName ?? metadata.ModelType.Name;
|
||||
var instance = metadata.Model ?? validationContext.ContainerMetadata.Model;
|
||||
var context = new ValidationContext(instance)
|
||||
var containerMetadata = validationContext.ContainerMetadata;
|
||||
var container = containerMetadata != null ? containerMetadata.Model : null;
|
||||
var context = new ValidationContext(container ?? metadata.Model)
|
||||
{
|
||||
DisplayName = metadata.GetDisplayName(),
|
||||
MemberName = memberName
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
#if NET45
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
|
|
@ -234,10 +235,56 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
Assert.Equal("name", model.Friends[1].LastName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModel_WithDefaultValidators_ValidatesSubProperties()
|
||||
{
|
||||
// Arrange
|
||||
var validatorProvider = new DataAnnotationsModelValidatorProvider();
|
||||
var binder = CreateBinderWithDefaults();
|
||||
var valueProvider = new SimpleHttpValueProvider
|
||||
{
|
||||
{ "user.password", "password-val" },
|
||||
{ "user.confirmpassword", "not-password-val" },
|
||||
};
|
||||
var bindingContext = CreateBindingContext(binder, valueProvider, typeof(User), new[] { validatorProvider });
|
||||
bindingContext.ModelName = "user";
|
||||
|
||||
// Act
|
||||
var isBound = await binder.BindModelAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
var error = Assert.Single(bindingContext.ModelState["user.confirmpassword"].Errors);
|
||||
Assert.Equal("'ConfirmPassword' and 'Password' do not match.", error.ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModel_WithDefaultValidators_ValidatesInstance()
|
||||
{
|
||||
// Arrange
|
||||
var validatorProvider = new DataAnnotationsModelValidatorProvider();
|
||||
var binder = CreateBinderWithDefaults();
|
||||
var valueProvider = new SimpleHttpValueProvider
|
||||
{
|
||||
{ "user.password", "password" },
|
||||
{ "user.confirmpassword", "password" },
|
||||
};
|
||||
var bindingContext = CreateBindingContext(binder, valueProvider, typeof(User), new[] { validatorProvider });
|
||||
bindingContext.ModelName = "user";
|
||||
|
||||
// Act
|
||||
var isBound = await binder.BindModelAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
var error = Assert.Single(bindingContext.ModelState["user"].Errors);
|
||||
Assert.Equal("Password does not meet complexity requirements.", error.ErrorMessage);
|
||||
}
|
||||
|
||||
private static ModelBindingContext CreateBindingContext(IModelBinder binder,
|
||||
IValueProvider valueProvider,
|
||||
Type type)
|
||||
Type type,
|
||||
IEnumerable<IModelValidatorProvider> validatorProviders = null)
|
||||
{
|
||||
validatorProviders = validatorProviders ?? Enumerable.Empty<IModelValidatorProvider>();
|
||||
var metadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||
var bindingContext = new ModelBindingContext
|
||||
{
|
||||
|
|
@ -247,7 +294,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
ModelMetadata = metadataProvider.GetMetadataForType(null, type),
|
||||
ModelState = new ModelStateDictionary(),
|
||||
ValueProvider = valueProvider,
|
||||
ValidatorProviders = Enumerable.Empty<IModelValidatorProvider>()
|
||||
ValidatorProviders = validatorProviders
|
||||
};
|
||||
return bindingContext;
|
||||
}
|
||||
|
|
@ -287,6 +334,22 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
|
||||
public List<Person> Friends { get; set; }
|
||||
}
|
||||
|
||||
private class User : IValidatableObject
|
||||
{
|
||||
public string Password { get; set; }
|
||||
|
||||
[Compare("Password")]
|
||||
public string ConfirmPassword { get; set; }
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
if (Password == "password")
|
||||
{
|
||||
yield return new ValidationResult("Password does not meet complexity requirements.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Reference in New Issue