Reintroduce model validation

This commit is contained in:
Pranav K 2014-02-27 23:20:28 -08:00
parent cca3f92c5f
commit e434918337
56 changed files with 2498 additions and 457 deletions

View File

@ -1,7 +1,11 @@
namespace MvcSample.Models
using System.ComponentModel.DataAnnotations;
namespace MvcSample.Models
{
public class User
{
[Required]
[MinLength(4)]
public string Name { get; set; }
public string Address { get; set; }
public int Age { get; set; }

View File

@ -1,40 +1,42 @@
{
"version" : "0.1-alpha-*",
"version": "0.1-alpha-*",
"dependencies": {
"Microsoft.AspNet.Abstractions": "0.1-alpha-*",
"Microsoft.AspNet.ConfigurationModel": "0.1-alpha-*",
"Microsoft.AspNet.DependencyInjection" : "0.1-alpha-*",
"Microsoft.AspNet.Routing" : "0.1-alpha-*",
"Microsoft.AspNet.Mvc.ModelBinding" : "",
"Microsoft.AspNet.Mvc.Core" : "",
"Microsoft.AspNet.Mvc" : "",
"Microsoft.AspNet.DependencyInjection": "0.1-alpha-*",
"Microsoft.AspNet.Routing": "0.1-alpha-*",
"Microsoft.AspNet.Mvc.ModelBinding": "",
"Microsoft.AspNet.Mvc.Core": "",
"Microsoft.AspNet.Mvc": "",
"Microsoft.AspNet.Mvc.Razor": "",
"Microsoft.AspNet.Mvc.Rendering" : ""
"Microsoft.AspNet.Mvc.Rendering": ""
},
"configurations": {
"net45": {
"dependencies": {
"Autofac": "3.3.0",
"Owin": "1.0",
"Microsoft.AspNet.DependencyInjection.Autofac": "0.1-alpha-*",
"Microsoft.Owin": "2.1.0",
"Microsoft.Owin.Diagnostics": "2.1.0",
"Microsoft.Owin.Hosting": "2.1.0",
"Microsoft.Owin.Host.HttpListener": "2.1.0",
"Microsoft.AspNet.AppBuilderSupport": "0.1-alpha-*"
}
"Autofac": "3.3.0",
"Owin": "1.0",
"Microsoft.AspNet.DependencyInjection.Autofac": "0.1-alpha-*",
"Microsoft.Owin": "2.1.0",
"Microsoft.Owin.Diagnostics": "2.1.0",
"Microsoft.Owin.Hosting": "2.1.0",
"Microsoft.Owin.Host.HttpListener": "2.1.0",
"Microsoft.AspNet.AppBuilderSupport": "0.1-alpha-*",
"System.ComponentModel.DataAnnotations": ""
}
},
"k10" : {
"dependencies": {
"System.ComponentModel": "4.0.0.0",
"System.Console": "4.0.0.0",
"System.Diagnostics.Debug": "4.0.10.0",
"System.Diagnostics.Tools": "4.0.0.0",
"System.Dynamic.Runtime": "4.0.0.0",
"System.Runtime": "4.0.20.0",
"System.Runtime.InteropServices": "4.0.10.0",
"System.Threading.Tasks": "4.0.0.0"
}
"k10": {
"dependencies": {
"System.ComponentModel": "4.0.0.0",
"System.Console": "4.0.0.0",
"System.Diagnostics.Debug": "4.0.10.0",
"System.Diagnostics.Tools": "4.0.0.0",
"System.Dynamic.Runtime": "4.0.0.0",
"System.Runtime": "4.0.20.0",
"System.Runtime.InteropServices": "4.0.10.0",
"System.Threading.Tasks": "4.0.0.0",
"Microsoft.ComponentModel.DataAnnotations": "0.1-alpha-*"
}
}
}
}

View File

@ -29,6 +29,7 @@ namespace Microsoft.AspNet.Mvc.Internal
ModelMetadata = modelMetadata,
ModelBinder = actionBindingContext.ModelBinder,
ValueProvider = actionBindingContext.ValueProvider,
ValidatorProviders = actionBindingContext.ValidatorProviders,
MetadataProvider = metadataProvider,
HttpContext = actionBindingContext.ActionContext.HttpContext,
FallbackToEmptyPrefix = true

View File

@ -1,4 +1,5 @@
using Microsoft.AspNet.Mvc.ModelBinding;
using System.Collections.Generic;
using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
{
@ -8,13 +9,15 @@ namespace Microsoft.AspNet.Mvc
IModelMetadataProvider metadataProvider,
IModelBinder modelBinder,
IValueProvider valueProvider,
IInputFormatter inputFormatter)
IInputFormatter inputFormatter,
IEnumerable<IModelValidatorProvider> validatorProviders)
{
ActionContext = context;
MetadataProvider = metadataProvider;
ModelBinder = modelBinder;
ValueProvider = valueProvider;
InputFormatter = inputFormatter;
ValidatorProviders = validatorProviders;
}
public ActionContext ActionContext { get; private set; }
@ -26,5 +29,7 @@ namespace Microsoft.AspNet.Mvc
public IValueProvider ValueProvider { get; private set; }
public IInputFormatter InputFormatter { get; private set; }
public IEnumerable<IModelValidatorProvider> ValidatorProviders { get; private set; }
}
}

View File

@ -10,17 +10,20 @@ namespace Microsoft.AspNet.Mvc
private readonly IModelMetadataProvider _modelMetadataProvider;
private readonly IEnumerable<IModelBinder> _modelBinders;
private readonly IEnumerable<IValueProviderFactory> _valueProviderFactories;
private readonly IEnumerable<IInputFormatter> _bodyReaders;
private readonly IEnumerable<IInputFormatter> _inputFormatters;
private readonly IEnumerable<IModelValidatorProvider> _validatorProviders;
public DefaultActionBindingContextProvider(IModelMetadataProvider modelMetadataProvider,
IEnumerable<IModelBinder> modelBinders,
IEnumerable<IValueProviderFactory> valueProviderFactories,
IEnumerable<IInputFormatter> bodyReaders)
IEnumerable<IInputFormatter> inputFormatters,
IEnumerable<IModelValidatorProvider> validatorProviders)
{
_modelMetadataProvider = modelMetadataProvider;
_modelBinders = modelBinders.OrderBy(binder => binder.GetType() == typeof(ComplexModelDtoModelBinder) ? 1 : 0);
_valueProviderFactories = valueProviderFactories;
_bodyReaders = bodyReaders;
_inputFormatters = inputFormatters;
_validatorProviders = validatorProviders;
}
public async Task<ActionBindingContext> GetActionBindingContextAsync(ActionContext actionContext)
@ -35,7 +38,8 @@ namespace Microsoft.AspNet.Mvc
_modelMetadataProvider,
new CompositeModelBinder(_modelBinders),
new CompositeValueProvider(valueProviders),
new CompositeInputFormatter(_bodyReaders)
new CompositeInputFormatter(_inputFormatters),
_validatorProviders
);
}
}

View File

@ -148,7 +148,6 @@ namespace Microsoft.AspNet.Mvc
actionBindingContext.ModelBinder.BindModel(modelBindingContext);
parameterValues[parameter.Name] = modelBindingContext.Model;
}
}
return parameterValues;

View File

@ -60,8 +60,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
if (bindingContext.ModelBinder.BindModel(innerBindingContext))
{
boundValue = innerBindingContext.Model;
// TODO: validation
// bindingContext.ValidationNode.ChildNodes.Add(innerBindingContext.ValidationNode);
bindingContext.ValidationNode.ChildNodes.Add(innerBindingContext.ValidationNode);
}
boundCollection.Add(ModelBindingHelper.CastOrDefault<TElement>(boundValue));
}
@ -114,9 +113,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
didBind = true;
boundValue = childBindingContext.Model;
// TODO: Validation
// merge validation up
// bindingContext.ValidationNode.ChildNodes.Add(childBindingContext.ValidationNode);
bindingContext.ValidationNode.ChildNodes.Add(childBindingContext.ValidationNode);
}
// infinite size collection stops on first bind failure

View File

@ -24,7 +24,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
if (bindingContext.ModelBinder.BindModel(propertyBindingContext))
{
dto.Results[propertyMetadata] = new ComplexModelDtoResult(propertyBindingContext.Model/*, propertyBindingContext.ValidationNode*/);
dto.Results[propertyMetadata] = new ComplexModelDtoResult(propertyBindingContext.Model, propertyBindingContext.ValidationNode);
}
}

View File

@ -1,21 +1,18 @@
namespace Microsoft.AspNet.Mvc.ModelBinding
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public sealed class ComplexModelDtoResult
{
public ComplexModelDtoResult(object model/*, ModelValidationNode validationNode*/)
public ComplexModelDtoResult(object model,
[NotNull] ModelValidationNode validationNode)
{
// TODO: Validation
//if (validationNode == null)
//{
// throw Error.ArgumentNull("validationNode");
//}
Model = model;
//ValidationNode = validationNode;
ValidationNode = validationNode;
}
public object Model { get; private set; }
//public ModelValidationNode ValidationNode { get; private set; }
public ModelValidationNode ValidationNode { get; private set; }
}
}

View File

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
@ -29,14 +28,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public virtual bool BindModel(ModelBindingContext bindingContext)
{
ModelBindingContext newBindingContext = CreateNewBindingContext(bindingContext, bindingContext.ModelName);
var newBindingContext = CreateNewBindingContext(bindingContext,
bindingContext.ModelName,
reuseValidationNode: true);
bool boundSuccessfully = TryBind(newBindingContext);
if (!boundSuccessfully && !String.IsNullOrEmpty(bindingContext.ModelName)
if (!boundSuccessfully && !string.IsNullOrEmpty(bindingContext.ModelName)
&& bindingContext.FallbackToEmptyPrefix)
{
// fallback to empty prefix?
newBindingContext = CreateNewBindingContext(bindingContext, modelName: String.Empty);
newBindingContext = CreateNewBindingContext(bindingContext,
modelName: string.Empty,
reuseValidationNode: false);
boundSuccessfully = TryBind(newBindingContext);
}
@ -49,31 +52,28 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// 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.
// TODO: Validation
//if (!newBindingContext.ModelMetadata.IsComplexType && String.IsNullOrEmpty(newBindingContext.ModelName))
//{
// newBindingContext.ValidationNode = new Validation.ModelValidationNode(newBindingContext.ModelMetadata, bindingContext.ModelName);
//}
if (!newBindingContext.ModelMetadata.IsComplexType && String.IsNullOrEmpty(newBindingContext.ModelName))
{
newBindingContext.ValidationNode = new ModelValidationNode(newBindingContext.ModelMetadata, bindingContext.ModelName);
}
//newBindingContext.ValidationNode.Validate(context, null /* parentNode */);
var validationContext = new ModelValidationContext(bindingContext.ModelMetadata,
bindingContext.ModelState,
bindingContext.MetadataProvider,
bindingContext.ValidatorProviders);
newBindingContext.ValidationNode.Validate(validationContext, parentNode: null);
bindingContext.Model = newBindingContext.Model;
return true;
}
private bool TryBind(ModelBindingContext bindingContext)
private bool TryBind([NotNull] ModelBindingContext bindingContext)
{
// TODO: The body of this method existed as HttpActionContextExtensions.Bind. We might have to refactor it into
// something that is shared.
if (bindingContext == null)
{
throw Error.ArgumentNull("bindingContext");
}
// TODO: RuntimeHelpers.EnsureSufficientExecutionStack does not exist in the CoreCLR.
// Protects against stack overflow for deeply nested model binding
// RuntimeHelpers.EnsureSufficientExecutionStack();
foreach (IModelBinder binder in Binders)
foreach (var binder in Binders)
{
if (binder.BindModel(bindingContext))
{
@ -85,7 +85,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return false;
}
private static ModelBindingContext CreateNewBindingContext(ModelBindingContext oldBindingContext, string modelName)
private static ModelBindingContext CreateNewBindingContext(ModelBindingContext oldBindingContext,
string modelName,
bool reuseValidationNode)
{
var newBindingContext = new ModelBindingContext
{
@ -93,17 +95,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
ModelName = modelName,
ModelState = oldBindingContext.ModelState,
ValueProvider = oldBindingContext.ValueProvider,
ValidatorProviders = oldBindingContext.ValidatorProviders,
MetadataProvider = oldBindingContext.MetadataProvider,
ModelBinder = oldBindingContext.ModelBinder,
HttpContext = oldBindingContext.HttpContext
};
// TODO: Validation
//// validation is expensive to create, so copy it over if we can
//if (Object.ReferenceEquals(modelName, oldBindingContext.ModelName))
//{
// newBindingContext.ValidationNode = oldBindingContext.ValidationNode;
//}
// validation is expensive to create, so copy it over if we can
if (reuseValidationNode)
{
newBindingContext.ValidationNode = oldBindingContext.ValidationNode;
}
return newBindingContext;
}

View File

@ -37,8 +37,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
object untypedModel = propertyBindingContext.Model;
model = ModelBindingHelper.CastOrDefault<TModel>(untypedModel);
// TODO: Revive once we get validation
// parentBindingContext.ValidationNode.ChildNodes.Add(propertyBindingContext.ValidationNode);
parentBindingContext.ValidationNode.ChildNodes.Add(propertyBindingContext.ValidationNode);
return true;
}

View File

@ -21,13 +21,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
EnsureModel(bindingContext);
IEnumerable<ModelMetadata> propertyMetadatas = GetMetadataForProperties(bindingContext);
ComplexModelDto dto = CreateAndPopulateDto(bindingContext, propertyMetadatas);
var propertyMetadatas = GetMetadataForProperties(bindingContext);
var dto = CreateAndPopulateDto(bindingContext, propertyMetadatas);
// post-processing, e.g. property setters and hooking up validation
ProcessDto(bindingContext, dto);
// TODO: Validation
// bindingContext.ValidationNode.ValidateAllProperties = true; // complex models require full validation
// complex models require full validation
bindingContext.ValidationNode.ValidateAllProperties = true;
return true;
}
@ -39,7 +39,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private static bool CanBindType(Type modelType)
{
// Simple types cannot use this binder
bool isComplexType = !modelType.HasStringConverter();
var isComplexType = !modelType.HasStringConverter();
if (!isComplexType)
{
return false;
@ -87,8 +87,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private ComplexModelDto CreateAndPopulateDto(ModelBindingContext bindingContext, IEnumerable<ModelMetadata> propertyMetadatas)
{
// create a DTO and call into the DTO binder
ComplexModelDto originalDto = new ComplexModelDto(bindingContext.ModelMetadata, propertyMetadatas);
ModelBindingContext dtoBindingContext = new ModelBindingContext(bindingContext)
var originalDto = new ComplexModelDto(bindingContext.ModelMetadata, propertyMetadatas);
var dtoBindingContext = new ModelBindingContext(bindingContext)
{
ModelMetadata = bindingContext.MetadataProvider.GetMetadataForType(() => originalDto, typeof(ComplexModelDto)),
ModelName = bindingContext.ModelName
@ -105,24 +105,26 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return Activator.CreateInstance(bindingContext.ModelType);
}
//// Called when the property setter null check failed, allows us to add our own error message to ModelState.
//internal static EventHandler<ModelValidatedEventArgs> CreateNullCheckFailedHandler(ModelMetadata modelMetadata, object incomingValue)
//{
// return (sender, e) =>
// {
// ModelValidationNode validationNode = (ModelValidationNode)sender;
// ModelStateDictionary modelState = e.ActionContext.ModelState;
// Called when the property setter null check failed, allows us to add our own error message to ModelState.
internal static EventHandler<ModelValidatedEventArgs> CreateNullCheckFailedHandler(ModelMetadata modelMetadata, object incomingValue)
{
return (sender, e) =>
{
var validationNode = (ModelValidationNode)sender;
var modelState = e.ValidationContext.ModelState;
// if (modelState.IsValidField(validationNode.ModelStateKey))
// {
// string errorMessage = ModelBinderConfig.ValueRequiredErrorMessageProvider(e.ActionContext, modelMetadata, incomingValue);
// if (errorMessage != null)
// {
// modelState.AddModelError(validationNode.ModelStateKey, errorMessage);
// }
// }
// };
//}
if (modelState.IsValidField(validationNode.ModelStateKey))
{
// TODO: Revive ModelBinderConfig
// string errorMessage = ModelBinderConfig.ValueRequiredErrorMessageProvider(e.ValidationContext, modelMetadata, incomingValue);
var errorMessage = e.ValidationContext.ModelMetadata.PropertyName + " is required";
if (errorMessage != null)
{
modelState.AddModelError(validationNode.ModelStateKey, errorMessage);
}
}
};
}
protected virtual void EnsureModel(ModelBindingContext bindingContext)
{
@ -134,12 +136,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
protected virtual IEnumerable<ModelMetadata> GetMetadataForProperties(ModelBindingContext bindingContext)
{
// TODO: Revive required properties. This has a dependency on HttpBindingAttribute and DataAnnotations
// keep a set of the required properties so that we can cross-reference bound properties later
HashSet<string> requiredProperties = new HashSet<string>();
// Dictionary<string, ModelValidator> requiredValidators;
HashSet<string> skipProperties = new HashSet<string>();
// GetRequiredPropertiesCollection(bindingContext, out requiredProperties, out skipProperties);
HashSet<string> requiredProperties;
Dictionary<string, IModelValidator> requiredValidators;
HashSet<string> skipProperties;
GetRequiredPropertiesCollection(bindingContext, out requiredProperties, out requiredValidators, out skipProperties);
return from propertyMetadata in bindingContext.ModelMetadata.Properties
let propertyName = propertyMetadata.PropertyName
@ -150,102 +151,84 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private static object GetPropertyDefaultValue(PropertyInfo propertyInfo)
{
DefaultValueAttribute attr = propertyInfo.GetCustomAttribute<DefaultValueAttribute>();
var attr = propertyInfo.GetCustomAttribute<DefaultValueAttribute>();
return (attr != null) ? attr.Value : null;
}
//internal static void GetRequiredPropertiesCollection(ModelBindingContext bindingContext, out HashSet<string> requiredProperties, out HashSet<string> skipProperties)
//{
// requiredProperties = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
// // requiredValidators = new Dictionary<string, ModelValidator>(StringComparer.OrdinalIgnoreCase);
// skipProperties = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
internal static void GetRequiredPropertiesCollection(ModelBindingContext bindingContext,
out HashSet<string> requiredProperties,
out Dictionary<string, IModelValidator> requiredValidators,
out HashSet<string> skipProperties)
{
requiredProperties = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
requiredValidators = new Dictionary<string, IModelValidator>(StringComparer.OrdinalIgnoreCase);
skipProperties = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
// // Use attributes on the property before attributes on the type.
// Type modelType = bindingContext.ModelType;
// ICustomTypeDescriptor modelDescriptor = new AssociatedMetadataTypeTypeDescriptionProvider(modelType)
// .GetTypeDescriptor(modelType);
// TODO: HttpBindingBehaviorAttribute
var modelTypeInfo = bindingContext.ModelType.GetTypeInfo();
foreach (var propertyMetadata in bindingContext.ModelMetadata.Properties)
{
var propertyName = propertyMetadata.PropertyName;
var requiredValidator = bindingContext.GetValidators(propertyMetadata)
.FirstOrDefault(v => v.IsRequired);
// TODO: Revive HttpBindingBehaviorAttribute
// PropertyDescriptorCollection propertyDescriptors = modelDescriptor.GetProperties();
// // TODO: Revive HttpBindingBehavior
// // HttpBindingBehaviorAttribute typeAttr = modelDescriptor.GetAttributes().OfType<HttpBindingBehaviorAttribute>().SingleOrDefault();
// foreach (PropertyDescriptor propertyDescriptor in propertyDescriptors)
// {
// string propertyName = propertyDescriptor.Name;
// ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[propertyName];
// // ModelValidator requiredValidator = context.GetValidators(propertyMetadata).Where(v => v.IsRequired).FirstOrDefault();
// // requiredValidators[propertyName] = requiredValidator;
// HttpBindingBehaviorAttribute propAttr = propertyDescriptor.Attributes.OfType<HttpBindingBehaviorAttribute>().SingleOrDefault();
// HttpBindingBehaviorAttribute workingAttr = propAttr ?? typeAttr;
// if (workingAttr != null)
// {
// switch (workingAttr.Behavior)
// {
// case HttpBindingBehavior.Required:
// requiredProperties.Add(propertyName);
// break;
// case HttpBindingBehavior.Never:
// skipProperties.Add(propertyName);
// break;
// }
// }
// else if (requiredValidator != null)
// {
// requiredProperties.Add(propertyName);
// }
// }
//}
if (requiredValidator != null)
{
requiredValidators[propertyName] = requiredValidator;
requiredProperties.Add(propertyName);
}
}
}
internal void ProcessDto(ModelBindingContext bindingContext, ComplexModelDto dto)
{
// TODO: Uncomment this once we revive validation
HashSet<string> requiredProperties;
Dictionary<string, IModelValidator> requiredValidators;
HashSet<string> skipProperties;
GetRequiredPropertiesCollection(bindingContext, out requiredProperties, out requiredValidators, out skipProperties);
//HashSet<string> requiredProperties;
//// Dictionary<string, ModelValidator> requiredValidators;
//HashSet<string> skipProperties;
//GetRequiredPropertiesCollection(context, bindingContext, out requiredProperties, out requiredValidators, out skipProperties);
// Eliminate provided properties from requiredProperties; leaving just *missing* required properties.
requiredProperties.ExceptWith(dto.Results.Select(r => r.Key.PropertyName));
//// Eliminate provided properties from requiredProperties; leaving just *missing* required properties.
//requiredProperties.ExceptWith(dto.Results.Select(r => r.Key.PropertyName));
foreach (var missingRequiredProperty in requiredProperties)
{
var addedError = false;
var modelStateKey = ModelBindingHelper.CreatePropertyModelName(
bindingContext.ValidationNode.ModelStateKey, missingRequiredProperty);
//foreach (string missingRequiredProperty in requiredProperties)
//{
// string modelStateKey = ModelBindingHelper.CreatePropertyModelName(
// bindingContext.ValidationNode.ModelStateKey, missingRequiredProperty);
// Update Model as SetProperty() would: Place null value where validator will check for non-null. This
// ensures a failure result from a required validator (if any) even for a non-nullable property.
// (Otherwise, propertyMetadata.Model is likely already null.)
var propertyMetadata = bindingContext.PropertyMetadata[missingRequiredProperty];
propertyMetadata.Model = null;
// // Update Model as SetProperty() would: Place null value where validator will check for non-null. This
// // ensures a failure result from a required validator (if any) even for a non-nullable property.
// // (Otherwise, propertyMetadata.Model is likely already null.)
// ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[missingRequiredProperty];
// propertyMetadata.Model = null;
// Execute validator (if any) to get custom error message.
IModelValidator validator;
if (requiredValidators.TryGetValue(missingRequiredProperty, out validator))
{
addedError = RunValidator(validator, bindingContext, propertyMetadata, modelStateKey);
}
// // Execute validator (if any) to get custom error message.
// ModelValidator validator = requiredValidators[missingRequiredProperty];
// bool addedError = RunValidator(validator, bindingContext, propertyMetadata, modelStateKey);
// // Fall back to default message if HttpBindingBehaviorAttribute required this property or validator
// // (oddly) succeeded.
// if (!addedError)
// {
// bindingContext.ModelState.AddModelError(modelStateKey,
// Error.Format(SRResources.MissingRequiredMember, missingRequiredProperty));
// }
//}
// Fall back to default message if HttpBindingBehaviorAttribute required this property or validator
// (oddly) succeeded.
if (!addedError)
{
bindingContext.ModelState.AddModelError(
modelStateKey,
Resources.FormatMissingRequiredMember(missingRequiredProperty));
}
}
// for each property that was bound, call the setter, recording exceptions as necessary
foreach (var entry in dto.Results)
{
ModelMetadata propertyMetadata = entry.Key;
ComplexModelDtoResult dtoResult = entry.Value;
var propertyMetadata = entry.Key;
var dtoResult = entry.Value;
if (dtoResult != null)
{
SetProperty(bindingContext, propertyMetadata, dtoResult);
// TODO: Validation
// bindingContext.ValidationNode.ChildNodes.Add(dtoResult.ValidationNode);
bindingContext.ValidationNode.ChildNodes.Add(dtoResult.ValidationNode);
}
}
}
@ -253,14 +236,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We're recording this exception so that we can act on it later.")]
protected virtual void SetProperty(ModelBindingContext bindingContext,
ModelMetadata propertyMetadata,
ComplexModelDtoResult dtoResult
/*, ModelValidator requiredValidator*/)
ComplexModelDtoResult dtoResult)
{
// TODO: This used TypeDescriptor which is no longer available. Lookups performed using System.ComponentModel were
// cached. To maintain parity, we'll need to cache property lookups.
PropertyInfo property = bindingContext.ModelType
.GetRuntimeProperties()
.FirstOrDefault(p => p.Name.Equals(propertyMetadata.PropertyName, StringComparison.OrdinalIgnoreCase));
var property = bindingContext.ModelType
.GetProperty(propertyMetadata.PropertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
if (property == null || !property.CanWrite)
{
@ -268,21 +247,28 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return;
}
object value = dtoResult.Model ?? GetPropertyDefaultValue(property);
var value = dtoResult.Model ?? GetPropertyDefaultValue(property);
propertyMetadata.Model = value;
//// 'Required' validators need to run first so that we can provide useful error messages if
//// the property setters throw, e.g. if we're setting entity keys to null. See comments in
//// DefaultModelBinder.SetProperty() for more information.
//if (value == null)
//{
// string modelStateKey = dtoResult.ValidationNode.ModelStateKey;
// if (bindingContext.ModelState.IsValidField(modelStateKey))
// {
// RunValidator(requiredValidator, bindingContext, propertyMetadata, modelStateKey);
// }
//}
// 'Required' validators need to run first so that we can provide useful error messages if
// the property setters throw, e.g. if we're setting entity keys to null.
if (value == null)
{
var modelStateKey = dtoResult.ValidationNode.ModelStateKey;
if (bindingContext.ModelState.IsValidField(modelStateKey))
{
var requiredValidator = bindingContext.GetValidators(propertyMetadata).FirstOrDefault(v => v.IsRequired);
if (requiredValidator != null)
{
var validationContext = bindingContext.CreateValidationContext(propertyMetadata);
foreach (var validationResult in requiredValidator.Validate(validationContext))
{
bindingContext.ModelState.AddModelError(modelStateKey, validationResult.Message);
}
}
}
}
if (value != null || property.PropertyType.AllowsNullValue())
{
@ -293,39 +279,39 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
catch (Exception ex)
{
// don't display a duplicate error message if a binding error has already occurred for this field
//string modelStateKey = dtoResult.ValidationNode.ModelStateKey;
//if (bindingContext.ModelState.IsValidField(modelStateKey))
//{
bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex);
//}
var modelStateKey = dtoResult.ValidationNode.ModelStateKey;
if (bindingContext.ModelState.IsValidField(modelStateKey))
{
bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex);
}
}
}
else
{
// trying to set a non-nullable value type to null, need to make sure there's a message
//string modelStateKey = dtoResult.ValidationNode.ModelStateKey;
//if (bindingContext.ModelState.IsValidField(modelStateKey))
//{
// dtoResult.ValidationNode.Validated += CreateNullCheckFailedHandler(propertyMetadata, value);
//}
var modelStateKey = dtoResult.ValidationNode.ModelStateKey;
if (bindingContext.ModelState.IsValidField(modelStateKey))
{
dtoResult.ValidationNode.Validated += CreateNullCheckFailedHandler(propertyMetadata, value);
}
}
}
//// Returns true if validator execution adds a model error.
//private static bool RunValidator(ModelValidator validator, ModelBindingContext bindingContext,
// ModelMetadata propertyMetadata, string modelStateKey)
//{
// bool addedError = false;
// if (validator != null)
// {
// foreach (ModelValidationResult validationResult in validator.Validate(propertyMetadata, bindingContext.Model))
// {
// bindingContext.ModelState.AddModelError(modelStateKey, validationResult.Message);
// addedError = true;
// }
// }
// Returns true if validator execution adds a model error.
private static bool RunValidator(IModelValidator validator,
ModelBindingContext bindingContext,
ModelMetadata propertyMetadata,
string modelStateKey)
{
var validationContext = bindingContext.CreateValidationContext(propertyMetadata);
// return addedError;
//}
var addedError = false;
foreach (var validationResult in validator.Validate(validationContext))
{
bindingContext.ModelState.AddModelError(modelStateKey, validationResult.Message);
addedError = true;
}
return addedError;
}
}
}

View File

@ -1,4 +1,5 @@
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
using System.Linq;
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
@ -17,17 +18,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
object model = valueProviderResult.RawValue;
ModelBindingHelper.ReplaceEmptyStringWithNull(bindingContext.ModelMetadata, ref model);
bindingContext.Model = model;
if (bindingContext.ModelMetadata.IsComplexType)
{
// TODO: Validation
//IBodyModelValidator validator = services.GetBodyModelValidator();
//ModelMetadataProvider metadataProvider = services.GetModelMetadataProvider();
//if (validator != null && metadataProvider != null)
//{
// validator.Validate(model, bindingContext.ModelType, metadataProvider, context, bindingContext.ModelName);
//}
}
// TODO: Determine if we need IBodyValidator here.
return true;
}

View File

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
namespace Microsoft.AspNet.Mvc.ModelBinding.Internal
{
public static class DictionaryHelper
{
public static IEnumerable<KeyValuePair<string, TValue>> FindKeysWithPrefix<TValue>([NotNull] IDictionary<string, TValue> dictionary,
[NotNull] string prefix)
{
TValue exactMatchValue;
if (dictionary.TryGetValue(prefix, out exactMatchValue))
{
yield return new KeyValuePair<string, TValue>(prefix, exactMatchValue);
}
foreach (var entry in dictionary)
{
var key = entry.Key;
if (key.Length <= prefix.Length)
{
continue;
}
if (!key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
continue;
}
// Everything is prefixed by the empty string
if (prefix.Length == 0)
{
yield return entry;
}
else
{
char charAfterPrefix = key[prefix.Length];
switch (charAfterPrefix)
{
case '[':
case '.':
yield return entry;
break;
}
}
}
}
}
}

View File

@ -1,6 +1,6 @@
using System;
namespace Microsoft.AspNet.Mvc.ModelBinding
namespace Microsoft.AspNet.Mvc.ModelBinding.Internal
{
internal class EfficientTypePropertyKey<T1, T2> : Tuple<T1, T2>
{

View File

@ -0,0 +1,32 @@
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.AspNet.Mvc.ModelBinding.Internal
{
internal static class ModelBindingContextExtensions
{
public static IEnumerable<IModelValidator> GetValidators([NotNull] this ModelBindingContext context,
[NotNull] ModelMetadata metadata)
{
return context.ValidatorProviders.SelectMany(vp => vp.GetValidators(metadata))
.Where(v => v != null);
}
public static ModelValidationContext CreateValidationContext([NotNull] this ModelBindingContext context,
[NotNull] ModelMetadata metadata)
{
return new ModelValidationContext(metadata,
context.ModelState,
context.MetadataProvider,
context.ValidatorProviders);
}
public static IEnumerable<ModelValidationResult> Validate([NotNull] this ModelBindingContext bindingContext)
{
var validators = GetValidators(bindingContext, bindingContext.ModelMetadata);
var compositeValidator = new CompositeModelValidator(validators);
var modelValidationContext = CreateValidationContext(bindingContext, bindingContext.ModelMetadata);
return compositeValidator.Validate(modelValidationContext);
}
}
}

View File

@ -14,28 +14,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
private readonly ConcurrentDictionary<Type, TypeInformation> _typeInfoCache = new ConcurrentDictionary<Type, TypeInformation>();
public IEnumerable<ModelMetadata> GetMetadataForProperties(object container, Type containerType)
public IEnumerable<ModelMetadata> GetMetadataForProperties(object container, [NotNull] Type containerType)
{
if (containerType == null)
{
throw Error.ArgumentNull("containerType");
}
return GetMetadataForPropertiesCore(container, containerType);
}
public ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName)
public ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, [NotNull] Type containerType, [NotNull] string propertyName)
{
if (containerType == null)
{
throw Error.ArgumentNull("containerType");
}
if (String.IsNullOrEmpty(propertyName))
if (string.IsNullOrEmpty(propertyName))
{
throw Error.ArgumentNullOrEmpty("propertyName");
}
TypeInformation typeInfo = GetTypeInformation(containerType);
var typeInfo = GetTypeInformation(containerType);
PropertyInformation propertyInfo;
if (!typeInfo.Properties.TryGetValue(propertyName, out propertyInfo))
{
@ -45,13 +36,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return CreateMetadataFromPrototype(propertyInfo.Prototype, modelAccessor);
}
public ModelMetadata GetMetadataForType(Func<object> modelAccessor, Type modelType)
public ModelMetadata GetMetadataForType(Func<object> modelAccessor, [NotNull] Type modelType)
{
if (modelType == null)
{
throw Error.ArgumentNull("modelType");
}
TModelMetadata prototype = GetTypeInformation(modelType).Prototype;
return CreateMetadataFromPrototype(prototype, modelAccessor);
}
@ -68,10 +54,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private IEnumerable<ModelMetadata> GetMetadataForPropertiesCore(object container, Type containerType)
{
TypeInformation typeInfo = GetTypeInformation(containerType);
foreach (KeyValuePair<string, PropertyInformation> kvp in typeInfo.Properties)
var typeInfo = GetTypeInformation(containerType);
foreach (var kvp in typeInfo.Properties)
{
PropertyInformation propertyInfo = kvp.Value;
var propertyInfo = kvp.Value;
Func<object> modelAccessor = null;
if (container != null)
{
@ -96,24 +82,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private TypeInformation CreateTypeInformation(Type type, IEnumerable<Attribute> associatedAttributes)
{
TypeInfo typeInfo = type.GetTypeInfo();
IEnumerable<Attribute> attributes = typeInfo.GetCustomAttributes();
var typeInfo = type.GetTypeInfo();
var attributes = typeInfo.GetCustomAttributes();
if (associatedAttributes != null)
{
attributes = attributes.Concat(associatedAttributes);
}
TypeInformation info = new TypeInformation
var info = new TypeInformation
{
Prototype = CreateMetadataPrototype(attributes, containerType: null, modelType: type, propertyName: null)
};
// TODO: Determine if we need this. TypeDescriptor does not exist in CoreCLR.
//ICustomTypeDescriptor typeDescriptor = TypeDescriptorHelper.Get(type);
//info.TypeDescriptor = typeDescriptor;
Dictionary<string, PropertyInformation> properties = new Dictionary<string, PropertyInformation>();
// TODO: Figure out if there's a better way to identify public non-static properties
foreach (PropertyInfo property in type.GetRuntimeProperties().Where(p => p.GetMethod.IsPublic && !p.GetMethod.IsStatic))
var properties = new Dictionary<string, PropertyInformation>();
foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
// Avoid re-generating a property descriptor if one has already been generated for the property name
if (!properties.ContainsKey(property.Name))
@ -128,21 +109,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private PropertyInformation CreatePropertyInformation(Type containerType, PropertyInfo property)
{
PropertyInformation info = new PropertyInformation();
var info = new PropertyInformation();
info.ValueAccessor = CreatePropertyValueAccessor(property);
info.Prototype = CreateMetadataPrototype(property.GetCustomAttributes().Cast<Attribute>(), containerType, property.PropertyType, property.Name);
info.Prototype = CreateMetadataPrototype(property.GetCustomAttributes(),
containerType,
property.PropertyType,
property.Name);
return info;
}
private static Func<object, object> CreatePropertyValueAccessor(PropertyInfo property)
{
Type declaringType = property.DeclaringType;
TypeInfo declaringTypeInfo = declaringType.GetTypeInfo();
var declaringType = property.DeclaringType;
var declaringTypeInfo = declaringType.GetTypeInfo();
if (declaringTypeInfo.IsVisible)
{
if (property.CanRead)
{
MethodInfo getMethodInfo = property.GetMethod;
var getMethodInfo = property.GetMethod;
if (getMethodInfo != null)
{
return CreateDynamicValueAccessor(getMethodInfo, declaringType, property.Name);
@ -161,10 +145,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
Contract.Assert(getMethodInfo != null && getMethodInfo.IsPublic && !getMethodInfo.IsStatic);
TypeInfo declaringTypeInfo = declaringType.GetTypeInfo();
Type propertyType = getMethodInfo.ReturnType;
DynamicMethod dynamicMethod = new DynamicMethod("Get" + propertyName + "From" + declaringType.Name, typeof(object), new Type[] { typeof(object) });
ILGenerator ilg = dynamicMethod.GetILGenerator();
var declaringTypeInfo = declaringType.GetTypeInfo();
var propertyType = getMethodInfo.ReturnType;
var dynamicMethod = new DynamicMethod("Get" + propertyName + "From" + declaringType.Name,
typeof(object),
new [] { typeof(object) });
var ilg = dynamicMethod.GetILGenerator();
// Load the container onto the stack, convert from object => declaring type for the property
ilg.Emit(OpCodes.Ldarg_0);

View File

@ -1,7 +1,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.AspNet.Abstractions;
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
@ -9,6 +10,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
private string _modelName;
private ModelStateDictionary _modelState;
private Dictionary<string, ModelMetadata> _propertyMetadata;
private ModelValidationNode _validationNode;
public ModelBindingContext()
{
@ -24,6 +27,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
ValueProvider = bindingContext.ValueProvider;
MetadataProvider = bindingContext.MetadataProvider;
ModelBinder = bindingContext.ModelBinder;
ValidatorProviders = bindingContext.ValidatorProviders;
HttpContext = bindingContext.HttpContext;
}
}
@ -102,6 +106,38 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
set;
}
public IEnumerable<IModelValidatorProvider> ValidatorProviders
{
get;
set;
}
public IDictionary<string, ModelMetadata> PropertyMetadata
{
get
{
if (_propertyMetadata == null)
{
_propertyMetadata = ModelMetadata.Properties.ToDictionary(m => m.PropertyName, StringComparer.OrdinalIgnoreCase);
}
return _propertyMetadata;
}
}
public ModelValidationNode ValidationNode
{
get
{
if (_validationNode == null)
{
_validationNode = new ModelValidationNode(ModelMetadata, ModelName);
}
return _validationNode;
}
set { _validationNode = value; }
}
private void EnsureModelMetadata()
{
if (ModelMetadata == null)

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
@ -12,7 +13,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
}
public ModelStateDictionary([NotNull]ModelStateDictionary dictionary)
public ModelStateDictionary([NotNull] ModelStateDictionary dictionary)
{
foreach (var entry in dictionary)
{
@ -35,7 +36,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
GetModelStateForKey(key).Errors.Add(errorMessage);
}
private ModelState GetModelStateForKey([NotNull]string key)
public bool IsValidField([NotNull] string key)
{
// if the key is not found in the dictionary, we just say that it's valid (since there are no errors)
foreach (var entry in DictionaryHelper.FindKeysWithPrefix(this, key))
{
if (entry.Value.Errors.Count != 0)
{
return false;
}
}
return true;
}
private ModelState GetModelStateForKey([NotNull] string key)
{
ModelState modelState;
if (!TryGetValue(key, out modelState))

View File

@ -58,6 +58,38 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return GetString("JQuerySyntaxMissingClosingBracket");
}
/// <summary>
/// Property '{0}' on type '{1}' is invalid. Value-typed properties marked as [Required] must also be marked with [DataMember(IsRequired=true)] to be recognized as required. Consider attributing the declaring type with [DataContract] and the property with [DataMember(IsRequired=true)].
/// </summary>
internal static string MissingDataMemberIsRequired
{
get { return GetString("MissingDataMemberIsRequired"); }
}
/// <summary>
/// Property '{0}' on type '{1}' is invalid. Value-typed properties marked as [Required] must also be marked with [DataMember(IsRequired=true)] to be recognized as required. Consider attributing the declaring type with [DataContract] and the property with [DataMember(IsRequired=true)].
/// </summary>
internal static string FormatMissingDataMemberIsRequired(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("MissingDataMemberIsRequired"), p0, p1);
}
/// <summary>
/// The '{0}' property is required.
/// </summary>
internal static string MissingRequiredMember
{
get { return GetString("MissingRequiredMember"); }
}
/// <summary>
/// The '{0}' property is required.
/// </summary>
internal static string FormatMissingRequiredMember(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("MissingRequiredMember"), p0);
}
/// <summary>
/// The value '{0}' is not valid for {1}.
/// </summary>
@ -170,6 +202,70 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return GetString("ModelBindingContext_ModelMetadataMustBeSet");
}
/// <summary>
/// The model object inside the metadata claimed to be compatible with '{0}', but was actually '{1}'.
/// </summary>
internal static string ValidatableObjectAdapter_IncompatibleType
{
get { return GetString("ValidatableObjectAdapter_IncompatibleType"); }
}
/// <summary>
/// The model object inside the metadata claimed to be compatible with '{0}', but was actually '{1}'.
/// </summary>
internal static string FormatValidatableObjectAdapter_IncompatibleType(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("ValidatableObjectAdapter_IncompatibleType"), p0, p1);
}
/// <summary>
/// Field '{0}' on type '{1}' is attributed with one or more validation attributes. Validation attributes on fields are not supported. Consider using a public property for validation instead.
/// </summary>
internal static string ValidationAttributeOnField
{
get { return GetString("ValidationAttributeOnField"); }
}
/// <summary>
/// Field '{0}' on type '{1}' is attributed with one or more validation attributes. Validation attributes on fields are not supported. Consider using a public property for validation instead.
/// </summary>
internal static string FormatValidationAttributeOnField(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("ValidationAttributeOnField"), p0, p1);
}
/// <summary>
/// Non-public property '{0}' on type '{1}' is attributed with one or more validation attributes. Validation attributes on non-public properties are not supported. Consider using a public property for validation instead.
/// </summary>
internal static string ValidationAttributeOnNonPublicProperty
{
get { return GetString("ValidationAttributeOnNonPublicProperty"); }
}
/// <summary>
/// Non-public property '{0}' on type '{1}' is attributed with one or more validation attributes. Validation attributes on non-public properties are not supported. Consider using a public property for validation instead.
/// </summary>
internal static string FormatValidationAttributeOnNonPublicProperty(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("ValidationAttributeOnNonPublicProperty"), p0, p1);
}
/// <summary>
/// A value is required but was not present in the request.
/// </summary>
internal static string Validation_ValueNotFound
{
get { return GetString("Validation_ValueNotFound"); }
}
/// <summary>
/// A value is required but was not present in the request.
/// </summary>
internal static string FormatValidation_ValueNotFound()
{
return GetString("Validation_ValueNotFound");
}
/// <summary>
/// The parameter conversion from type '{0}' to type '{1}' failed. See the inner exception for more information.
/// </summary>

View File

@ -126,6 +126,12 @@
<data name="JQuerySyntaxMissingClosingBracket" xml:space="preserve">
<value>The key is invalid JQuery syntax because it is missing a closing bracket.</value>
</data>
<data name="MissingDataMemberIsRequired" xml:space="preserve">
<value>Property '{0}' on type '{1}' is invalid. Value-typed properties marked as [Required] must also be marked with [DataMember(IsRequired=true)] to be recognized as required. Consider attributing the declaring type with [DataContract] and the property with [DataMember(IsRequired=true)].</value>
</data>
<data name="MissingRequiredMember" xml:space="preserve">
<value>The '{0}' property is required.</value>
</data>
<data name="ModelBinderConfig_ValueInvalid" xml:space="preserve">
<value>The value '{0}' is not valid for {1}.</value>
</data>
@ -147,6 +153,18 @@
<data name="ModelBindingContext_ModelMetadataMustBeSet" xml:space="preserve">
<value>The ModelMetadata property must be set before accessing this property.</value>
</data>
<data name="ValidatableObjectAdapter_IncompatibleType" xml:space="preserve">
<value>The model object inside the metadata claimed to be compatible with '{0}', but was actually '{1}'.</value>
</data>
<data name="ValidationAttributeOnField" xml:space="preserve">
<value>Field '{0}' on type '{1}' is attributed with one or more validation attributes. Validation attributes on fields are not supported. Consider using a public property for validation instead.</value>
</data>
<data name="ValidationAttributeOnNonPublicProperty" xml:space="preserve">
<value>Non-public property '{0}' on type '{1}' is attributed with one or more validation attributes. Validation attributes on non-public properties are not supported. Consider using a public property for validation instead.</value>
</data>
<data name="Validation_ValueNotFound" xml:space="preserve">
<value>A value is required but was not present in the request.</value>
</data>
<data name="ValueProviderResult_ConversionThrew" xml:space="preserve">
<value>The parameter conversion from type '{0}' to type '{1}' failed. See the inner exception for more information.</value>
</data>

View File

@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public abstract class AssociatedValidatorProvider : IModelValidatorProvider
{
public IEnumerable<IModelValidator> GetValidators(ModelMetadata metadata)
{
if (metadata == null)
{
throw new ArgumentNullException("metadata");
}
if (metadata.ContainerType != null && !string.IsNullOrEmpty(metadata.PropertyName))
{
return GetValidatorsForProperty(metadata);
}
return GetValidatorsForType(metadata);
}
protected abstract IEnumerable<IModelValidator> GetValidators(ModelMetadata metadata,
IEnumerable<Attribute> attributes);
private IEnumerable<IModelValidator> GetValidatorsForProperty(ModelMetadata metadata)
{
var propertyName = metadata.PropertyName;
var property = metadata.ContainerType
.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (property == null)
{
throw new ArgumentException(
Resources.FormatCommon_PropertyNotFound(
metadata.ContainerType.FullName,
metadata.PropertyName),
"metadata");
}
var attributes = property.GetCustomAttributes();
return GetValidators(metadata, attributes);
}
private IEnumerable<IModelValidator> GetValidatorsForType(ModelMetadata metadata)
{
var attributes = metadata.ModelType
.GetTypeInfo()
.GetCustomAttributes();
return GetValidators(metadata, attributes);
}
}
}

View File

@ -0,0 +1,56 @@
using System.Collections.Generic;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class CompositeModelValidator : IModelValidator
{
private readonly IEnumerable<IModelValidator> _validators;
public CompositeModelValidator(IEnumerable<IModelValidator> validators)
{
_validators = validators;
}
public bool IsRequired
{
get { return false; }
}
public IEnumerable<ModelValidationResult> Validate(ModelValidationContext context)
{
var propertiesValid = true;
var metadata = context.ModelMetadata;
foreach (var propertyMetadata in metadata.Properties)
{
var propertyContext = new ModelValidationContext(context, propertyMetadata);
foreach (var propertyValidator in _validators)
{
foreach (var validationResult in propertyValidator.Validate(propertyContext))
{
propertiesValid = false;
yield return CreateSubPropertyResult(propertyMetadata, validationResult);
}
}
}
if (propertiesValid)
{
foreach (var typeValidator in _validators)
{
foreach (var typeResult in typeValidator.Validate(context))
{
yield return typeResult;
}
}
}
}
private static ModelValidationResult CreateSubPropertyResult(ModelMetadata propertyMetadata, ModelValidationResult propertyResult)
{
return new ModelValidationResult(propertyMetadata.PropertyName + '.' + propertyResult.MemberName,
propertyResult.Message);
}
}
}

View File

@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class DataAnnotationsModelValidator : IModelValidator
{
public DataAnnotationsModelValidator(ValidationAttribute attribute)
{
if (attribute == null)
{
throw new ArgumentNullException("attribute");
}
Attribute = attribute;
}
public ValidationAttribute Attribute { get; private set; }
public bool IsRequired
{
get { return Attribute is RequiredAttribute; }
}
public IEnumerable<ModelValidationResult> Validate(ModelValidationContext validationContext)
{
var metadata = validationContext.ModelMetadata;
var memberName = metadata.PropertyName ?? metadata.ModelType.Name;
var context = new ValidationContext(metadata.Model)
{
DisplayName = metadata.GetDisplayName(),
MemberName = memberName
};
var result = Attribute.GetValidationResult(metadata.Model, context);
if (result != ValidationResult.Success)
{
// ModelValidationResult.MemberName is used by invoking validators (such as ModelValidator) to
// construct the ModelKey for ModelStateDictionary. When validating at type level we want to append the
// returned MemberNames if specified (e.g. person.Address.FirstName). For property validation, the
// ModelKey can be constructed using the ModelMetadata and we should ignore MemberName (we don't want
// (person.Name.Name). However the invoking validator does not have a way to distinguish between these two
// cases. Consequently we'll only set MemberName if this validation returns a MemberName that is different
// from the property being validated.
var errorMemberName = result.MemberNames.FirstOrDefault();
if (string.Equals(errorMemberName, memberName, StringComparison.Ordinal))
{
errorMemberName = null;
}
var validationResult = new ModelValidationResult(errorMemberName, result.ErrorMessage);
return new ModelValidationResult[] { validationResult };
}
return Enumerable.Empty<ModelValidationResult>();
}
}
}

View File

@ -0,0 +1,82 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
/// <summary>
/// An implementation of <see cref="ModelValidatorProvider"/> which providers validators
/// for attributes which derive from <see cref="ValidationAttribute"/>. It also provides
/// a validator for types which implement <see cref="IValidatableObject"/>. To support
/// client side validation, you can either register adapters through the static methods
/// on this class, or by having your validation attributes implement
/// <see cref="IClientValidatable"/>. The logic to support IClientValidatable
/// is implemented in <see cref="DataAnnotationsModelValidator"/>.
/// </summary>
public class DataAnnotationsModelValidatorProvider : AssociatedValidatorProvider
{
// A factory for validators based on ValidationAttribute
private delegate IModelValidator DataAnnotationsModelValidationFactory(ValidationAttribute attribute);
// A factory for validators based on IValidatableObject
private delegate IModelValidator DataAnnotationsValidatableObjectAdapterFactory();
private static bool _addImplicitRequiredAttributeForValueTypes = true;
// Factories for validation attributes
private static DataAnnotationsModelValidationFactory DefaultAttributeFactory =
(attribute) => new DataAnnotationsModelValidator(attribute);
private static Dictionary<Type, DataAnnotationsModelValidationFactory> AttributeFactories =
new Dictionary<Type, DataAnnotationsModelValidationFactory>();
#if NET45
// Factories for IValidatableObject models
private static DataAnnotationsValidatableObjectAdapterFactory DefaultValidatableFactory =
() => new ValidatableObjectAdapter();
#endif
private static Dictionary<Type, DataAnnotationsValidatableObjectAdapterFactory> ValidatableFactories =
new Dictionary<Type, DataAnnotationsValidatableObjectAdapterFactory>();
public static bool AddImplicitRequiredAttributeForValueTypes
{
get { return _addImplicitRequiredAttributeForValueTypes; }
set { _addImplicitRequiredAttributeForValueTypes = value; }
}
protected override IEnumerable<IModelValidator> GetValidators(ModelMetadata metadata, IEnumerable<Attribute> attributes)
{
var results = new List<IModelValidator>();
// Produce a validator for each validation attribute we find
foreach (var attribute in attributes.OfType<ValidationAttribute>())
{
DataAnnotationsModelValidationFactory factory;
if (!AttributeFactories.TryGetValue(attribute.GetType(), out factory))
{
factory = DefaultAttributeFactory;
}
results.Add(factory(attribute));
}
#if NET45
// Produce a validator if the type supports IValidatableObject
if (typeof(IValidatableObject).IsAssignableFrom(metadata.ModelType))
{
DataAnnotationsValidatableObjectAdapterFactory factory;
if (!ValidatableFactories.TryGetValue(metadata.ModelType, out factory))
{
factory = DefaultValidatableFactory;
}
results.Add(factory());
}
#endif
return results;
}
}
}

View File

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
/// <summary>
/// This <see cref="ModelValidatorProvider"/> provides a required ModelValidator for members marked as [DataMember(IsRequired=true)].
/// </summary>
public class DataMemberModelValidatorProvider : AssociatedValidatorProvider
{
protected override IEnumerable<IModelValidator> GetValidators(ModelMetadata metadata,
IEnumerable<Attribute> attributes)
{
// Types cannot be required; only properties can
if (metadata.ContainerType == null || string.IsNullOrEmpty(metadata.PropertyName))
{
return Enumerable.Empty<IModelValidator>();
}
if (IsRequiredDataMember(metadata.ContainerType, attributes))
{
return new[] { new RequiredMemberModelValidator() };
}
return Enumerable.Empty<IModelValidator>();
}
internal static bool IsRequiredDataMember(Type containerType, IEnumerable<Attribute> attributes)
{
var dataMemberAttribute = attributes.OfType<DataMemberAttribute>()
.FirstOrDefault();
if (dataMemberAttribute != null)
{
// isDataContract == true iff the container type has at least one DataContractAttribute
bool isDataContract = containerType.GetTypeInfo()
.GetCustomAttributes()
.OfType<DataContractAttribute>()
.Any();
if (isDataContract && dataMemberAttribute.IsRequired)
{
return true;
}
}
return false;
}
}
}

View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
/// <summary>
/// A <see cref="IModelValidator"/> to represent an error. This validator will always throw an exception regardless
/// of the actual model value.
/// This is used to perform meta-validation - that is to verify the validation attributes make sense.
/// </summary>
public class ErrorModelValidator : IModelValidator
{
private readonly string _errorMessage;
public ErrorModelValidator([NotNull] string errorMessage)
{
_errorMessage = errorMessage;
}
public bool IsRequired { get { return false; } }
public IEnumerable<ModelValidationResult> Validate(ModelValidationContext context)
{
throw new InvalidOperationException(_errorMessage);
}
}
}

View File

@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public interface IModelValidator
{
bool IsRequired { get; }
IEnumerable<ModelValidationResult> Validate(ModelValidationContext context);
}
}

View File

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public interface IModelValidatorProvider
{
IEnumerable<IModelValidator> GetValidators(ModelMetadata metadata);
}
}

View File

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class InvalidModelValidatorProvider : AssociatedValidatorProvider
{
protected override IEnumerable<IModelValidator> GetValidators(ModelMetadata metadata,
IEnumerable<Attribute> attributes)
{
if (metadata.ContainerType == null || String.IsNullOrEmpty(metadata.PropertyName))
{
// Validate that the type's fields and nonpublic properties don't have any validation attributes on them
// Validation only runs against public properties
var type = metadata.ModelType;
var nonPublicProperties = type.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var nonPublicProperty in nonPublicProperties)
{
if (nonPublicProperty.GetCustomAttributes(typeof(ValidationAttribute), inherit: true).Any())
{
yield return new ErrorModelValidator(Resources.FormatValidationAttributeOnNonPublicProperty(nonPublicProperty.Name, type));
}
}
var allFields = metadata.ModelType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var field in allFields)
{
if (field.GetCustomAttributes(typeof(ValidationAttribute), inherit: true).Any())
{
yield return new ErrorModelValidator(Resources.FormatValidationAttributeOnField(field.Name, type));
}
}
}
else
{
// Validate that value-typed properties marked as [Required] are also marked as [DataMember(IsRequired=true)]
// Certain formatters may not recognize a member as required if it's marked as [Required] but not [DataMember(IsRequired=true)]
// This is not a problem for reference types because [Required] will still cause a model error to be raised after a null value is deserialized
if (metadata.ModelType.GetTypeInfo().IsValueType && attributes.Any(attribute => attribute is RequiredAttribute))
{
if (!DataMemberModelValidatorProvider.IsRequiredDataMember(metadata.ContainerType, attributes))
{
yield return new ErrorModelValidator(Resources.FormatMissingDataMemberIsRequired(metadata.PropertyName, metadata.ContainerType));
}
}
}
}
}
}

View File

@ -0,0 +1,18 @@
using System;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public sealed class ModelValidatedEventArgs : EventArgs
{
public ModelValidatedEventArgs([NotNull] ModelValidationContext validationContext,
[NotNull] ModelValidationNode parentNode)
{
ValidationContext = validationContext;
ParentNode = parentNode;
}
public ModelValidationContext ValidationContext { get; private set; }
public ModelValidationNode ParentNode { get; private set; }
}
}

View File

@ -0,0 +1,18 @@
using System.ComponentModel;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public sealed class ModelValidatingEventArgs : CancelEventArgs
{
public ModelValidatingEventArgs([NotNull] ModelValidationContext validationContext,
[NotNull] ModelValidationNode parentNode)
{
ValidationContext = validationContext;
ParentNode = parentNode;
}
public ModelValidationContext ValidationContext { get; private set; }
public ModelValidationNode ParentNode { get; private set; }
}
}

View File

@ -0,0 +1,38 @@
using System.Collections.Generic;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class ModelValidationContext
{
public ModelValidationContext([NotNull] ModelMetadata metadata,
[NotNull] ModelStateDictionary modelState,
[NotNull] IModelMetadataProvider metadataProvider,
[NotNull] IEnumerable<IModelValidatorProvider> validatorProviders)
{
ModelMetadata = metadata;
ModelState = modelState;
MetadataProvider = metadataProvider;
ValidatorProviders = validatorProviders;
}
public ModelValidationContext([NotNull] ModelValidationContext parentContext,
[NotNull] ModelMetadata metadata)
{
ModelMetadata = metadata;
ContainerMetadata = parentContext.ModelMetadata;
ModelState = parentContext.ModelState;
MetadataProvider = parentContext.MetadataProvider;
ValidatorProviders = parentContext.ValidatorProviders;
}
public ModelMetadata ModelMetadata { get; private set; }
public ModelMetadata ContainerMetadata { get; private set; }
public ModelStateDictionary ModelState { get; private set; }
public IModelMetadataProvider MetadataProvider { get; private set; }
public IEnumerable<IModelValidatorProvider> ValidatorProviders { get; private set; }
}
}

View File

@ -0,0 +1,209 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class ModelValidationNode
{
private readonly List<ModelValidationNode> _childNodes;
public ModelValidationNode(ModelMetadata modelMetadata, string modelStateKey)
: this(modelMetadata, modelStateKey, null)
{
}
public ModelValidationNode([NotNull] ModelMetadata modelMetadata,
[NotNull] string modelStateKey,
IEnumerable<ModelValidationNode> childNodes)
{
ModelMetadata = modelMetadata;
ModelStateKey = modelStateKey;
_childNodes = (childNodes != null) ? childNodes.ToList() : new List<ModelValidationNode>();
}
public event EventHandler<ModelValidatedEventArgs> Validated;
public event EventHandler<ModelValidatingEventArgs> Validating;
public ICollection<ModelValidationNode> ChildNodes
{
get { return _childNodes; }
}
public ModelMetadata ModelMetadata { get; private set; }
public string ModelStateKey { get; private set; }
public bool ValidateAllProperties { get; set; }
public bool SuppressValidation { get; set; }
public void CombineWith(ModelValidationNode otherNode)
{
if (otherNode != null && !otherNode.SuppressValidation)
{
Validated += otherNode.Validated;
Validating += otherNode.Validating;
var otherChildNodes = otherNode._childNodes;
for (var i = 0; i < otherChildNodes.Count; i++)
{
var childNode = otherChildNodes[i];
_childNodes.Add(childNode);
}
}
}
private void OnValidated(ModelValidatedEventArgs e)
{
if (Validated != null)
{
Validated(this, e);
}
}
private void OnValidating(ModelValidatingEventArgs e)
{
if (Validating != null)
{
Validating(this, e);
}
}
private object TryConvertContainerToMetadataType(ModelValidationNode parentNode)
{
if (parentNode != null)
{
var containerInstance = parentNode.ModelMetadata.Model;
if (containerInstance != null)
{
var expectedContainerType = ModelMetadata.ContainerType;
if (expectedContainerType != null)
{
if (expectedContainerType.IsCompatibleWith(containerInstance))
{
return containerInstance;
}
}
}
}
return null;
}
public void Validate(ModelValidationContext validationContext)
{
Validate(validationContext, parentNode: null);
}
public void Validate(ModelValidationContext validationContext, ModelValidationNode parentNode)
{
if (validationContext == null)
{
throw Error.ArgumentNull("validationContext");
}
if (SuppressValidation)
{
// no-op
return;
}
// pre-validation steps
var validatingEventArgs = new ModelValidatingEventArgs(validationContext, parentNode);
OnValidating(validatingEventArgs);
if (validatingEventArgs.Cancel)
{
return;
}
ValidateChildren(validationContext);
ValidateThis(validationContext, parentNode);
// post-validation steps
var validatedEventArgs = new ModelValidatedEventArgs(validationContext, parentNode);
OnValidated(validatedEventArgs);
}
private void ValidateChildren(ModelValidationContext validationContext)
{
for (var i = 0; i < _childNodes.Count; i++)
{
var child = _childNodes[i];
var childValidationContext = new ModelValidationContext(validationContext, child.ModelMetadata);
child.Validate(childValidationContext, this);
}
if (ValidateAllProperties)
{
ValidateProperties(validationContext);
}
}
private void ValidateProperties(ModelValidationContext validationContext)
{
var modelState = validationContext.ModelState;
var model = ModelMetadata.Model;
var updatedMetadata = validationContext.MetadataProvider.GetMetadataForType(() => model, ModelMetadata.ModelType);
foreach (var propertyMetadata in updatedMetadata.Properties)
{
// Only want to add errors to ModelState if something doesn't already exist for the property node,
// else we could end up with duplicate or irrelevant error messages.
var propertyKeyRoot = ModelBindingHelper.CreatePropertyModelName(ModelStateKey, propertyMetadata.PropertyName);
if (modelState.IsValidField(propertyKeyRoot))
{
var propertyValidators = GetValidators(validationContext, propertyMetadata);
var propertyValidationContext = new ModelValidationContext(validationContext, propertyMetadata);
foreach (var propertyValidator in propertyValidators)
{
foreach (var propertyResult in propertyValidator.Validate(propertyValidationContext))
{
var thisErrorKey = ModelBindingHelper.CreatePropertyModelName(propertyKeyRoot, propertyResult.MemberName);
modelState.AddModelError(thisErrorKey, propertyResult.Message);
}
}
}
}
}
private void ValidateThis(ModelValidationContext validationContext, ModelValidationNode parentNode)
{
var modelState = validationContext.ModelState;
if (!modelState.IsValidField(ModelStateKey))
{
return; // short-circuit
}
// 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
if (parentNode == null && ModelMetadata.Model == null)
{
var trueModelStateKey = ModelBindingHelper.CreatePropertyModelName(ModelStateKey, ModelMetadata.GetDisplayName());
modelState.AddModelError(trueModelStateKey, Resources.Validation_ValueNotFound);
return;
}
var container = TryConvertContainerToMetadataType(parentNode);
var validators = GetValidators(validationContext, ModelMetadata).ToArray();
for (var i = 0; i < validators.Length; i++)
{
var validator = validators[i];
foreach (var validationResult in validator.Validate(validationContext))
{
var trueModelStateKey = ModelBindingHelper.CreatePropertyModelName(ModelStateKey, validationResult.MemberName);
modelState.AddModelError(trueModelStateKey, validationResult.Message);
}
}
}
private static IEnumerable<IModelValidator> GetValidators(ModelValidationContext validationContext, ModelMetadata metadata)
{
return validationContext.ValidatorProviders.SelectMany(vp => vp.GetValidators(metadata));
}
}
}

View File

@ -0,0 +1,16 @@

namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class ModelValidationResult
{
public ModelValidationResult(string memberName, string message)
{
MemberName = memberName ?? string.Empty;
Message = message ?? string.Empty;
}
public string MemberName { get; private set; }
public string Message { get; private set; }
}
}

View File

@ -0,0 +1,18 @@
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class RequiredMemberModelValidator : IModelValidator
{
public bool IsRequired
{
get { return true; }
}
public IEnumerable<ModelValidationResult> Validate(ModelValidationContext context)
{
return Enumerable.Empty<ModelValidationResult>();
}
}
}

View File

@ -0,0 +1,60 @@
#if NET45
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class ValidatableObjectAdapter : IModelValidator
{
public bool IsRequired
{
get { return false; }
}
public IEnumerable<ModelValidationResult> Validate(ModelValidationContext context)
{
var model = context.ModelMetadata.Model;
if (model == null)
{
return Enumerable.Empty<ModelValidationResult>();
}
var validatable = model as IValidatableObject;
if (validatable == null)
{
var message = Resources.FormatValidatableObjectAdapter_IncompatibleType(
typeof(IValidatableObject).Name,
model.GetType());
throw new InvalidOperationException(message);
}
var validationContext = new ValidationContext(validatable, serviceProvider: null, items: null);
return ConvertResults(validatable.Validate(validationContext));
}
private IEnumerable<ModelValidationResult> ConvertResults(IEnumerable<ValidationResult> results)
{
foreach (var result in results)
{
if (result != ValidationResult.Success)
{
if (result.MemberNames == null || !result.MemberNames.Any())
{
yield return new ModelValidationResult(memberName: null, message: result.ErrorMessage);
}
else
{
foreach (var memberName in result.MemberNames)
{
yield return new ModelValidationResult(memberName, result.ErrorMessage);
}
}
}
}
}
}
}
#endif

View File

@ -2,12 +2,17 @@
"version": "0.1-alpha-*",
"dependencies": {
"Common": "",
"Microsoft.AspNet.DependencyInjection" : "0.1-alpha-*",
"Microsoft.AspNet.DependencyInjection": "0.1-alpha-*",
"Microsoft.AspNet.Abstractions": "0.1-alpha-*",
"Newtonsoft.Json": "5.0.8"
},
"configurations": {
"net45": {},
"net45": {
"dependencies": {
"System.ComponentModel.DataAnnotations": "",
"System.Runtime.Serialization": ""
}
},
"k10": {
"dependencies": {
"System.Collections": "4.0.0.0",
@ -22,12 +27,15 @@
"System.Reflection": "4.0.10.0",
"System.Reflection.Emit.ILGeneration": "4.0.0.0",
"System.Reflection.Emit.Lightweight": "4.0.0.0",
"System.Reflection.Compatibility": "4.0.0.0",
"System.Reflection.Extensions": "4.0.0.0",
"System.Reflection.Primitives": "4.0.0.0",
"System.Resources.ResourceManager": "4.0.0.0",
"System.Runtime": "4.0.20.0",
"System.Runtime.Extensions": "4.0.10.0",
"System.Threading.Tasks": "4.0.0.0"
"System.Threading.Tasks": "4.0.0.0",
"System.Runtime.Serialization.Primitives": "4.0.0.0",
"Microsoft.ComponentModel.DataAnnotations": "0.1-alpha-*"
}
}
}

View File

@ -61,10 +61,13 @@ namespace Microsoft.AspNet.Mvc
yield return describe.Transient<IModelBinder, MutableObjectModelBinder>();
yield return describe.Transient<IModelBinder, ComplexModelDtoModelBinder>();
yield return describe.Transient<IInputFormatter, JsonInputFormatter>();
yield return describe.Transient<INestedProviderManager<FilterProviderContext>, NestedProviderManager<FilterProviderContext>>();
yield return describe.Transient<INestedProvider<FilterProviderContext>, DefaultFilterProvider>();
yield return describe.Transient<IInputFormatter, JsonInputFormatter>();
yield return describe.Singleton<IModelValidatorProvider, DataAnnotationsModelValidatorProvider>();
yield return describe.Singleton<IModelValidatorProvider, DataMemberModelValidatorProvider>();
}
}
}

View File

@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Moq;
using Xunit;
@ -20,12 +21,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
var binder = new CollectionModelBinder<int>();
// Act
List<int> boundCollection = binder.BindComplexCollectionFromIndexes(bindingContext, new[] { "foo", "bar", "baz" });
var boundCollection = binder.BindComplexCollectionFromIndexes(bindingContext, new[] { "foo", "bar", "baz" });
// Assert
Assert.Equal(new[] { 42, 0, 200 }, boundCollection.ToArray());
// TODO: Validation
// Assert.Equal(new[] { "someName[foo]", "someName[baz]" }, bindingContext.ValidationNode.ChildNodes.Select(o => o.ModelStateKey).ToArray());
Assert.Equal(new[] { "someName[foo]", "someName[baz]" }, bindingContext.ValidationNode.ChildNodes.Select(o => o.ModelStateKey).ToArray());
}
[Fact]
@ -42,12 +42,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
var binder = new CollectionModelBinder<int>();
// Act
List<int> boundCollection = binder.BindComplexCollectionFromIndexes(bindingContext, indexNames: null);
var boundCollection = binder.BindComplexCollectionFromIndexes(bindingContext, indexNames: null);
// Assert
Assert.Equal(new[] { 42, 100 }, boundCollection.ToArray());
// TODO: Validation
// Assert.Equal(new[] { "someName[0]", "someName[1]" }, bindingContext.ValidationNode.ChildNodes.Select(o => o.ModelStateKey).ToArray());
Assert.Equal(new[] { "someName[0]", "someName[1]" }, bindingContext.ValidationNode.ChildNodes.Select(o => o.ModelStateKey).ToArray());
}
[Fact]
@ -97,7 +96,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
var binder = new CollectionModelBinder<int>();
// Act
List<int> boundCollection = binder.BindSimpleCollection(bindingContext: null, rawValue: new object[0], culture: null);
var boundCollection = binder.BindSimpleCollection(bindingContext: null, rawValue: new object[0], culture: null);
// Assert
Assert.NotNull(boundCollection);
@ -111,7 +110,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
var binder = new CollectionModelBinder<int>();
// Act
List<int> boundCollection = binder.BindSimpleCollection(bindingContext: null, rawValue: null, culture: null);
var boundCollection = binder.BindSimpleCollection(bindingContext: null, rawValue: null, culture: null);
// Assert
Assert.Null(boundCollection);
@ -124,25 +123,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
var culture = CultureInfo.GetCultureInfo("fr-FR");
var bindingContext = GetModelBindingContext(new SimpleHttpValueProvider());
// TODO: Validation
// ModelValidationNode childValidationNode = null;
ModelValidationNode childValidationNode = null;
Mock.Get<IModelBinder>(bindingContext.ModelBinder)
.Setup(o => o.BindModel(It.IsAny<ModelBindingContext>()))
.Returns((ModelBindingContext mbc) =>
{
Assert.Equal("someName", mbc.ModelName);
// childValidationNode = mbc.ValidationNode;
childValidationNode = mbc.ValidationNode;
mbc.Model = 42;
return true;
});
var modelBinder = new CollectionModelBinder<int>();
// Act
List<int> boundCollection = modelBinder.BindSimpleCollection(bindingContext, new int[1], culture);
var boundCollection = modelBinder.BindSimpleCollection(bindingContext, new int[1], culture);
// Assert
Assert.Equal(new[] { 42 }, boundCollection.ToArray());
// Assert.Equal(new[] { childValidationNode }, bindingContext.ValidationNode.ChildNodes.ToArray());
Assert.Equal(new[] { childValidationNode }, bindingContext.ValidationNode.ChildNodes.ToArray());
}
private static ModelBindingContext GetModelBindingContext(IValueProvider valueProvider)

View File

@ -4,36 +4,34 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
{
public class ComplexModelDtoResultTest
{
// TODO: Validation
////[Fact]
////public void Constructor_ThrowsIfValidationNodeIsNull()
////{
//// // Act & assert
//// ExceptionAssert.ThrowsArgumentNull(
//// () => new ComplexModelDtoResult("some string"),
//// "validationNode");
////}
[Fact]
public void Constructor_ThrowsIfValidationNodeIsNull()
{
// Act & assert
ExceptionAssert.ThrowsArgumentNull(
() => new ComplexModelDtoResult("some string", validationNode: null),
"validationNode");
}
// TODO: Validation
//[Fact]
//public void Constructor_SetsProperties()
//{
// // Arrange
// ModelValidationNode validationNode = GetValidationNode();
[Fact]
public void Constructor_SetsProperties()
{
// Arrange
var validationNode = GetValidationNode();
// // Act
// ComplexModelDtoResult result = new ComplexModelDtoResult("some string", validationNode);
// Act
var result = new ComplexModelDtoResult("some string", validationNode);
// // Assert
// Assert.Equal("some string", result.Model);
// Assert.Equal(validationNode, result.ValidationNode);
//}
// Assert
Assert.Equal("some string", result.Model);
Assert.Equal(validationNode, result.ValidationNode);
}
//private static ModelValidationNode GetValidationNode()
//{
// EmptyModelMetadataProvider provider = new EmptyModelMetadataProvider();
// ModelMetadata metadata = provider.GetMetadataForType(null, typeof(object));
// return new ModelValidationNode(metadata, "someKey");
//}
private static ModelValidationNode GetValidationNode()
{
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(null, typeof(object));
return new ModelValidationNode(metadata, "someKey");
}
}
}

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Moq;
using Xunit;
@ -12,22 +13,22 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
public void BindModel_SuccessfulBind_RunsValidationAndReturnsModel()
{
// Arrange
bool validationCalled = false;
var validationCalled = false;
ModelBindingContext bindingContext = new ModelBindingContext
var bindingContext = new ModelBindingContext
{
FallbackToEmptyPrefix = true,
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
ModelName = "someName",
//ModelState = executionContext.Controller.ViewData.ModelState,
//PropertyFilter = _ => true,
ModelState = new ModelStateDictionary(),
ValueProvider = new SimpleValueProvider
{
{ "someName", "dummyValue" }
}
},
ValidatorProviders = Enumerable.Empty<IModelValidatorProvider>()
};
Mock<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
var mockIntBinder = new Mock<IModelBinder>();
mockIntBinder
.Setup(o => o.BindModel(It.IsAny<ModelBindingContext>()))
.Returns(
@ -38,22 +39,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.Same(bindingContext.ValueProvider, context.ValueProvider);
context.Model = 42;
// TODO: Validation
// mbc.ValidationNode.Validating += delegate { validationCalled = true; };
bindingContext.ValidationNode.Validating += delegate { validationCalled = true; };
return true;
});
//binderProviders.RegisterBinderForType(typeof(int), mockIntBinder.Object, false /* suppressPrefixCheck */);
IModelBinder shimBinder = new CompositeModelBinder(mockIntBinder.Object);
var shimBinder = new CompositeModelBinder(mockIntBinder.Object);
// Act
bool isBound = shimBinder.BindModel(bindingContext);
var isBound = shimBinder.BindModel(bindingContext);
// Assert
Assert.True(isBound);
Assert.Equal(42, bindingContext.Model);
// TODO: Validation
// Assert.True(validationCalled);
Assert.True(validationCalled);
Assert.True(bindingContext.ModelState.IsValid);
}
@ -61,23 +60,23 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
public void BindModel_SuccessfulBind_ComplexTypeFallback_RunsValidationAndReturnsModel()
{
// Arrange
bool validationCalled = false;
List<int> expectedModel = new List<int> { 1, 2, 3, 4, 5 };
var validationCalled = false;
var expectedModel = new List<int> { 1, 2, 3, 4, 5 };
ModelBindingContext bindingContext = new ModelBindingContext
var bindingContext = new ModelBindingContext
{
FallbackToEmptyPrefix = true,
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(List<int>)),
ModelName = "someName",
//ModelState = executionContext.Controller.ViewData.ModelState,
//PropertyFilter = _ => true,
ModelState = new ModelStateDictionary(),
ValueProvider = new SimpleValueProvider
{
{ "someOtherName", "dummyValue" }
}
},
ValidatorProviders = Enumerable.Empty<IModelValidatorProvider>()
};
Mock<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
var mockIntBinder = new Mock<IModelBinder>();
mockIntBinder
.Setup(o => o.BindModel(It.IsAny<ModelBindingContext>()))
.Returns(
@ -93,12 +92,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.Same(bindingContext.ValueProvider, mbc.ValueProvider);
mbc.Model = expectedModel;
// TODO: Validation
// mbc.ValidationNode.Validating += delegate { validationCalled = true; };
mbc.ValidationNode.Validating += delegate { validationCalled = true; };
return true;
});
//binderProviders.RegisterBinderForType(typeof(List<int>), mockIntBinder.Object, false /* suppressPrefixCheck */);
IModelBinder shimBinder = new CompositeModelBinder(mockIntBinder.Object);
// Act
@ -107,36 +104,34 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
// Assert
Assert.True(isBound);
Assert.Equal(expectedModel, bindingContext.Model);
// TODO: Validation
// Assert.True(validationCalled);
// Assert.True(bindingContext.ModelState.IsValid);
Assert.True(validationCalled);
Assert.True(bindingContext.ModelState.IsValid);
}
[Fact]
public void BindModel_UnsuccessfulBind_BinderFails_ReturnsNull()
{
// Arrange
Mock<IModelBinder> mockListBinder = new Mock<IModelBinder>();
var mockListBinder = new Mock<IModelBinder>();
mockListBinder.Setup(o => o.BindModel(It.IsAny<ModelBindingContext>()))
.Returns(false)
.Verifiable();
IModelBinder shimBinder = (IModelBinder)mockListBinder.Object;
var shimBinder = (IModelBinder)mockListBinder.Object;
ModelBindingContext bindingContext = new ModelBindingContext
var bindingContext = new ModelBindingContext
{
FallbackToEmptyPrefix = false,
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(List<int>)),
};
// Act
bool isBound = shimBinder.BindModel(bindingContext);
var isBound = shimBinder.BindModel(bindingContext);
// Assert
Assert.False(isBound);
Assert.Null(bindingContext.Model);
// TODO: Validation
// Assert.True(bindingContext.ModelState.IsValid);
Assert.True(bindingContext.ModelState.IsValid);
mockListBinder.Verify();
}
@ -145,17 +140,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
{
// Arrange
var innerBinder = Mock.Of<IModelBinder>();
CompositeModelBinder shimBinder = new CompositeModelBinder(innerBinder);
var shimBinder = new CompositeModelBinder(innerBinder);
ModelBindingContext bindingContext = new ModelBindingContext
var bindingContext = new ModelBindingContext
{
FallbackToEmptyPrefix = true,
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
//ModelState = executionContext.Controller.ViewData.ModelState
ModelState = new ModelStateDictionary()
};
// Act
bool isBound = shimBinder.BindModel(bindingContext);
var isBound = shimBinder.BindModel(bindingContext);
// Assert
Assert.False(isBound);

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using Moq;
using Xunit;
@ -11,7 +12,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
{
// Arrange
var valueProvider = new SimpleHttpValueProvider();
ModelBindingContext bindingContext = GetBindingContext(valueProvider, Mock.Of<IModelBinder>());
var bindingContext = GetBindingContext(valueProvider, Mock.Of<IModelBinder>());
var binder = new KeyValuePairModelBinder<int, string>();
// Act
@ -20,8 +21,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
// Assert
Assert.False(retVal);
Assert.Null(bindingContext.Model);
// TODO: Validation
// Assert.Empty(bindingContext.ValidationNode.ChildNodes);
Assert.Empty(bindingContext.ValidationNode.ChildNodes);
}
[Fact]
@ -29,7 +29,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
{
// Arrange
var valueProvider = new SimpleHttpValueProvider();
ModelBindingContext bindingContext = GetBindingContext(valueProvider);
var bindingContext = GetBindingContext(valueProvider);
var binder = new KeyValuePairModelBinder<int, string>();
// Act
@ -38,28 +38,26 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
// Assert
Assert.True(retVal);
Assert.Null(bindingContext.Model);
// TODO: Validation
// Assert.Equal(new[] { "someName.key" }, bindingContext.ValidationNode.ChildNodes.Select(n => n.ModelStateKey).ToArray());
Assert.Equal(new[] { "someName.key" }, bindingContext.ValidationNode.ChildNodes.Select(n => n.ModelStateKey).ToArray());
}
[Fact]
public void BindModel_SubBindingSucceeds()
{
// Arrange
IModelBinder innerBinder = new CompositeModelBinder(CreateStringBinder(), CreateIntBinder());
var innerBinder = new CompositeModelBinder(CreateStringBinder(), CreateIntBinder());
var valueProvider = new SimpleHttpValueProvider();
ModelBindingContext bindingContext = GetBindingContext(valueProvider, innerBinder);
var bindingContext = GetBindingContext(valueProvider, innerBinder);
var binder = new KeyValuePairModelBinder<int, string>();
// Act
bool retVal = binder.BindModel(bindingContext);
var retVal = binder.BindModel(bindingContext);
// Assert
Assert.True(retVal);
Assert.Equal(new KeyValuePair<int, string>(42, "some-value"), bindingContext.Model);
// TODO: Validation
// Assert.Equal(new[] { "someName.key", "someName.value" }, bindingContext.ValidationNode.ChildNodes.Select(n => n.ModelStateKey).ToArray());
Assert.Equal(new[] { "someName.key", "someName.value" }, bindingContext.ValidationNode.ChildNodes.Select(n => n.ModelStateKey).ToArray());
}
[Fact]
@ -71,13 +69,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
// Act
int model;
bool retVal = binder.TryBindStrongModel(bindingContext, "key", out model);
var retVal = binder.TryBindStrongModel(bindingContext, "key", out model);
// Assert
Assert.True(retVal);
Assert.Equal(42, model);
// TODO: Validation
// Assert.Single(bindingContext.ValidationNode.ChildNodes);
Assert.Single(bindingContext.ValidationNode.ChildNodes);
Assert.Empty(bindingContext.ModelState);
}
@ -100,33 +97,33 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
// Act
int model;
bool retVal = binder.TryBindStrongModel(bindingContext, "key", out model);
var retVal = binder.TryBindStrongModel(bindingContext, "key", out model);
// Assert
Assert.True(retVal);
Assert.Equal(default(int), model);
// TODO: Validation
// Assert.Single(bindingContext.ValidationNode.ChildNodes);
Assert.Single(bindingContext.ValidationNode.ChildNodes);
Assert.Empty(bindingContext.ModelState);
}
private static ModelBindingContext GetBindingContext(IValueProvider valueProvider, IModelBinder innerBinder = null)
{
var metataProvider = new EmptyModelMetadataProvider();
ModelBindingContext bindingContext = new ModelBindingContext
var bindingContext = new ModelBindingContext
{
ModelMetadata = metataProvider.GetMetadataForType(null, typeof(KeyValuePair<int, string>)),
ModelName = "someName",
ValueProvider = valueProvider,
ModelBinder = innerBinder ?? CreateIntBinder(),
MetadataProvider = metataProvider
MetadataProvider = metataProvider,
ValidatorProviders = Enumerable.Empty<IModelValidatorProvider>()
};
return bindingContext;
}
private static IModelBinder CreateIntBinder()
{
Mock<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
var mockIntBinder = new Mock<IModelBinder>();
mockIntBinder
.Setup(o => o.BindModel(It.IsAny<ModelBindingContext>()))
.Returns((ModelBindingContext mbc) =>
@ -143,8 +140,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
private static IModelBinder CreateStringBinder()
{
Mock<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
mockIntBinder
var mockStringBinder = new Mock<IModelBinder>();
mockStringBinder
.Setup(o => o.BindModel(It.IsAny<ModelBindingContext>()))
.Returns((ModelBindingContext mbc) =>
{
@ -155,7 +152,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
}
return false;
});
return mockIntBinder.Object;
return mockStringBinder.Object;
}
}
}

View File

@ -9,7 +9,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
public void CopyConstructor()
{
// Arrange
ModelBindingContext originalBindingContext = new ModelBindingContext
var originalBindingContext = new ModelBindingContext
{
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(object)),
ModelName = "theName",
@ -18,7 +18,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
};
// Act
ModelBindingContext newBindingContext = new ModelBindingContext(originalBindingContext);
var newBindingContext = new ModelBindingContext(originalBindingContext);
// Assert
Assert.Null(newBindingContext.ModelMetadata);
@ -31,7 +31,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
public void ModelProperty_ThrowsIfModelMetadataDoesNotExist()
{
// Arrange
ModelBindingContext bindingContext = new ModelBindingContext();
var bindingContext = new ModelBindingContext();
// Act & assert
ExceptionAssert.Throws<InvalidOperationException>(
@ -43,7 +43,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
public void ModelAndModelTypeAreFedFromModelMetadata()
{
// Act
ModelBindingContext bindingContext = new ModelBindingContext
var bindingContext = new ModelBindingContext
{
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => 42, typeof(int))
};
@ -53,38 +53,23 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.Equal(typeof(int), bindingContext.ModelType);
}
// TODO: Validation
//[Fact]
//public void ValidationNodeProperty()
//{
// // Act
// ModelBindingContext bindingContext = new ModelBindingContext
// {
// ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => 42, typeof(int))
// };
[Fact]
public void ValidationNodeProperty_DefaultValues()
{
// Act
var bindingContext = new ModelBindingContext
{
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => 42, typeof(int)),
ModelName = "theInt"
};
// // Act & assert
// MemberHelper.TestPropertyWithDefaultInstance(bindingContext, "ValidationNode", new ModelValidationNode(bindingContext.ModelMetadata, "someName"));
//}
// Act
var validationNode = bindingContext.ValidationNode;
// TODO: Validation
//[Fact]
//public void ValidationNodeProperty_DefaultValues()
//{
// // Act
// ModelBindingContext bindingContext = new ModelBindingContext
// {
// ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => 42, typeof(int)),
// ModelName = "theInt"
// };
// // Act
// ModelValidationNode validationNode = bindingContext.ValidationNode;
// // Assert
// Assert.NotNull(validationNode);
// Assert.Equal(bindingContext.ModelMetadata, validationNode.ModelMetadata);
// Assert.Equal(bindingContext.ModelName, validationNode.ModelStateKey);
//}
// Assert
Assert.NotNull(validationNode);
Assert.Equal(bindingContext.ModelMetadata, validationNode.ModelMetadata);
Assert.Equal(bindingContext.ModelName, validationNode.ModelStateKey);
}
}
}

View File

@ -10,17 +10,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test.Binders
{
// GetMetadataForProperties
[Fact]
public void GetMetadataForPropertiesNullContainerTypeThrows()
{
// Arrange
TestableAssociatedMetadataProvider provider = new TestableAssociatedMetadataProvider();
//[Fact]
//public void GetMetadataForPropertiesNullContainerTypeThrows()
//{
// // Arrange
// TestableAssociatedMetadataProvider provider = new TestableAssociatedMetadataProvider();
// Act & Assert
ExceptionAssert.ThrowsArgumentNull(
() => provider.GetMetadataForProperties(new Object(), containerType: null),
"containerType");
}
// // Act & Assert
// ExceptionAssert.ThrowsArgumentNull(
// () => provider.GetMetadataForProperties(new Object(), containerType: null),
// "containerType");
//}
[Fact]
public void GetMetadataForPropertiesCreatesMetadataForAllPropertiesOnModelWithPropertyValues()
@ -72,17 +72,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test.Binders
// GetMetadataForProperty
[Fact]
public void GetMetadataForPropertyNullContainerTypeThrows()
{
// Arrange
TestableAssociatedMetadataProvider provider = new TestableAssociatedMetadataProvider();
//[Fact]
//public void GetMetadataForPropertyNullContainerTypeThrows()
//{
// // Arrange
// TestableAssociatedMetadataProvider provider = new TestableAssociatedMetadataProvider();
// Act & Assert
ExceptionAssert.ThrowsArgumentNull(
() => provider.GetMetadataForProperty(modelAccessor: null, containerType: null, propertyName: "propertyName"),
"containerType");
}
// // Act & Assert
// ExceptionAssert.ThrowsArgumentNull(
// () => provider.GetMetadataForProperty(modelAccessor: null, containerType: null, propertyName: "propertyName"),
// "containerType");
//}
[Fact]
public void GetMetadataForPropertyNullOrEmptyPropertyNameThrows()
@ -167,17 +167,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test.Binders
// GetMetadataForType
[Fact]
public void GetMetadataForTypeNullModelTypeThrows()
{
// Arrange
TestableAssociatedMetadataProvider provider = new TestableAssociatedMetadataProvider();
//[Fact]
//public void GetMetadataForTypeNullModelTypeThrows()
//{
// // Arrange
// TestableAssociatedMetadataProvider provider = new TestableAssociatedMetadataProvider();
// Act & Assert
ExceptionAssert.ThrowsArgumentNull(
() => provider.GetMetadataForType(() => new Object(), modelType: null),
"modelType");
}
// // Act & Assert
// ExceptionAssert.ThrowsArgumentNull(
// () => provider.GetMetadataForType(() => new Object(), modelType: null),
// "modelType");
//}
[Fact]
public void GetMetadataForTypeIncludesAttributesOnType()

View File

@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class AssociatedValidatorProviderTest
{
private readonly DataAnnotationsModelMetadataProvider _metadataProvider = new DataAnnotationsModelMetadataProvider();
[Fact]
public void GetValidatorsThrowsIfMetadataIsNull()
{
// Arrange
var metadata = _metadataProvider.GetMetadataForType(null, typeof(object));
var provider = new Mock<AssociatedValidatorProvider> { CallBase = true };
// Act & Assert
ExceptionAssert.ThrowsArgumentNull(
() => provider.Object.GetValidators(metadata: null),
"metadata");
}
[Fact]
public void GetValidatorsForPropertyWithLocalAttributes()
{
// Arrange
IEnumerable<Attribute> callbackAttributes = null;
var metadata = _metadataProvider.GetMetadataForProperty(null, typeof(PropertyModel), "LocalAttributes");
var provider = new Mock<TestableAssociatedValidatorProvider> { CallBase = true };
provider.Setup(p => p.AbstractGetValidators(metadata, It.IsAny<IEnumerable<Attribute>>()))
.Callback<ModelMetadata, IEnumerable<Attribute>>((m, attributes) => callbackAttributes = attributes)
.Returns(() => null)
.Verifiable();
// Act
provider.Object.GetValidators(metadata);
// Assert
provider.Verify();
Assert.True(callbackAttributes.Any(a => a is RequiredAttribute));
}
[Fact]
public void GetValidatorsForPropertyWithMetadataAttributes()
{
// Arrange
IEnumerable<Attribute> callbackAttributes = null;
var metadata = _metadataProvider.GetMetadataForProperty(null, typeof(PropertyModel), "MetadataAttributes");
Mock<TestableAssociatedValidatorProvider> provider = new Mock<TestableAssociatedValidatorProvider> { CallBase = true };
provider.Setup(p => p.AbstractGetValidators(metadata, It.IsAny<IEnumerable<Attribute>>()))
.Callback<ModelMetadata, IEnumerable<Attribute>>((m, attributes) => callbackAttributes = attributes)
.Returns(() => null)
.Verifiable();
// Act
provider.Object.GetValidators(metadata);
// Assert
provider.Verify();
Assert.True(callbackAttributes.Any(a => a is RangeAttribute));
}
[Fact]
public void GetValidatorsForPropertyWithMixedAttributes()
{
// Arrange
IEnumerable<Attribute> callbackAttributes = null;
var metadata = _metadataProvider.GetMetadataForProperty(null, typeof(PropertyModel), "MixedAttributes");
Mock<TestableAssociatedValidatorProvider> provider = new Mock<TestableAssociatedValidatorProvider> { CallBase = true };
provider.Setup(p => p.AbstractGetValidators(metadata, It.IsAny<IEnumerable<Attribute>>()))
.Callback<ModelMetadata, IEnumerable<Attribute>>((m, attributes) => callbackAttributes = attributes)
.Returns(() => null)
.Verifiable();
// Act
provider.Object.GetValidators(metadata);
// Assert
provider.Verify();
Assert.True(callbackAttributes.Any(a => a is RangeAttribute));
Assert.True(callbackAttributes.Any(a => a is RequiredAttribute));
}
private class PropertyModel
{
[Required]
public int LocalAttributes { get; set; }
[Range(10, 100)]
public string MetadataAttributes { get; set; }
[Required]
[Range(10, 100)]
public double MixedAttributes { get; set; }
}
public abstract class TestableAssociatedValidatorProvider : AssociatedValidatorProvider
{
protected override IEnumerable<IModelValidator> GetValidators(ModelMetadata metadata, IEnumerable<Attribute> attributes)
{
return AbstractGetValidators(metadata, attributes);
}
public abstract IEnumerable<IModelValidator> AbstractGetValidators(ModelMetadata metadata, IEnumerable<Attribute> attributes);
}
}
}

View File

@ -0,0 +1,108 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class DataAnnotationsModelValidatorProviderTest
{
private readonly DataAnnotationsModelMetadataProvider _metadataProvider = new DataAnnotationsModelMetadataProvider();
[Fact]
public void UnknownValidationAttributeGetsDefaultAdapter()
{
// Arrange
var provider = new DataAnnotationsModelValidatorProvider();
var metadata = _metadataProvider.GetMetadataForType(() => null, typeof(DummyClassWithDummyValidationAttribute));
// Act
IEnumerable<IModelValidator> validators = provider.GetValidators(metadata);
// Assert
var validator = validators.Single();
Assert.IsType<DataAnnotationsModelValidator>(validator);
}
private class DummyValidationAttribute : ValidationAttribute
{
}
[DummyValidation]
private class DummyClassWithDummyValidationAttribute
{
}
// Default IValidatableObject adapter factory
[Fact]
public void IValidatableObjectGetsAValidator()
{
// Arrange
var provider = new DataAnnotationsModelValidatorProvider();
var mockValidatable = new Mock<IValidatableObject>();
var metadata = _metadataProvider.GetMetadataForType(() => null, mockValidatable.Object.GetType());
// Act
var validators = provider.GetValidators(metadata);
// Assert
Assert.Single(validators);
}
// Integration with metadata system
[Fact]
public void DoesNotReadPropertyValue()
{
// Arrange
var provider = new DataAnnotationsModelValidatorProvider();
var model = new ObservableModel();
var metadata = _metadataProvider.GetMetadataForProperty(() => model.TheProperty, typeof(ObservableModel), "TheProperty");
var context = new ModelValidationContext(metadata, null, null, null);
// Act
var validators = provider.GetValidators(metadata).ToArray();
var results = validators.SelectMany(o => o.Validate(context)).ToArray();
// Assert
Assert.Empty(validators);
Assert.False(model.PropertyWasRead());
}
private class ObservableModel
{
private bool _propertyWasRead;
public string TheProperty
{
get
{
_propertyWasRead = true;
return "Hello";
}
}
public bool PropertyWasRead()
{
return _propertyWasRead;
}
}
private class BaseModel
{
public virtual string MyProperty { get; set; }
}
private class DerivedModel : BaseModel
{
[StringLength(10)]
public override string MyProperty
{
get { return base.MyProperty; }
set { base.MyProperty = value; }
}
}
}
}

View File

@ -0,0 +1,231 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Moq;
using Moq.Protected;
using Xunit;
using Xunit.Extensions;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class DataAnnotationsModelValidatorTest
{
private static DataAnnotationsModelMetadataProvider _metadataProvider = new DataAnnotationsModelMetadataProvider();
[Fact]
public void ConstructorGuards()
{
// Arrange
var metadata = _metadataProvider.GetMetadataForType(null, typeof(object));
var attribute = new RequiredAttribute();
// Act & Assert
ExceptionAssert.ThrowsArgumentNull(
() => new DataAnnotationsModelValidator(null),
"attribute");
}
[Fact]
public void ValuesSet()
{
// Arrange
var metadata = _metadataProvider.GetMetadataForProperty(() => 15, typeof(string), "Length");
var attribute = new RequiredAttribute();
// Act
var validator = new DataAnnotationsModelValidator(attribute);
// Assert
Assert.Same(attribute, validator.Attribute);
}
public static IEnumerable<object[]> ValidateSetsMemberNamePropertyDataSet
{
get
{
yield return new object[]
{
_metadataProvider.GetMetadataForProperty(() => 15, typeof(string), "Length"),
"Length"
};
yield return new object[]
{
_metadataProvider.GetMetadataForType(() => new object(), typeof(SampleModel)),
"SampleModel"
};
}
}
[Theory]
[PropertyData("ValidateSetsMemberNamePropertyDataSet")]
public void ValidateSetsMemberNamePropertyOfValidationContextForProperties(ModelMetadata metadata, string expectedMemberName)
{
// Arrange
var attribute = new Mock<ValidationAttribute> { CallBase = true };
attribute.Protected()
.Setup<ValidationResult>("IsValid", ItExpr.IsAny<object>(), ItExpr.IsAny<ValidationContext>())
.Callback((object o, ValidationContext context) =>
{
Assert.Equal(expectedMemberName, context.MemberName);
})
.Returns(ValidationResult.Success)
.Verifiable();
var validator = new DataAnnotationsModelValidator(attribute.Object);
var validationContext = CreateValidationContext(metadata);
// Act
var results = validator.Validate(validationContext);
// Assert
Assert.Empty(results);
attribute.VerifyAll();
}
[Fact]
public void ValidateWithIsValidTrue()
{
// Arrange
var metadata = _metadataProvider.GetMetadataForProperty(() => 15, typeof(string), "Length");
var attribute = new Mock<ValidationAttribute> { CallBase = true };
attribute.Setup(a => a.IsValid(metadata.Model)).Returns(true);
var validator = new DataAnnotationsModelValidator(attribute.Object);
var validationContext = CreateValidationContext(metadata);
// Act
var result = validator.Validate(validationContext);
// Assert
Assert.Empty(result);
}
[Fact]
public void ValidateWithIsValidFalse()
{
// Arrange
var metadata = _metadataProvider.GetMetadataForProperty(() => 15, typeof(string), "Length");
var attribute = new Mock<ValidationAttribute> { CallBase = true };
attribute.Setup(a => a.IsValid(metadata.Model)).Returns(false);
var validator = new DataAnnotationsModelValidator(attribute.Object);
var validationContext = CreateValidationContext(metadata);
// Act
var result = validator.Validate(validationContext);
// Assert
var validationResult = result.Single();
Assert.Equal("", validationResult.MemberName);
Assert.Equal(attribute.Object.FormatErrorMessage("Length"), validationResult.Message);
}
[Fact]
public void ValidatateWithValidationResultSuccess()
{
// Arrange
var metadata = _metadataProvider.GetMetadataForProperty(() => 15, typeof(string), "Length");
var attribute = new Mock<ValidationAttribute> { CallBase = true };
attribute.Protected()
.Setup<ValidationResult>("IsValid", ItExpr.IsAny<object>(), ItExpr.IsAny<ValidationContext>())
.Returns(ValidationResult.Success);
DataAnnotationsModelValidator validator = new DataAnnotationsModelValidator(attribute.Object);
var validationContext = CreateValidationContext(metadata);
// Act
var result = validator.Validate(validationContext);
// Assert
Assert.Empty(result);
}
[Fact]
public void ValidateReturnsSingleValidationResultIfMemberNameSequenceIsEmpty()
{
// Arrange
const string errorMessage = "Some error message";
var metadata = _metadataProvider.GetMetadataForProperty(() => 15, typeof(string), "Length");
var attribute = new Mock<ValidationAttribute> { CallBase = true };
attribute.Protected()
.Setup<ValidationResult>("IsValid", ItExpr.IsAny<object>(), ItExpr.IsAny<ValidationContext>())
.Returns(new ValidationResult(errorMessage, memberNames: null));
var validator = new DataAnnotationsModelValidator(attribute.Object);
var validationContext = CreateValidationContext(metadata);
// Act
var results = validator.Validate(validationContext);
// Assert
ModelValidationResult validationResult = Assert.Single(results);
Assert.Equal(errorMessage, validationResult.Message);
Assert.Empty(validationResult.MemberName);
}
[Fact]
public void ValidateReturnsSingleValidationResultIfOneMemberNameIsSpecified()
{
// Arrange
const string errorMessage = "A different error message";
var metadata = _metadataProvider.GetMetadataForType(() => new object(), typeof(object));
var attribute = new Mock<ValidationAttribute> { CallBase = true };
attribute.Protected()
.Setup<ValidationResult>("IsValid", ItExpr.IsAny<object>(), ItExpr.IsAny<ValidationContext>())
.Returns(new ValidationResult(errorMessage, new[] { "FirstName" }));
var validator = new DataAnnotationsModelValidator(attribute.Object);
var validationContext = CreateValidationContext(metadata);
// Act
var results = validator.Validate(validationContext);
// Assert
ModelValidationResult validationResult = Assert.Single(results);
Assert.Equal(errorMessage, validationResult.Message);
Assert.Equal("FirstName", validationResult.MemberName);
}
[Fact]
public void ValidateReturnsMemberNameIfItIsDifferentFromDisplayName()
{
// Arrange
var metadata = _metadataProvider.GetMetadataForType(() => new SampleModel(), typeof(SampleModel));
var attribute = new Mock<ValidationAttribute> { CallBase = true };
attribute.Protected()
.Setup<ValidationResult>("IsValid", ItExpr.IsAny<object>(), ItExpr.IsAny<ValidationContext>())
.Returns(new ValidationResult("Name error", new[] { "Name" }));
var validator = new DataAnnotationsModelValidator(attribute.Object);
var validationContext = CreateValidationContext(metadata);
// Act
var results = validator.Validate(validationContext);
// Assert
ModelValidationResult validationResult = Assert.Single(results);
Assert.Equal("Name", validationResult.MemberName);
}
[Fact]
public void IsRequiredTests()
{
// Arrange
var metadata = _metadataProvider.GetMetadataForProperty(() => 15, typeof(string), "Length");
// Act & Assert
Assert.False(new DataAnnotationsModelValidator(new RangeAttribute(10, 20)).IsRequired);
Assert.True(new DataAnnotationsModelValidator(new RequiredAttribute()).IsRequired);
Assert.True(new DataAnnotationsModelValidator(new DerivedRequiredAttribute()).IsRequired);
}
private static ModelValidationContext CreateValidationContext(ModelMetadata metadata)
{
return new ModelValidationContext(metadata, null, null, null);
}
class DerivedRequiredAttribute : RequiredAttribute
{
}
class SampleModel
{
public string Name { get; set; }
}
}
}

View File

@ -0,0 +1,92 @@
using System.Runtime.Serialization;
using Xunit;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class DataMemberModelValidatorProviderTest
{
private readonly DataAnnotationsModelMetadataProvider _metadataProvider = new DataAnnotationsModelMetadataProvider();
[Fact]
public void ClassWithoutAttributes_NoValidator()
{
// Arrange
var provider = new DataMemberModelValidatorProvider();
var metadata = _metadataProvider.GetMetadataForProperty(() => null, typeof(ClassWithoutAttributes), "TheProperty");
// Act
var validators = provider.GetValidators(metadata);
// Assert
Assert.Empty(validators);
}
class ClassWithoutAttributes
{
public int TheProperty { get; set; }
}
[Fact]
public void ClassWithDataMemberIsRequiredTrue_Validator()
{
// Arrange
var provider = new DataMemberModelValidatorProvider();
var metadata = _metadataProvider.GetMetadataForProperty(() => null, typeof(ClassWithDataMemberIsRequiredTrue), "TheProperty");
// Act
var validators = provider.GetValidators(metadata);
// Assert
var validator = Assert.Single(validators);
Assert.True(validator.IsRequired);
}
[DataContract]
class ClassWithDataMemberIsRequiredTrue
{
[DataMember(IsRequired = true)]
public int TheProperty { get; set; }
}
[Fact]
public void ClassWithDataMemberIsRequiredFalse_NoValidator()
{
// Arrange
var provider = new DataMemberModelValidatorProvider();
var metadata = _metadataProvider.GetMetadataForProperty(() => null, typeof(ClassWithDataMemberIsRequiredFalse), "TheProperty");
// Act
var validators = provider.GetValidators(metadata);
// Assert
Assert.Empty(validators);
}
[DataContract]
class ClassWithDataMemberIsRequiredFalse
{
[DataMember(IsRequired = false)]
public int TheProperty { get; set; }
}
[Fact]
public void ClassWithDataMemberIsRequiredTrueWithoutDataContract_NoValidator()
{
// Arrange
var provider = new DataMemberModelValidatorProvider();
var metadata = _metadataProvider.GetMetadataForProperty(() => null, typeof(ClassWithDataMemberIsRequiredTrueWithoutDataContract), "TheProperty");
// Act
var validators = provider.GetValidators(metadata);
// Assert
Assert.Empty(validators);
}
class ClassWithDataMemberIsRequiredTrueWithoutDataContract
{
[DataMember(IsRequired = true)]
public int TheProperty { get; set; }
}
}
}

View File

@ -0,0 +1,29 @@
using System;
using Xunit;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class ErrorModelValidatorTest
{
private readonly DataAnnotationsModelMetadataProvider _metadataProvider = new DataAnnotationsModelMetadataProvider();
[Fact]
public void ConstructorGuards()
{
// Act and Assert
ExceptionAssert.ThrowsArgumentNull(
() => new ErrorModelValidator(errorMessage: null),
"errorMessage");
}
[Fact]
public void ValidateThrowsException()
{
// Arrange
var validator = new ErrorModelValidator("error");
// Act and Assert
ExceptionAssert.Throws<InvalidOperationException>(() => validator.Validate(null), "error");
}
}
}

View File

@ -0,0 +1,94 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Runtime.Serialization;
using Xunit;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class InvalidModelValidatorProviderTest
{
private static DataAnnotationsModelMetadataProvider _metadataProvider = new DataAnnotationsModelMetadataProvider();
[Fact]
public void GetValidatorsReturnsNothingForValidModel()
{
// Arrange
var validatorProvider = new InvalidModelValidatorProvider();
// Act
var validators = validatorProvider.GetValidators(_metadataProvider.GetMetadataForType(null, typeof(ValidModel)));
// Assert
Assert.Empty(validators);
}
[Fact]
public void GetValidatorsReturnsInvalidModelValidatorsForInvalidModelType()
{
// Arrange
var name = typeof(InvalidModel).FullName;
var validatorProvider = new InvalidModelValidatorProvider();
// Act
var validators = validatorProvider.GetValidators(_metadataProvider.GetMetadataForType(null, typeof(InvalidModel)));
// Assert
Assert.Equal(2, validators.Count());
ExceptionAssert.Throws<InvalidOperationException>(() => validators.ElementAt(0).Validate(null),
"Non-public property 'Internal' on type '" + name + "' is attributed with one or more validation attributes. Validation attributes on non-public properties are not supported. Consider using a public property for validation instead.");
ExceptionAssert.Throws<InvalidOperationException>(() => validators.ElementAt(1).Validate(null),
"Field 'Field' on type '" + name + "' is attributed with one or more validation attributes. Validation attributes on fields are not supported. Consider using a public property for validation instead.");
}
[Fact]
public void GetValidatorsReturnsInvalidModelValidatorsForInvalidModelProperty()
{
// Arrange
var name = typeof(InvalidModel).FullName;
var validatorProvider = new InvalidModelValidatorProvider();
// Act
var validators = validatorProvider.GetValidators(_metadataProvider.GetMetadataForProperty(null, typeof(InvalidModel), "Value"));
// Assert
Assert.Equal(1, validators.Count());
ExceptionAssert.Throws<InvalidOperationException>(() => validators.First().Validate(null),
"Property 'Value' on type '" + name + "' is invalid. Value-typed properties marked as [Required] must also be marked with [DataMember(IsRequired=true)] to be recognized as required. Consider attributing the declaring type with [DataContract] and the property with [DataMember(IsRequired=true)].");
}
[DataContract]
public class ValidModel
{
[Required]
[DataMember]
[StringLength(10)]
public string Ref { get; set; }
[DataMember]
internal string Internal { get; set; }
[Required]
[DataMember(IsRequired = true)]
public int Value { get; set; }
public string Field;
}
public class InvalidModel
{
[Required]
public string Ref { get; set; }
[StringLength(10)]
[RegularExpression("pattern")]
internal string Internal { get; set; }
[Required]
public int Value { get; set; }
[StringLength(10)]
public string Field;
}
}
}

View File

@ -0,0 +1,326 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Microsoft.AspNet.Mvc.ModelBinding.Test;
using Xunit;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class ModelValidationNodeTest
{
[Fact]
public void ConstructorSetsCollectionInstance()
{
// Arrange
var metadata = GetModelMetadata();
var modelStateKey = "someKey";
var childNodes = new[]
{
new ModelValidationNode(metadata, "someKey0"),
new ModelValidationNode(metadata, "someKey1")
};
// Act
var node = new ModelValidationNode(metadata, modelStateKey, childNodes);
// Assert
Assert.Equal(childNodes, node.ChildNodes.ToArray());
}
[Fact]
public void PropertiesAreSet()
{
// Arrange
var metadata = GetModelMetadata();
var modelStateKey = "someKey";
// Act
var node = new ModelValidationNode(metadata, modelStateKey);
// Assert
Assert.Equal(metadata, node.ModelMetadata);
Assert.Equal(modelStateKey, node.ModelStateKey);
Assert.NotNull(node.ChildNodes);
Assert.Empty(node.ChildNodes);
}
[Fact]
public void CombineWith()
{
// Arrange
var log = new List<string>();
var allChildNodes = new[]
{
new ModelValidationNode(GetModelMetadata(), "key1"),
new ModelValidationNode(GetModelMetadata(), "key2"),
new ModelValidationNode(GetModelMetadata(), "key3"),
};
var parentNode1 = new ModelValidationNode(GetModelMetadata(), "parent1");
parentNode1.ChildNodes.Add(allChildNodes[0]);
parentNode1.Validating += (sender, e) => log.Add("Validating parent1.");
parentNode1.Validated += (sender, e) => log.Add("Validated parent1.");
var parentNode2 = new ModelValidationNode(GetModelMetadata(), "parent2");
parentNode2.ChildNodes.Add(allChildNodes[1]);
parentNode2.ChildNodes.Add(allChildNodes[2]);
parentNode2.Validating += (sender, e) => log.Add("Validating parent2.");
parentNode2.Validated += (sender, e) => log.Add("Validated parent2.");
var context = CreateContext();
// Act
parentNode1.CombineWith(parentNode2);
parentNode1.Validate(context);
// Assert
Assert.Equal(new[] { "Validating parent1.", "Validating parent2.", "Validated parent1.", "Validated parent2." }, log.ToArray());
Assert.Equal(allChildNodes, parentNode1.ChildNodes.ToArray());
}
[Fact]
public void CombineWith_OtherNodeIsSuppressed_DoesNothing()
{
// Arrange
var log = new List<string>();
var allChildNodes = new[]
{
new ModelValidationNode(GetModelMetadata(), "key1"),
new ModelValidationNode(GetModelMetadata(), "key2"),
new ModelValidationNode(GetModelMetadata(), "key3"),
};
var expectedChildNodes = new[]
{
allChildNodes[0]
};
var parentNode1 = new ModelValidationNode(GetModelMetadata(), "parent1");
parentNode1.ChildNodes.Add(allChildNodes[0]);
parentNode1.Validating += (sender, e) => log.Add("Validating parent1.");
parentNode1.Validated += (sender, e) => log.Add("Validated parent1.");
var parentNode2 = new ModelValidationNode(GetModelMetadata(), "parent2");
parentNode2.ChildNodes.Add(allChildNodes[1]);
parentNode2.ChildNodes.Add(allChildNodes[2]);
parentNode2.Validating += (sender, e) => log.Add("Validating parent2.");
parentNode2.Validated += (sender, e) => log.Add("Validated parent2.");
parentNode2.SuppressValidation = true;
var context = CreateContext();
// Act
parentNode1.CombineWith(parentNode2);
parentNode1.Validate(context);
// Assert
Assert.Equal(new[] { "Validating parent1.", "Validated parent1." }, log.ToArray());
Assert.Equal(expectedChildNodes, parentNode1.ChildNodes.ToArray());
}
[Fact]
public void Validate_Ordering()
{
// Proper order of invocation:
// 1. OnValidating()
// 2. Child validators
// 3. This validator
// 4. OnValidated()
// Arrange
var log = new List<string>();
var model = new LoggingValidatableObject(log);
var modelMetadata = GetModelMetadata(model);
var childMetadata = new EmptyModelMetadataProvider().GetMetadataForProperty(() => model, model.GetType(), "ValidStringProperty");
var node = new ModelValidationNode(modelMetadata, "theKey");
node.Validating += (sender, e) => log.Add("In OnValidating()");
node.Validated += (sender, e) => log.Add("In OnValidated()");
node.ChildNodes.Add(new ModelValidationNode(childMetadata, "theKey.ValidStringProperty"));
var context = CreateContext(modelMetadata);
// Act
node.Validate(context);
// Assert
Assert.Equal(new[] { "In OnValidating()", "In LoggingValidatonAttribute.IsValid()", "In IValidatableObject.Validate()", "In OnValidated()" }, log.ToArray());
}
[Fact]
public void Validate_SkipsRemainingValidationIfModelStateIsInvalid()
{
// Because a property validator fails, the model validator shouldn't run
// Arrange
var log = new List<string>();
var model = new LoggingValidatableObject(log);
var modelMetadata = GetModelMetadata(model);
var childMetadata = new EmptyModelMetadataProvider().GetMetadataForProperty(() => model, model.GetType(), "InvalidStringProperty");
var node = new ModelValidationNode(modelMetadata, "theKey");
node.ChildNodes.Add(new ModelValidationNode(childMetadata, "theKey.InvalidStringProperty"));
node.Validating += (sender, e) => log.Add("In OnValidating()");
node.Validated += (sender, e) => log.Add("In OnValidated()");
var context = CreateContext(modelMetadata);
// Act
node.Validate(context);
// Assert
Assert.Equal(new[] { "In OnValidating()", "In IValidatableObject.Validate()", "In OnValidated()" }, log.ToArray());
Assert.Equal("Sample error message", context.ModelState["theKey.InvalidStringProperty"].Errors[0].ErrorMessage);
}
[Fact]
public void Validate_SkipsValidationIfHandlerCancels()
{
// Arrange
var log = new List<string>();
var model = new LoggingValidatableObject(log);
var modelMetadata = GetModelMetadata(model);
var node = new ModelValidationNode(modelMetadata, "theKey");
node.Validating += (sender, e) =>
{
log.Add("In OnValidating()");
e.Cancel = true;
};
node.Validated += (sender, e) => log.Add("In OnValidated()");
var context = CreateContext(modelMetadata);
// Act
node.Validate(context);
// Assert
Assert.Equal(new[] { "In OnValidating()" }, log.ToArray());
}
[Fact]
public void Validate_SkipsValidationIfSuppressed()
{
// Arrange
var log = new List<string>();
var model = new LoggingValidatableObject(log);
var modelMetadata = GetModelMetadata(model);
var node = new ModelValidationNode(modelMetadata, "theKey")
{
SuppressValidation = true
};
node.Validating += (sender, e) => log.Add("In OnValidating()");
node.Validated += (sender, e) => log.Add("In OnValidated()");
var context = CreateContext();
// Act
node.Validate(context);
// Assert
Assert.Empty(log);
}
[Fact]
public void Validate_ThrowsIfControllerContextIsNull()
{
// Arrange
var node = new ModelValidationNode(GetModelMetadata(), "someKey");
// Act & assert
ExceptionAssert.ThrowsArgumentNull(
() => node.Validate(null),
"validationContext");
}
[Fact]
[ReplaceCulture]
public void Validate_ValidateAllProperties_AddsValidationErrors()
{
// Arrange
var model = new ValidateAllPropertiesModel
{
RequiredString = null /* error */,
RangedInt = 0 /* error */,
ValidString = "dog"
};
var modelMetadata = GetModelMetadata(model);
var node = new ModelValidationNode(modelMetadata, "theKey")
{
ValidateAllProperties = true
};
var context = CreateContext(modelMetadata);
context.ModelState.AddModelError("theKey.RequiredString.Dummy", "existing Error Text");
// Act
node.Validate(context);
// Assert
Assert.False(context.ModelState.ContainsKey("theKey.RequiredString"));
Assert.Equal("existing Error Text", context.ModelState["theKey.RequiredString.Dummy"].Errors[0].ErrorMessage);
Assert.Equal("The field RangedInt must be between 10 and 30.", context.ModelState["theKey.RangedInt"].Errors[0].ErrorMessage);
Assert.False(context.ModelState.ContainsKey("theKey.ValidString"));
Assert.False(context.ModelState.ContainsKey("theKey"));
}
private static ModelMetadata GetModelMetadata()
{
return new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(object));
}
private static ModelMetadata GetModelMetadata(object o)
{
return new DataAnnotationsModelMetadataProvider().GetMetadataForType(() => o, o.GetType());
}
private static ModelValidationContext CreateContext(ModelMetadata metadata = null)
{
var providers = new IModelValidatorProvider[] {
new DataAnnotationsModelValidatorProvider(),
new DataMemberModelValidatorProvider()
};
return new ModelValidationContext(metadata,
new ModelStateDictionary(),
new EmptyModelMetadataProvider(),
providers);
}
private sealed class LoggingValidatableObject : IValidatableObject
{
private readonly IList<string> _log;
public LoggingValidatableObject(IList<string> log)
{
_log = log;
}
[LoggingValidation]
public string ValidStringProperty { get; set; }
public string InvalidStringProperty { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
_log.Add("In IValidatableObject.Validate()");
yield return new ValidationResult("Sample error message", new[] { "InvalidStringProperty" });
}
private sealed class LoggingValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
LoggingValidatableObject lvo = (LoggingValidatableObject)value;
lvo._log.Add("In LoggingValidatonAttribute.IsValid()");
return ValidationResult.Success;
}
}
}
private class ValidateAllPropertiesModel
{
[Required]
public string RequiredString { get; set; }
[Range(10, 30)]
public int RangedInt { get; set; }
[RegularExpression("dog")]
public string ValidString { get; set; }
}
}
}

View File

@ -10,6 +10,16 @@
"Xunit.extensions": "1.9.1"
},
"configurations": {
"net45": { }
"net45": {
dependencies: {
"System.ComponentModel.DataAnnotations": "",
"System.Runtime.Serialization": ""
}
},
"k10" : {
dependencies: {
"Microsoft.ComponentModel.DataAnnotations" : "0.1-alpha-*"
}
}
}
}

View File

@ -3,9 +3,9 @@ using System.Globalization;
using System.Threading;
using Xunit;
namespace Microsoft.AspNet.Mvc.ModelBinding.Test
namespace Microsoft.AspNet.Mvc
{
public class CultureReplacer : IDisposable
internal class CultureReplacer : IDisposable
{
private const string _defaultCultureName = "en-GB";
private const string _defaultUICultureName = "en-US";

View File

@ -2,9 +2,9 @@
using System.Reflection;
using Xunit;
namespace Microsoft.AspNet.Mvc.ModelBinding.Test
namespace Microsoft.AspNet.Mvc
{
public static class ExceptionAssert
internal static class ExceptionAssert
{
/// <summary>
/// Verifies that an exception of the given type (or optionally a derived type) is thrown.

View File

@ -0,0 +1,56 @@
using System;
using System.Globalization;
using System.Reflection;
using System.Threading;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Replaces the current culture and UI culture for the test.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class ReplaceCultureAttribute : Xunit.BeforeAfterTestAttribute
{
private const string _defaultCultureName = "en-GB";
private const string _defaultUICultureName = "en-US";
private static readonly CultureInfo _defaultCulture = CultureInfo.GetCultureInfo(_defaultCultureName);
private CultureInfo _originalCulture;
private CultureInfo _originalUICulture;
public ReplaceCultureAttribute()
{
Culture = _defaultCulture;
UICulture = _defaultCulture;
}
/// <summary>
/// Sets <see cref="Thread.CurrentCulture"/> for the test. Defaults to en-GB.
/// </summary>
/// <remarks>
/// en-GB is used here as the default because en-US is equivalent to the InvariantCulture. We
/// want to be able to find bugs where we're accidentally relying on the Invariant instead of the
/// user's culture.
/// </remarks>
public CultureInfo Culture { get; set; }
/// <summary>
/// Sets <see cref="Thread.CurrentUICulture"/> for the test. Defaults to en-US.
/// </summary>
public CultureInfo UICulture { get; set; }
public override void Before(MethodInfo methodUnderTest)
{
_originalCulture = Thread.CurrentThread.CurrentCulture;
_originalUICulture = Thread.CurrentThread.CurrentUICulture;
Thread.CurrentThread.CurrentCulture = Culture;
Thread.CurrentThread.CurrentUICulture = UICulture;
}
public override void After(MethodInfo methodUnderTest)
{
Thread.CurrentThread.CurrentCulture = _originalCulture;
Thread.CurrentThread.CurrentUICulture = _originalUICulture;
}
}
}