Reintroduce model validation
This commit is contained in:
parent
cca3f92c5f
commit
e434918337
|
|
@ -1,7 +1,11 @@
|
||||||
namespace MvcSample.Models
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace MvcSample.Models
|
||||||
{
|
{
|
||||||
public class User
|
public class User
|
||||||
{
|
{
|
||||||
|
[Required]
|
||||||
|
[MinLength(4)]
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string Address { get; set; }
|
public string Address { get; set; }
|
||||||
public int Age { get; set; }
|
public int Age { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,42 @@
|
||||||
{
|
{
|
||||||
"version" : "0.1-alpha-*",
|
"version": "0.1-alpha-*",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.AspNet.Abstractions": "0.1-alpha-*",
|
"Microsoft.AspNet.Abstractions": "0.1-alpha-*",
|
||||||
"Microsoft.AspNet.ConfigurationModel": "0.1-alpha-*",
|
"Microsoft.AspNet.ConfigurationModel": "0.1-alpha-*",
|
||||||
"Microsoft.AspNet.DependencyInjection" : "0.1-alpha-*",
|
"Microsoft.AspNet.DependencyInjection": "0.1-alpha-*",
|
||||||
"Microsoft.AspNet.Routing" : "0.1-alpha-*",
|
"Microsoft.AspNet.Routing": "0.1-alpha-*",
|
||||||
"Microsoft.AspNet.Mvc.ModelBinding" : "",
|
"Microsoft.AspNet.Mvc.ModelBinding": "",
|
||||||
"Microsoft.AspNet.Mvc.Core" : "",
|
"Microsoft.AspNet.Mvc.Core": "",
|
||||||
"Microsoft.AspNet.Mvc" : "",
|
"Microsoft.AspNet.Mvc": "",
|
||||||
"Microsoft.AspNet.Mvc.Razor": "",
|
"Microsoft.AspNet.Mvc.Razor": "",
|
||||||
"Microsoft.AspNet.Mvc.Rendering" : ""
|
"Microsoft.AspNet.Mvc.Rendering": ""
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"net45": {
|
"net45": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Autofac": "3.3.0",
|
"Autofac": "3.3.0",
|
||||||
"Owin": "1.0",
|
"Owin": "1.0",
|
||||||
"Microsoft.AspNet.DependencyInjection.Autofac": "0.1-alpha-*",
|
"Microsoft.AspNet.DependencyInjection.Autofac": "0.1-alpha-*",
|
||||||
"Microsoft.Owin": "2.1.0",
|
"Microsoft.Owin": "2.1.0",
|
||||||
"Microsoft.Owin.Diagnostics": "2.1.0",
|
"Microsoft.Owin.Diagnostics": "2.1.0",
|
||||||
"Microsoft.Owin.Hosting": "2.1.0",
|
"Microsoft.Owin.Hosting": "2.1.0",
|
||||||
"Microsoft.Owin.Host.HttpListener": "2.1.0",
|
"Microsoft.Owin.Host.HttpListener": "2.1.0",
|
||||||
"Microsoft.AspNet.AppBuilderSupport": "0.1-alpha-*"
|
"Microsoft.AspNet.AppBuilderSupport": "0.1-alpha-*",
|
||||||
}
|
"System.ComponentModel.DataAnnotations": ""
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"k10" : {
|
"k10": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"System.ComponentModel": "4.0.0.0",
|
"System.ComponentModel": "4.0.0.0",
|
||||||
"System.Console": "4.0.0.0",
|
"System.Console": "4.0.0.0",
|
||||||
"System.Diagnostics.Debug": "4.0.10.0",
|
"System.Diagnostics.Debug": "4.0.10.0",
|
||||||
"System.Diagnostics.Tools": "4.0.0.0",
|
"System.Diagnostics.Tools": "4.0.0.0",
|
||||||
"System.Dynamic.Runtime": "4.0.0.0",
|
"System.Dynamic.Runtime": "4.0.0.0",
|
||||||
"System.Runtime": "4.0.20.0",
|
"System.Runtime": "4.0.20.0",
|
||||||
"System.Runtime.InteropServices": "4.0.10.0",
|
"System.Runtime.InteropServices": "4.0.10.0",
|
||||||
"System.Threading.Tasks": "4.0.0.0"
|
"System.Threading.Tasks": "4.0.0.0",
|
||||||
}
|
"Microsoft.ComponentModel.DataAnnotations": "0.1-alpha-*"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -29,6 +29,7 @@ namespace Microsoft.AspNet.Mvc.Internal
|
||||||
ModelMetadata = modelMetadata,
|
ModelMetadata = modelMetadata,
|
||||||
ModelBinder = actionBindingContext.ModelBinder,
|
ModelBinder = actionBindingContext.ModelBinder,
|
||||||
ValueProvider = actionBindingContext.ValueProvider,
|
ValueProvider = actionBindingContext.ValueProvider,
|
||||||
|
ValidatorProviders = actionBindingContext.ValidatorProviders,
|
||||||
MetadataProvider = metadataProvider,
|
MetadataProvider = metadataProvider,
|
||||||
HttpContext = actionBindingContext.ActionContext.HttpContext,
|
HttpContext = actionBindingContext.ActionContext.HttpContext,
|
||||||
FallbackToEmptyPrefix = true
|
FallbackToEmptyPrefix = true
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc
|
namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
|
|
@ -8,13 +9,15 @@ namespace Microsoft.AspNet.Mvc
|
||||||
IModelMetadataProvider metadataProvider,
|
IModelMetadataProvider metadataProvider,
|
||||||
IModelBinder modelBinder,
|
IModelBinder modelBinder,
|
||||||
IValueProvider valueProvider,
|
IValueProvider valueProvider,
|
||||||
IInputFormatter inputFormatter)
|
IInputFormatter inputFormatter,
|
||||||
|
IEnumerable<IModelValidatorProvider> validatorProviders)
|
||||||
{
|
{
|
||||||
ActionContext = context;
|
ActionContext = context;
|
||||||
MetadataProvider = metadataProvider;
|
MetadataProvider = metadataProvider;
|
||||||
ModelBinder = modelBinder;
|
ModelBinder = modelBinder;
|
||||||
ValueProvider = valueProvider;
|
ValueProvider = valueProvider;
|
||||||
InputFormatter = inputFormatter;
|
InputFormatter = inputFormatter;
|
||||||
|
ValidatorProviders = validatorProviders;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ActionContext ActionContext { get; private set; }
|
public ActionContext ActionContext { get; private set; }
|
||||||
|
|
@ -26,5 +29,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
public IValueProvider ValueProvider { get; private set; }
|
public IValueProvider ValueProvider { get; private set; }
|
||||||
|
|
||||||
public IInputFormatter InputFormatter { get; private set; }
|
public IInputFormatter InputFormatter { get; private set; }
|
||||||
|
|
||||||
|
public IEnumerable<IModelValidatorProvider> ValidatorProviders { get; private set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,17 +10,20 @@ namespace Microsoft.AspNet.Mvc
|
||||||
private readonly IModelMetadataProvider _modelMetadataProvider;
|
private readonly IModelMetadataProvider _modelMetadataProvider;
|
||||||
private readonly IEnumerable<IModelBinder> _modelBinders;
|
private readonly IEnumerable<IModelBinder> _modelBinders;
|
||||||
private readonly IEnumerable<IValueProviderFactory> _valueProviderFactories;
|
private readonly IEnumerable<IValueProviderFactory> _valueProviderFactories;
|
||||||
private readonly IEnumerable<IInputFormatter> _bodyReaders;
|
private readonly IEnumerable<IInputFormatter> _inputFormatters;
|
||||||
|
private readonly IEnumerable<IModelValidatorProvider> _validatorProviders;
|
||||||
|
|
||||||
public DefaultActionBindingContextProvider(IModelMetadataProvider modelMetadataProvider,
|
public DefaultActionBindingContextProvider(IModelMetadataProvider modelMetadataProvider,
|
||||||
IEnumerable<IModelBinder> modelBinders,
|
IEnumerable<IModelBinder> modelBinders,
|
||||||
IEnumerable<IValueProviderFactory> valueProviderFactories,
|
IEnumerable<IValueProviderFactory> valueProviderFactories,
|
||||||
IEnumerable<IInputFormatter> bodyReaders)
|
IEnumerable<IInputFormatter> inputFormatters,
|
||||||
|
IEnumerable<IModelValidatorProvider> validatorProviders)
|
||||||
{
|
{
|
||||||
_modelMetadataProvider = modelMetadataProvider;
|
_modelMetadataProvider = modelMetadataProvider;
|
||||||
_modelBinders = modelBinders.OrderBy(binder => binder.GetType() == typeof(ComplexModelDtoModelBinder) ? 1 : 0);
|
_modelBinders = modelBinders.OrderBy(binder => binder.GetType() == typeof(ComplexModelDtoModelBinder) ? 1 : 0);
|
||||||
_valueProviderFactories = valueProviderFactories;
|
_valueProviderFactories = valueProviderFactories;
|
||||||
_bodyReaders = bodyReaders;
|
_inputFormatters = inputFormatters;
|
||||||
|
_validatorProviders = validatorProviders;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ActionBindingContext> GetActionBindingContextAsync(ActionContext actionContext)
|
public async Task<ActionBindingContext> GetActionBindingContextAsync(ActionContext actionContext)
|
||||||
|
|
@ -35,7 +38,8 @@ namespace Microsoft.AspNet.Mvc
|
||||||
_modelMetadataProvider,
|
_modelMetadataProvider,
|
||||||
new CompositeModelBinder(_modelBinders),
|
new CompositeModelBinder(_modelBinders),
|
||||||
new CompositeValueProvider(valueProviders),
|
new CompositeValueProvider(valueProviders),
|
||||||
new CompositeInputFormatter(_bodyReaders)
|
new CompositeInputFormatter(_inputFormatters),
|
||||||
|
_validatorProviders
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,6 @@ namespace Microsoft.AspNet.Mvc
|
||||||
actionBindingContext.ModelBinder.BindModel(modelBindingContext);
|
actionBindingContext.ModelBinder.BindModel(modelBindingContext);
|
||||||
parameterValues[parameter.Name] = modelBindingContext.Model;
|
parameterValues[parameter.Name] = modelBindingContext.Model;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return parameterValues;
|
return parameterValues;
|
||||||
|
|
|
||||||
|
|
@ -60,8 +60,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
if (bindingContext.ModelBinder.BindModel(innerBindingContext))
|
if (bindingContext.ModelBinder.BindModel(innerBindingContext))
|
||||||
{
|
{
|
||||||
boundValue = innerBindingContext.Model;
|
boundValue = innerBindingContext.Model;
|
||||||
// TODO: validation
|
bindingContext.ValidationNode.ChildNodes.Add(innerBindingContext.ValidationNode);
|
||||||
// bindingContext.ValidationNode.ChildNodes.Add(innerBindingContext.ValidationNode);
|
|
||||||
}
|
}
|
||||||
boundCollection.Add(ModelBindingHelper.CastOrDefault<TElement>(boundValue));
|
boundCollection.Add(ModelBindingHelper.CastOrDefault<TElement>(boundValue));
|
||||||
}
|
}
|
||||||
|
|
@ -114,9 +113,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
didBind = true;
|
didBind = true;
|
||||||
boundValue = childBindingContext.Model;
|
boundValue = childBindingContext.Model;
|
||||||
|
|
||||||
// TODO: Validation
|
|
||||||
// merge validation up
|
// merge validation up
|
||||||
// bindingContext.ValidationNode.ChildNodes.Add(childBindingContext.ValidationNode);
|
bindingContext.ValidationNode.ChildNodes.Add(childBindingContext.ValidationNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// infinite size collection stops on first bind failure
|
// infinite size collection stops on first bind failure
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
if (bindingContext.ModelBinder.BindModel(propertyBindingContext))
|
if (bindingContext.ModelBinder.BindModel(propertyBindingContext))
|
||||||
{
|
{
|
||||||
dto.Results[propertyMetadata] = new ComplexModelDtoResult(propertyBindingContext.Model/*, propertyBindingContext.ValidationNode*/);
|
dto.Results[propertyMetadata] = new ComplexModelDtoResult(propertyBindingContext.Model, propertyBindingContext.ValidationNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 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;
|
Model = model;
|
||||||
//ValidationNode = validationNode;
|
ValidationNode = validationNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Model { get; private set; }
|
public object Model { get; private set; }
|
||||||
|
|
||||||
//public ModelValidationNode ValidationNode { get; private set; }
|
public ModelValidationNode ValidationNode { get; private set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
|
|
@ -29,14 +28,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
public virtual bool BindModel(ModelBindingContext bindingContext)
|
public virtual bool BindModel(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
ModelBindingContext newBindingContext = CreateNewBindingContext(bindingContext, bindingContext.ModelName);
|
var newBindingContext = CreateNewBindingContext(bindingContext,
|
||||||
|
bindingContext.ModelName,
|
||||||
|
reuseValidationNode: true);
|
||||||
|
|
||||||
bool boundSuccessfully = TryBind(newBindingContext);
|
bool boundSuccessfully = TryBind(newBindingContext);
|
||||||
if (!boundSuccessfully && !String.IsNullOrEmpty(bindingContext.ModelName)
|
if (!boundSuccessfully && !string.IsNullOrEmpty(bindingContext.ModelName)
|
||||||
&& bindingContext.FallbackToEmptyPrefix)
|
&& bindingContext.FallbackToEmptyPrefix)
|
||||||
{
|
{
|
||||||
// fallback to empty prefix?
|
// fallback to empty prefix?
|
||||||
newBindingContext = CreateNewBindingContext(bindingContext, modelName: String.Empty);
|
newBindingContext = CreateNewBindingContext(bindingContext,
|
||||||
|
modelName: string.Empty,
|
||||||
|
reuseValidationNode: false);
|
||||||
boundSuccessfully = TryBind(newBindingContext);
|
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,
|
// 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.
|
// 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.
|
// 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))
|
||||||
//if (!newBindingContext.ModelMetadata.IsComplexType && String.IsNullOrEmpty(newBindingContext.ModelName))
|
{
|
||||||
//{
|
newBindingContext.ValidationNode = new ModelValidationNode(newBindingContext.ModelMetadata, bindingContext.ModelName);
|
||||||
// newBindingContext.ValidationNode = new Validation.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;
|
bindingContext.Model = newBindingContext.Model;
|
||||||
return true;
|
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.
|
// TODO: RuntimeHelpers.EnsureSufficientExecutionStack does not exist in the CoreCLR.
|
||||||
// Protects against stack overflow for deeply nested model binding
|
// Protects against stack overflow for deeply nested model binding
|
||||||
// RuntimeHelpers.EnsureSufficientExecutionStack();
|
// RuntimeHelpers.EnsureSufficientExecutionStack();
|
||||||
|
|
||||||
foreach (IModelBinder binder in Binders)
|
foreach (var binder in Binders)
|
||||||
{
|
{
|
||||||
if (binder.BindModel(bindingContext))
|
if (binder.BindModel(bindingContext))
|
||||||
{
|
{
|
||||||
|
|
@ -85,7 +85,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ModelBindingContext CreateNewBindingContext(ModelBindingContext oldBindingContext, string modelName)
|
private static ModelBindingContext CreateNewBindingContext(ModelBindingContext oldBindingContext,
|
||||||
|
string modelName,
|
||||||
|
bool reuseValidationNode)
|
||||||
{
|
{
|
||||||
var newBindingContext = new ModelBindingContext
|
var newBindingContext = new ModelBindingContext
|
||||||
{
|
{
|
||||||
|
|
@ -93,17 +95,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
ModelName = modelName,
|
ModelName = modelName,
|
||||||
ModelState = oldBindingContext.ModelState,
|
ModelState = oldBindingContext.ModelState,
|
||||||
ValueProvider = oldBindingContext.ValueProvider,
|
ValueProvider = oldBindingContext.ValueProvider,
|
||||||
|
ValidatorProviders = oldBindingContext.ValidatorProviders,
|
||||||
MetadataProvider = oldBindingContext.MetadataProvider,
|
MetadataProvider = oldBindingContext.MetadataProvider,
|
||||||
ModelBinder = oldBindingContext.ModelBinder,
|
ModelBinder = oldBindingContext.ModelBinder,
|
||||||
HttpContext = oldBindingContext.HttpContext
|
HttpContext = oldBindingContext.HttpContext
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Validation
|
// validation is expensive to create, so copy it over if we can
|
||||||
//// validation is expensive to create, so copy it over if we can
|
if (reuseValidationNode)
|
||||||
//if (Object.ReferenceEquals(modelName, oldBindingContext.ModelName))
|
{
|
||||||
//{
|
newBindingContext.ValidationNode = oldBindingContext.ValidationNode;
|
||||||
// newBindingContext.ValidationNode = oldBindingContext.ValidationNode;
|
}
|
||||||
//}
|
|
||||||
|
|
||||||
return newBindingContext;
|
return newBindingContext;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
object untypedModel = propertyBindingContext.Model;
|
object untypedModel = propertyBindingContext.Model;
|
||||||
model = ModelBindingHelper.CastOrDefault<TModel>(untypedModel);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,13 +21,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
EnsureModel(bindingContext);
|
EnsureModel(bindingContext);
|
||||||
IEnumerable<ModelMetadata> propertyMetadatas = GetMetadataForProperties(bindingContext);
|
var propertyMetadatas = GetMetadataForProperties(bindingContext);
|
||||||
ComplexModelDto dto = CreateAndPopulateDto(bindingContext, propertyMetadatas);
|
var dto = CreateAndPopulateDto(bindingContext, propertyMetadatas);
|
||||||
|
|
||||||
// post-processing, e.g. property setters and hooking up validation
|
// post-processing, e.g. property setters and hooking up validation
|
||||||
ProcessDto(bindingContext, dto);
|
ProcessDto(bindingContext, dto);
|
||||||
// TODO: Validation
|
// complex models require full validation
|
||||||
// bindingContext.ValidationNode.ValidateAllProperties = true; // complex models require full validation
|
bindingContext.ValidationNode.ValidateAllProperties = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,7 +39,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
private static bool CanBindType(Type modelType)
|
private static bool CanBindType(Type modelType)
|
||||||
{
|
{
|
||||||
// Simple types cannot use this binder
|
// Simple types cannot use this binder
|
||||||
bool isComplexType = !modelType.HasStringConverter();
|
var isComplexType = !modelType.HasStringConverter();
|
||||||
if (!isComplexType)
|
if (!isComplexType)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -87,8 +87,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
private ComplexModelDto CreateAndPopulateDto(ModelBindingContext bindingContext, IEnumerable<ModelMetadata> propertyMetadatas)
|
private ComplexModelDto CreateAndPopulateDto(ModelBindingContext bindingContext, IEnumerable<ModelMetadata> propertyMetadatas)
|
||||||
{
|
{
|
||||||
// create a DTO and call into the DTO binder
|
// create a DTO and call into the DTO binder
|
||||||
ComplexModelDto originalDto = new ComplexModelDto(bindingContext.ModelMetadata, propertyMetadatas);
|
var originalDto = new ComplexModelDto(bindingContext.ModelMetadata, propertyMetadatas);
|
||||||
ModelBindingContext dtoBindingContext = new ModelBindingContext(bindingContext)
|
var dtoBindingContext = new ModelBindingContext(bindingContext)
|
||||||
{
|
{
|
||||||
ModelMetadata = bindingContext.MetadataProvider.GetMetadataForType(() => originalDto, typeof(ComplexModelDto)),
|
ModelMetadata = bindingContext.MetadataProvider.GetMetadataForType(() => originalDto, typeof(ComplexModelDto)),
|
||||||
ModelName = bindingContext.ModelName
|
ModelName = bindingContext.ModelName
|
||||||
|
|
@ -105,24 +105,26 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
return Activator.CreateInstance(bindingContext.ModelType);
|
return Activator.CreateInstance(bindingContext.ModelType);
|
||||||
}
|
}
|
||||||
|
|
||||||
//// Called when the property setter null check failed, allows us to add our own error message to 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)
|
internal static EventHandler<ModelValidatedEventArgs> CreateNullCheckFailedHandler(ModelMetadata modelMetadata, object incomingValue)
|
||||||
//{
|
{
|
||||||
// return (sender, e) =>
|
return (sender, e) =>
|
||||||
// {
|
{
|
||||||
// ModelValidationNode validationNode = (ModelValidationNode)sender;
|
var validationNode = (ModelValidationNode)sender;
|
||||||
// ModelStateDictionary modelState = e.ActionContext.ModelState;
|
var modelState = e.ValidationContext.ModelState;
|
||||||
|
|
||||||
// if (modelState.IsValidField(validationNode.ModelStateKey))
|
if (modelState.IsValidField(validationNode.ModelStateKey))
|
||||||
// {
|
{
|
||||||
// string errorMessage = ModelBinderConfig.ValueRequiredErrorMessageProvider(e.ActionContext, modelMetadata, incomingValue);
|
// TODO: Revive ModelBinderConfig
|
||||||
// if (errorMessage != null)
|
// string errorMessage = ModelBinderConfig.ValueRequiredErrorMessageProvider(e.ValidationContext, modelMetadata, incomingValue);
|
||||||
// {
|
var errorMessage = e.ValidationContext.ModelMetadata.PropertyName + " is required";
|
||||||
// modelState.AddModelError(validationNode.ModelStateKey, errorMessage);
|
if (errorMessage != null)
|
||||||
// }
|
{
|
||||||
// }
|
modelState.AddModelError(validationNode.ModelStateKey, errorMessage);
|
||||||
// };
|
}
|
||||||
//}
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual void EnsureModel(ModelBindingContext bindingContext)
|
protected virtual void EnsureModel(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
|
|
@ -134,12 +136,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
protected virtual IEnumerable<ModelMetadata> GetMetadataForProperties(ModelBindingContext bindingContext)
|
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
|
// keep a set of the required properties so that we can cross-reference bound properties later
|
||||||
HashSet<string> requiredProperties = new HashSet<string>();
|
HashSet<string> requiredProperties;
|
||||||
// Dictionary<string, ModelValidator> requiredValidators;
|
Dictionary<string, IModelValidator> requiredValidators;
|
||||||
HashSet<string> skipProperties = new HashSet<string>();
|
HashSet<string> skipProperties;
|
||||||
// GetRequiredPropertiesCollection(bindingContext, out requiredProperties, out skipProperties);
|
GetRequiredPropertiesCollection(bindingContext, out requiredProperties, out requiredValidators, out skipProperties);
|
||||||
|
|
||||||
return from propertyMetadata in bindingContext.ModelMetadata.Properties
|
return from propertyMetadata in bindingContext.ModelMetadata.Properties
|
||||||
let propertyName = propertyMetadata.PropertyName
|
let propertyName = propertyMetadata.PropertyName
|
||||||
|
|
@ -150,102 +151,84 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
private static object GetPropertyDefaultValue(PropertyInfo propertyInfo)
|
private static object GetPropertyDefaultValue(PropertyInfo propertyInfo)
|
||||||
{
|
{
|
||||||
DefaultValueAttribute attr = propertyInfo.GetCustomAttribute<DefaultValueAttribute>();
|
var attr = propertyInfo.GetCustomAttribute<DefaultValueAttribute>();
|
||||||
return (attr != null) ? attr.Value : null;
|
return (attr != null) ? attr.Value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
//internal static void GetRequiredPropertiesCollection(ModelBindingContext bindingContext, out HashSet<string> requiredProperties, out HashSet<string> skipProperties)
|
internal static void GetRequiredPropertiesCollection(ModelBindingContext bindingContext,
|
||||||
//{
|
out HashSet<string> requiredProperties,
|
||||||
// requiredProperties = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
out Dictionary<string, IModelValidator> requiredValidators,
|
||||||
// // requiredValidators = new Dictionary<string, ModelValidator>(StringComparer.OrdinalIgnoreCase);
|
out HashSet<string> skipProperties)
|
||||||
// skipProperties = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
{
|
||||||
|
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.
|
// TODO: HttpBindingBehaviorAttribute
|
||||||
// Type modelType = bindingContext.ModelType;
|
var modelTypeInfo = bindingContext.ModelType.GetTypeInfo();
|
||||||
// ICustomTypeDescriptor modelDescriptor = new AssociatedMetadataTypeTypeDescriptionProvider(modelType)
|
foreach (var propertyMetadata in bindingContext.ModelMetadata.Properties)
|
||||||
// .GetTypeDescriptor(modelType);
|
{
|
||||||
|
var propertyName = propertyMetadata.PropertyName;
|
||||||
|
var requiredValidator = bindingContext.GetValidators(propertyMetadata)
|
||||||
|
.FirstOrDefault(v => v.IsRequired);
|
||||||
|
// TODO: Revive HttpBindingBehaviorAttribute
|
||||||
|
|
||||||
// PropertyDescriptorCollection propertyDescriptors = modelDescriptor.GetProperties();
|
if (requiredValidator != null)
|
||||||
|
{
|
||||||
// // TODO: Revive HttpBindingBehavior
|
requiredValidators[propertyName] = requiredValidator;
|
||||||
// // HttpBindingBehaviorAttribute typeAttr = modelDescriptor.GetAttributes().OfType<HttpBindingBehaviorAttribute>().SingleOrDefault();
|
requiredProperties.Add(propertyName);
|
||||||
|
}
|
||||||
// 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);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
internal void ProcessDto(ModelBindingContext bindingContext, ComplexModelDto dto)
|
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;
|
// Eliminate provided properties from requiredProperties; leaving just *missing* required properties.
|
||||||
//// Dictionary<string, ModelValidator> requiredValidators;
|
requiredProperties.ExceptWith(dto.Results.Select(r => r.Key.PropertyName));
|
||||||
//HashSet<string> skipProperties;
|
|
||||||
//GetRequiredPropertiesCollection(context, bindingContext, out requiredProperties, out requiredValidators, out skipProperties);
|
|
||||||
|
|
||||||
//// Eliminate provided properties from requiredProperties; leaving just *missing* required properties.
|
foreach (var missingRequiredProperty in requiredProperties)
|
||||||
//requiredProperties.ExceptWith(dto.Results.Select(r => r.Key.PropertyName));
|
{
|
||||||
|
var addedError = false;
|
||||||
|
var modelStateKey = ModelBindingHelper.CreatePropertyModelName(
|
||||||
|
bindingContext.ValidationNode.ModelStateKey, missingRequiredProperty);
|
||||||
|
|
||||||
//foreach (string missingRequiredProperty in requiredProperties)
|
// 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.
|
||||||
// string modelStateKey = ModelBindingHelper.CreatePropertyModelName(
|
// (Otherwise, propertyMetadata.Model is likely already null.)
|
||||||
// bindingContext.ValidationNode.ModelStateKey, missingRequiredProperty);
|
var propertyMetadata = bindingContext.PropertyMetadata[missingRequiredProperty];
|
||||||
|
propertyMetadata.Model = null;
|
||||||
|
|
||||||
// // Update Model as SetProperty() would: Place null value where validator will check for non-null. This
|
// Execute validator (if any) to get custom error message.
|
||||||
// // ensures a failure result from a required validator (if any) even for a non-nullable property.
|
IModelValidator validator;
|
||||||
// // (Otherwise, propertyMetadata.Model is likely already null.)
|
if (requiredValidators.TryGetValue(missingRequiredProperty, out validator))
|
||||||
// ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[missingRequiredProperty];
|
{
|
||||||
// propertyMetadata.Model = null;
|
addedError = RunValidator(validator, bindingContext, propertyMetadata, modelStateKey);
|
||||||
|
}
|
||||||
|
|
||||||
// // Execute validator (if any) to get custom error message.
|
// Fall back to default message if HttpBindingBehaviorAttribute required this property or validator
|
||||||
// ModelValidator validator = requiredValidators[missingRequiredProperty];
|
// (oddly) succeeded.
|
||||||
// bool addedError = RunValidator(validator, bindingContext, propertyMetadata, modelStateKey);
|
if (!addedError)
|
||||||
|
{
|
||||||
// // Fall back to default message if HttpBindingBehaviorAttribute required this property or validator
|
bindingContext.ModelState.AddModelError(
|
||||||
// // (oddly) succeeded.
|
modelStateKey,
|
||||||
// if (!addedError)
|
Resources.FormatMissingRequiredMember(missingRequiredProperty));
|
||||||
// {
|
}
|
||||||
// bindingContext.ModelState.AddModelError(modelStateKey,
|
}
|
||||||
// Error.Format(SRResources.MissingRequiredMember, missingRequiredProperty));
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
// for each property that was bound, call the setter, recording exceptions as necessary
|
// for each property that was bound, call the setter, recording exceptions as necessary
|
||||||
foreach (var entry in dto.Results)
|
foreach (var entry in dto.Results)
|
||||||
{
|
{
|
||||||
ModelMetadata propertyMetadata = entry.Key;
|
var propertyMetadata = entry.Key;
|
||||||
|
var dtoResult = entry.Value;
|
||||||
ComplexModelDtoResult dtoResult = entry.Value;
|
|
||||||
if (dtoResult != null)
|
if (dtoResult != null)
|
||||||
{
|
{
|
||||||
SetProperty(bindingContext, propertyMetadata, dtoResult);
|
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.")]
|
[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,
|
protected virtual void SetProperty(ModelBindingContext bindingContext,
|
||||||
ModelMetadata propertyMetadata,
|
ModelMetadata propertyMetadata,
|
||||||
ComplexModelDtoResult dtoResult
|
ComplexModelDtoResult dtoResult)
|
||||||
/*, ModelValidator requiredValidator*/)
|
|
||||||
{
|
{
|
||||||
// TODO: This used TypeDescriptor which is no longer available. Lookups performed using System.ComponentModel were
|
var property = bindingContext.ModelType
|
||||||
// cached. To maintain parity, we'll need to cache property lookups.
|
.GetProperty(propertyMetadata.PropertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
|
||||||
PropertyInfo property = bindingContext.ModelType
|
|
||||||
.GetRuntimeProperties()
|
|
||||||
.FirstOrDefault(p => p.Name.Equals(propertyMetadata.PropertyName, StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
|
||||||
if (property == null || !property.CanWrite)
|
if (property == null || !property.CanWrite)
|
||||||
{
|
{
|
||||||
|
|
@ -268,21 +247,28 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
object value = dtoResult.Model ?? GetPropertyDefaultValue(property);
|
var value = dtoResult.Model ?? GetPropertyDefaultValue(property);
|
||||||
propertyMetadata.Model = value;
|
propertyMetadata.Model = value;
|
||||||
|
|
||||||
|
|
||||||
//// 'Required' validators need to run first so that we can provide useful error messages if
|
// '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
|
// the property setters throw, e.g. if we're setting entity keys to null.
|
||||||
//// DefaultModelBinder.SetProperty() for more information.
|
if (value == null)
|
||||||
//if (value == null)
|
{
|
||||||
//{
|
var modelStateKey = dtoResult.ValidationNode.ModelStateKey;
|
||||||
// string modelStateKey = dtoResult.ValidationNode.ModelStateKey;
|
if (bindingContext.ModelState.IsValidField(modelStateKey))
|
||||||
// if (bindingContext.ModelState.IsValidField(modelStateKey))
|
{
|
||||||
// {
|
var requiredValidator = bindingContext.GetValidators(propertyMetadata).FirstOrDefault(v => v.IsRequired);
|
||||||
// RunValidator(requiredValidator, bindingContext, propertyMetadata, modelStateKey);
|
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())
|
if (value != null || property.PropertyType.AllowsNullValue())
|
||||||
{
|
{
|
||||||
|
|
@ -293,39 +279,39 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// don't display a duplicate error message if a binding error has already occurred for this field
|
// don't display a duplicate error message if a binding error has already occurred for this field
|
||||||
//string modelStateKey = dtoResult.ValidationNode.ModelStateKey;
|
var modelStateKey = dtoResult.ValidationNode.ModelStateKey;
|
||||||
//if (bindingContext.ModelState.IsValidField(modelStateKey))
|
if (bindingContext.ModelState.IsValidField(modelStateKey))
|
||||||
//{
|
{
|
||||||
bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex);
|
bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex);
|
||||||
//}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// trying to set a non-nullable value type to null, need to make sure there's a message
|
// trying to set a non-nullable value type to null, need to make sure there's a message
|
||||||
//string modelStateKey = dtoResult.ValidationNode.ModelStateKey;
|
var modelStateKey = dtoResult.ValidationNode.ModelStateKey;
|
||||||
//if (bindingContext.ModelState.IsValidField(modelStateKey))
|
if (bindingContext.ModelState.IsValidField(modelStateKey))
|
||||||
//{
|
{
|
||||||
// dtoResult.ValidationNode.Validated += CreateNullCheckFailedHandler(propertyMetadata, value);
|
dtoResult.ValidationNode.Validated += CreateNullCheckFailedHandler(propertyMetadata, value);
|
||||||
//}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//// Returns true if validator execution adds a model error.
|
// Returns true if validator execution adds a model error.
|
||||||
//private static bool RunValidator(ModelValidator validator, ModelBindingContext bindingContext,
|
private static bool RunValidator(IModelValidator validator,
|
||||||
// ModelMetadata propertyMetadata, string modelStateKey)
|
ModelBindingContext bindingContext,
|
||||||
//{
|
ModelMetadata propertyMetadata,
|
||||||
// bool addedError = false;
|
string modelStateKey)
|
||||||
// if (validator != null)
|
{
|
||||||
// {
|
var validationContext = bindingContext.CreateValidationContext(propertyMetadata);
|
||||||
// foreach (ModelValidationResult validationResult in validator.Validate(propertyMetadata, bindingContext.Model))
|
|
||||||
// {
|
|
||||||
// bindingContext.ModelState.AddModelError(modelStateKey, validationResult.Message);
|
|
||||||
// addedError = true;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return addedError;
|
var addedError = false;
|
||||||
//}
|
foreach (var validationResult in validator.Validate(validationContext))
|
||||||
|
{
|
||||||
|
bindingContext.ModelState.AddModelError(modelStateKey, validationResult.Message);
|
||||||
|
addedError = true;
|
||||||
|
}
|
||||||
|
return addedError;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
|
using System.Linq;
|
||||||
|
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
|
|
@ -17,17 +18,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
object model = valueProviderResult.RawValue;
|
object model = valueProviderResult.RawValue;
|
||||||
ModelBindingHelper.ReplaceEmptyStringWithNull(bindingContext.ModelMetadata, ref model);
|
ModelBindingHelper.ReplaceEmptyStringWithNull(bindingContext.ModelMetadata, ref model);
|
||||||
bindingContext.Model = model;
|
bindingContext.Model = model;
|
||||||
if (bindingContext.ModelMetadata.IsComplexType)
|
|
||||||
{
|
// TODO: Determine if we need IBodyValidator here.
|
||||||
// TODO: Validation
|
|
||||||
//IBodyModelValidator validator = services.GetBodyModelValidator();
|
|
||||||
//ModelMetadataProvider metadataProvider = services.GetModelMetadataProvider();
|
|
||||||
//if (validator != null && metadataProvider != null)
|
|
||||||
//{
|
|
||||||
// validator.Validate(model, bindingContext.ModelType, metadataProvider, context, bindingContext.ModelName);
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
namespace Microsoft.AspNet.Mvc.ModelBinding.Internal
|
||||||
{
|
{
|
||||||
internal class EfficientTypePropertyKey<T1, T2> : Tuple<T1, T2>
|
internal class EfficientTypePropertyKey<T1, T2> : Tuple<T1, T2>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,28 +14,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
private readonly ConcurrentDictionary<Type, TypeInformation> _typeInfoCache = new ConcurrentDictionary<Type, TypeInformation>();
|
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);
|
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)
|
if (string.IsNullOrEmpty(propertyName))
|
||||||
{
|
|
||||||
throw Error.ArgumentNull("containerType");
|
|
||||||
}
|
|
||||||
if (String.IsNullOrEmpty(propertyName))
|
|
||||||
{
|
{
|
||||||
throw Error.ArgumentNullOrEmpty("propertyName");
|
throw Error.ArgumentNullOrEmpty("propertyName");
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeInformation typeInfo = GetTypeInformation(containerType);
|
var typeInfo = GetTypeInformation(containerType);
|
||||||
PropertyInformation propertyInfo;
|
PropertyInformation propertyInfo;
|
||||||
if (!typeInfo.Properties.TryGetValue(propertyName, out propertyInfo))
|
if (!typeInfo.Properties.TryGetValue(propertyName, out propertyInfo))
|
||||||
{
|
{
|
||||||
|
|
@ -45,13 +36,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
return CreateMetadataFromPrototype(propertyInfo.Prototype, modelAccessor);
|
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;
|
TModelMetadata prototype = GetTypeInformation(modelType).Prototype;
|
||||||
return CreateMetadataFromPrototype(prototype, modelAccessor);
|
return CreateMetadataFromPrototype(prototype, modelAccessor);
|
||||||
}
|
}
|
||||||
|
|
@ -68,10 +54,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
private IEnumerable<ModelMetadata> GetMetadataForPropertiesCore(object container, Type containerType)
|
private IEnumerable<ModelMetadata> GetMetadataForPropertiesCore(object container, Type containerType)
|
||||||
{
|
{
|
||||||
TypeInformation typeInfo = GetTypeInformation(containerType);
|
var typeInfo = GetTypeInformation(containerType);
|
||||||
foreach (KeyValuePair<string, PropertyInformation> kvp in typeInfo.Properties)
|
foreach (var kvp in typeInfo.Properties)
|
||||||
{
|
{
|
||||||
PropertyInformation propertyInfo = kvp.Value;
|
var propertyInfo = kvp.Value;
|
||||||
Func<object> modelAccessor = null;
|
Func<object> modelAccessor = null;
|
||||||
if (container != null)
|
if (container != null)
|
||||||
{
|
{
|
||||||
|
|
@ -96,24 +82,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
private TypeInformation CreateTypeInformation(Type type, IEnumerable<Attribute> associatedAttributes)
|
private TypeInformation CreateTypeInformation(Type type, IEnumerable<Attribute> associatedAttributes)
|
||||||
{
|
{
|
||||||
TypeInfo typeInfo = type.GetTypeInfo();
|
var typeInfo = type.GetTypeInfo();
|
||||||
IEnumerable<Attribute> attributes = typeInfo.GetCustomAttributes();
|
var attributes = typeInfo.GetCustomAttributes();
|
||||||
if (associatedAttributes != null)
|
if (associatedAttributes != null)
|
||||||
{
|
{
|
||||||
attributes = attributes.Concat(associatedAttributes);
|
attributes = attributes.Concat(associatedAttributes);
|
||||||
}
|
}
|
||||||
TypeInformation info = new TypeInformation
|
var info = new TypeInformation
|
||||||
{
|
{
|
||||||
Prototype = CreateMetadataPrototype(attributes, containerType: null, modelType: type, propertyName: null)
|
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>();
|
var properties = new Dictionary<string, PropertyInformation>();
|
||||||
|
foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
||||||
// 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))
|
|
||||||
{
|
{
|
||||||
// Avoid re-generating a property descriptor if one has already been generated for the property name
|
// Avoid re-generating a property descriptor if one has already been generated for the property name
|
||||||
if (!properties.ContainsKey(property.Name))
|
if (!properties.ContainsKey(property.Name))
|
||||||
|
|
@ -128,21 +109,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
private PropertyInformation CreatePropertyInformation(Type containerType, PropertyInfo property)
|
private PropertyInformation CreatePropertyInformation(Type containerType, PropertyInfo property)
|
||||||
{
|
{
|
||||||
PropertyInformation info = new PropertyInformation();
|
var info = new PropertyInformation();
|
||||||
info.ValueAccessor = CreatePropertyValueAccessor(property);
|
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;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Func<object, object> CreatePropertyValueAccessor(PropertyInfo property)
|
private static Func<object, object> CreatePropertyValueAccessor(PropertyInfo property)
|
||||||
{
|
{
|
||||||
Type declaringType = property.DeclaringType;
|
var declaringType = property.DeclaringType;
|
||||||
TypeInfo declaringTypeInfo = declaringType.GetTypeInfo();
|
var declaringTypeInfo = declaringType.GetTypeInfo();
|
||||||
if (declaringTypeInfo.IsVisible)
|
if (declaringTypeInfo.IsVisible)
|
||||||
{
|
{
|
||||||
if (property.CanRead)
|
if (property.CanRead)
|
||||||
{
|
{
|
||||||
MethodInfo getMethodInfo = property.GetMethod;
|
var getMethodInfo = property.GetMethod;
|
||||||
if (getMethodInfo != null)
|
if (getMethodInfo != null)
|
||||||
{
|
{
|
||||||
return CreateDynamicValueAccessor(getMethodInfo, declaringType, property.Name);
|
return CreateDynamicValueAccessor(getMethodInfo, declaringType, property.Name);
|
||||||
|
|
@ -161,10 +145,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
Contract.Assert(getMethodInfo != null && getMethodInfo.IsPublic && !getMethodInfo.IsStatic);
|
Contract.Assert(getMethodInfo != null && getMethodInfo.IsPublic && !getMethodInfo.IsStatic);
|
||||||
|
|
||||||
TypeInfo declaringTypeInfo = declaringType.GetTypeInfo();
|
var declaringTypeInfo = declaringType.GetTypeInfo();
|
||||||
Type propertyType = getMethodInfo.ReturnType;
|
var propertyType = getMethodInfo.ReturnType;
|
||||||
DynamicMethod dynamicMethod = new DynamicMethod("Get" + propertyName + "From" + declaringType.Name, typeof(object), new Type[] { typeof(object) });
|
var dynamicMethod = new DynamicMethod("Get" + propertyName + "From" + declaringType.Name,
|
||||||
ILGenerator ilg = dynamicMethod.GetILGenerator();
|
typeof(object),
|
||||||
|
new [] { typeof(object) });
|
||||||
|
var ilg = dynamicMethod.GetILGenerator();
|
||||||
|
|
||||||
// Load the container onto the stack, convert from object => declaring type for the property
|
// Load the container onto the stack, convert from object => declaring type for the property
|
||||||
ilg.Emit(OpCodes.Ldarg_0);
|
ilg.Emit(OpCodes.Ldarg_0);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
using Microsoft.AspNet.Abstractions;
|
using Microsoft.AspNet.Abstractions;
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
|
|
@ -9,6 +10,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
private string _modelName;
|
private string _modelName;
|
||||||
private ModelStateDictionary _modelState;
|
private ModelStateDictionary _modelState;
|
||||||
|
private Dictionary<string, ModelMetadata> _propertyMetadata;
|
||||||
|
private ModelValidationNode _validationNode;
|
||||||
|
|
||||||
public ModelBindingContext()
|
public ModelBindingContext()
|
||||||
{
|
{
|
||||||
|
|
@ -24,6 +27,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
ValueProvider = bindingContext.ValueProvider;
|
ValueProvider = bindingContext.ValueProvider;
|
||||||
MetadataProvider = bindingContext.MetadataProvider;
|
MetadataProvider = bindingContext.MetadataProvider;
|
||||||
ModelBinder = bindingContext.ModelBinder;
|
ModelBinder = bindingContext.ModelBinder;
|
||||||
|
ValidatorProviders = bindingContext.ValidatorProviders;
|
||||||
HttpContext = bindingContext.HttpContext;
|
HttpContext = bindingContext.HttpContext;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -102,6 +106,38 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
set;
|
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()
|
private void EnsureModelMetadata()
|
||||||
{
|
{
|
||||||
if (ModelMetadata == null)
|
if (ModelMetadata == null)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
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)
|
foreach (var entry in dictionary)
|
||||||
{
|
{
|
||||||
|
|
@ -35,7 +36,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
GetModelStateForKey(key).Errors.Add(errorMessage);
|
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;
|
ModelState modelState;
|
||||||
if (!TryGetValue(key, out modelState))
|
if (!TryGetValue(key, out modelState))
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,38 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
return GetString("JQuerySyntaxMissingClosingBracket");
|
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>
|
/// <summary>
|
||||||
/// The value '{0}' is not valid for {1}.
|
/// The value '{0}' is not valid for {1}.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -170,6 +202,70 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
return GetString("ModelBindingContext_ModelMetadataMustBeSet");
|
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>
|
/// <summary>
|
||||||
/// The parameter conversion from type '{0}' to type '{1}' failed. See the inner exception for more information.
|
/// The parameter conversion from type '{0}' to type '{1}' failed. See the inner exception for more information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,12 @@
|
||||||
<data name="JQuerySyntaxMissingClosingBracket" xml:space="preserve">
|
<data name="JQuerySyntaxMissingClosingBracket" xml:space="preserve">
|
||||||
<value>The key is invalid JQuery syntax because it is missing a closing bracket.</value>
|
<value>The key is invalid JQuery syntax because it is missing a closing bracket.</value>
|
||||||
</data>
|
</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">
|
<data name="ModelBinderConfig_ValueInvalid" xml:space="preserve">
|
||||||
<value>The value '{0}' is not valid for {1}.</value>
|
<value>The value '{0}' is not valid for {1}.</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
@ -147,6 +153,18 @@
|
||||||
<data name="ModelBindingContext_ModelMetadataMustBeSet" xml:space="preserve">
|
<data name="ModelBindingContext_ModelMetadataMustBeSet" xml:space="preserve">
|
||||||
<value>The ModelMetadata property must be set before accessing this property.</value>
|
<value>The ModelMetadata property must be set before accessing this property.</value>
|
||||||
</data>
|
</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">
|
<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>
|
<value>The parameter conversion from type '{0}' to type '{1}' failed. See the inner exception for more information.</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
{
|
||||||
|
public interface IModelValidator
|
||||||
|
{
|
||||||
|
bool IsRequired { get; }
|
||||||
|
|
||||||
|
IEnumerable<ModelValidationResult> Validate(ModelValidationContext context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
{
|
||||||
|
public interface IModelValidatorProvider
|
||||||
|
{
|
||||||
|
IEnumerable<IModelValidator> GetValidators(ModelMetadata metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -2,12 +2,17 @@
|
||||||
"version": "0.1-alpha-*",
|
"version": "0.1-alpha-*",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Common": "",
|
"Common": "",
|
||||||
"Microsoft.AspNet.DependencyInjection" : "0.1-alpha-*",
|
"Microsoft.AspNet.DependencyInjection": "0.1-alpha-*",
|
||||||
"Microsoft.AspNet.Abstractions": "0.1-alpha-*",
|
"Microsoft.AspNet.Abstractions": "0.1-alpha-*",
|
||||||
"Newtonsoft.Json": "5.0.8"
|
"Newtonsoft.Json": "5.0.8"
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"net45": {},
|
"net45": {
|
||||||
|
"dependencies": {
|
||||||
|
"System.ComponentModel.DataAnnotations": "",
|
||||||
|
"System.Runtime.Serialization": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
"k10": {
|
"k10": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"System.Collections": "4.0.0.0",
|
"System.Collections": "4.0.0.0",
|
||||||
|
|
@ -22,12 +27,15 @@
|
||||||
"System.Reflection": "4.0.10.0",
|
"System.Reflection": "4.0.10.0",
|
||||||
"System.Reflection.Emit.ILGeneration": "4.0.0.0",
|
"System.Reflection.Emit.ILGeneration": "4.0.0.0",
|
||||||
"System.Reflection.Emit.Lightweight": "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.Extensions": "4.0.0.0",
|
||||||
"System.Reflection.Primitives": "4.0.0.0",
|
"System.Reflection.Primitives": "4.0.0.0",
|
||||||
"System.Resources.ResourceManager": "4.0.0.0",
|
"System.Resources.ResourceManager": "4.0.0.0",
|
||||||
"System.Runtime": "4.0.20.0",
|
"System.Runtime": "4.0.20.0",
|
||||||
"System.Runtime.Extensions": "4.0.10.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-*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,10 +61,13 @@ namespace Microsoft.AspNet.Mvc
|
||||||
yield return describe.Transient<IModelBinder, MutableObjectModelBinder>();
|
yield return describe.Transient<IModelBinder, MutableObjectModelBinder>();
|
||||||
yield return describe.Transient<IModelBinder, ComplexModelDtoModelBinder>();
|
yield return describe.Transient<IModelBinder, ComplexModelDtoModelBinder>();
|
||||||
|
|
||||||
|
yield return describe.Transient<IInputFormatter, JsonInputFormatter>();
|
||||||
|
|
||||||
yield return describe.Transient<INestedProviderManager<FilterProviderContext>, NestedProviderManager<FilterProviderContext>>();
|
yield return describe.Transient<INestedProviderManager<FilterProviderContext>, NestedProviderManager<FilterProviderContext>>();
|
||||||
yield return describe.Transient<INestedProvider<FilterProviderContext>, DefaultFilterProvider>();
|
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>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -20,12 +21,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var binder = new CollectionModelBinder<int>();
|
var binder = new CollectionModelBinder<int>();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
List<int> boundCollection = binder.BindComplexCollectionFromIndexes(bindingContext, new[] { "foo", "bar", "baz" });
|
var boundCollection = binder.BindComplexCollectionFromIndexes(bindingContext, new[] { "foo", "bar", "baz" });
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(new[] { 42, 0, 200 }, boundCollection.ToArray());
|
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]
|
[Fact]
|
||||||
|
|
@ -42,12 +42,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var binder = new CollectionModelBinder<int>();
|
var binder = new CollectionModelBinder<int>();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
List<int> boundCollection = binder.BindComplexCollectionFromIndexes(bindingContext, indexNames: null);
|
var boundCollection = binder.BindComplexCollectionFromIndexes(bindingContext, indexNames: null);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(new[] { 42, 100 }, boundCollection.ToArray());
|
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]
|
[Fact]
|
||||||
|
|
@ -97,7 +96,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var binder = new CollectionModelBinder<int>();
|
var binder = new CollectionModelBinder<int>();
|
||||||
|
|
||||||
// Act
|
// 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
|
||||||
Assert.NotNull(boundCollection);
|
Assert.NotNull(boundCollection);
|
||||||
|
|
@ -111,7 +110,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var binder = new CollectionModelBinder<int>();
|
var binder = new CollectionModelBinder<int>();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
List<int> boundCollection = binder.BindSimpleCollection(bindingContext: null, rawValue: null, culture: null);
|
var boundCollection = binder.BindSimpleCollection(bindingContext: null, rawValue: null, culture: null);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Null(boundCollection);
|
Assert.Null(boundCollection);
|
||||||
|
|
@ -124,25 +123,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var culture = CultureInfo.GetCultureInfo("fr-FR");
|
var culture = CultureInfo.GetCultureInfo("fr-FR");
|
||||||
var bindingContext = GetModelBindingContext(new SimpleHttpValueProvider());
|
var bindingContext = GetModelBindingContext(new SimpleHttpValueProvider());
|
||||||
|
|
||||||
// TODO: Validation
|
ModelValidationNode childValidationNode = null;
|
||||||
// ModelValidationNode childValidationNode = null;
|
|
||||||
Mock.Get<IModelBinder>(bindingContext.ModelBinder)
|
Mock.Get<IModelBinder>(bindingContext.ModelBinder)
|
||||||
.Setup(o => o.BindModel(It.IsAny<ModelBindingContext>()))
|
.Setup(o => o.BindModel(It.IsAny<ModelBindingContext>()))
|
||||||
.Returns((ModelBindingContext mbc) =>
|
.Returns((ModelBindingContext mbc) =>
|
||||||
{
|
{
|
||||||
Assert.Equal("someName", mbc.ModelName);
|
Assert.Equal("someName", mbc.ModelName);
|
||||||
// childValidationNode = mbc.ValidationNode;
|
childValidationNode = mbc.ValidationNode;
|
||||||
mbc.Model = 42;
|
mbc.Model = 42;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
var modelBinder = new CollectionModelBinder<int>();
|
var modelBinder = new CollectionModelBinder<int>();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
List<int> boundCollection = modelBinder.BindSimpleCollection(bindingContext, new int[1], culture);
|
var boundCollection = modelBinder.BindSimpleCollection(bindingContext, new int[1], culture);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(new[] { 42 }, boundCollection.ToArray());
|
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)
|
private static ModelBindingContext GetModelBindingContext(IValueProvider valueProvider)
|
||||||
|
|
|
||||||
|
|
@ -4,36 +4,34 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
{
|
{
|
||||||
public class ComplexModelDtoResultTest
|
public class ComplexModelDtoResultTest
|
||||||
{
|
{
|
||||||
// TODO: Validation
|
[Fact]
|
||||||
////[Fact]
|
public void Constructor_ThrowsIfValidationNodeIsNull()
|
||||||
////public void Constructor_ThrowsIfValidationNodeIsNull()
|
{
|
||||||
////{
|
// Act & assert
|
||||||
//// // Act & assert
|
ExceptionAssert.ThrowsArgumentNull(
|
||||||
//// ExceptionAssert.ThrowsArgumentNull(
|
() => new ComplexModelDtoResult("some string", validationNode: null),
|
||||||
//// () => new ComplexModelDtoResult("some string"),
|
"validationNode");
|
||||||
//// "validationNode");
|
}
|
||||||
////}
|
|
||||||
|
|
||||||
// TODO: Validation
|
[Fact]
|
||||||
//[Fact]
|
public void Constructor_SetsProperties()
|
||||||
//public void Constructor_SetsProperties()
|
{
|
||||||
//{
|
// Arrange
|
||||||
// // Arrange
|
var validationNode = GetValidationNode();
|
||||||
// ModelValidationNode validationNode = GetValidationNode();
|
|
||||||
|
|
||||||
// // Act
|
// Act
|
||||||
// ComplexModelDtoResult result = new ComplexModelDtoResult("some string", validationNode);
|
var result = new ComplexModelDtoResult("some string", validationNode);
|
||||||
|
|
||||||
// // Assert
|
// Assert
|
||||||
// Assert.Equal("some string", result.Model);
|
Assert.Equal("some string", result.Model);
|
||||||
// Assert.Equal(validationNode, result.ValidationNode);
|
Assert.Equal(validationNode, result.ValidationNode);
|
||||||
//}
|
}
|
||||||
|
|
||||||
//private static ModelValidationNode GetValidationNode()
|
private static ModelValidationNode GetValidationNode()
|
||||||
//{
|
{
|
||||||
// EmptyModelMetadataProvider provider = new EmptyModelMetadataProvider();
|
var provider = new EmptyModelMetadataProvider();
|
||||||
// ModelMetadata metadata = provider.GetMetadataForType(null, typeof(object));
|
var metadata = provider.GetMetadataForType(null, typeof(object));
|
||||||
// return new ModelValidationNode(metadata, "someKey");
|
return new ModelValidationNode(metadata, "someKey");
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -12,22 +13,22 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
public void BindModel_SuccessfulBind_RunsValidationAndReturnsModel()
|
public void BindModel_SuccessfulBind_RunsValidationAndReturnsModel()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
bool validationCalled = false;
|
var validationCalled = false;
|
||||||
|
|
||||||
ModelBindingContext bindingContext = new ModelBindingContext
|
var bindingContext = new ModelBindingContext
|
||||||
{
|
{
|
||||||
FallbackToEmptyPrefix = true,
|
FallbackToEmptyPrefix = true,
|
||||||
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
|
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
|
||||||
ModelName = "someName",
|
ModelName = "someName",
|
||||||
//ModelState = executionContext.Controller.ViewData.ModelState,
|
ModelState = new ModelStateDictionary(),
|
||||||
//PropertyFilter = _ => true,
|
|
||||||
ValueProvider = new SimpleValueProvider
|
ValueProvider = new SimpleValueProvider
|
||||||
{
|
{
|
||||||
{ "someName", "dummyValue" }
|
{ "someName", "dummyValue" }
|
||||||
}
|
},
|
||||||
|
ValidatorProviders = Enumerable.Empty<IModelValidatorProvider>()
|
||||||
};
|
};
|
||||||
|
|
||||||
Mock<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
|
var mockIntBinder = new Mock<IModelBinder>();
|
||||||
mockIntBinder
|
mockIntBinder
|
||||||
.Setup(o => o.BindModel(It.IsAny<ModelBindingContext>()))
|
.Setup(o => o.BindModel(It.IsAny<ModelBindingContext>()))
|
||||||
.Returns(
|
.Returns(
|
||||||
|
|
@ -38,22 +39,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
Assert.Same(bindingContext.ValueProvider, context.ValueProvider);
|
Assert.Same(bindingContext.ValueProvider, context.ValueProvider);
|
||||||
|
|
||||||
context.Model = 42;
|
context.Model = 42;
|
||||||
// TODO: Validation
|
bindingContext.ValidationNode.Validating += delegate { validationCalled = true; };
|
||||||
// mbc.ValidationNode.Validating += delegate { validationCalled = true; };
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
//binderProviders.RegisterBinderForType(typeof(int), mockIntBinder.Object, false /* suppressPrefixCheck */);
|
var shimBinder = new CompositeModelBinder(mockIntBinder.Object);
|
||||||
IModelBinder shimBinder = new CompositeModelBinder(mockIntBinder.Object);
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
bool isBound = shimBinder.BindModel(bindingContext);
|
var isBound = shimBinder.BindModel(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(isBound);
|
Assert.True(isBound);
|
||||||
Assert.Equal(42, bindingContext.Model);
|
Assert.Equal(42, bindingContext.Model);
|
||||||
// TODO: Validation
|
|
||||||
// Assert.True(validationCalled);
|
Assert.True(validationCalled);
|
||||||
Assert.True(bindingContext.ModelState.IsValid);
|
Assert.True(bindingContext.ModelState.IsValid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,23 +60,23 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
public void BindModel_SuccessfulBind_ComplexTypeFallback_RunsValidationAndReturnsModel()
|
public void BindModel_SuccessfulBind_ComplexTypeFallback_RunsValidationAndReturnsModel()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
bool validationCalled = false;
|
var validationCalled = false;
|
||||||
List<int> expectedModel = new List<int> { 1, 2, 3, 4, 5 };
|
var expectedModel = new List<int> { 1, 2, 3, 4, 5 };
|
||||||
|
|
||||||
ModelBindingContext bindingContext = new ModelBindingContext
|
var bindingContext = new ModelBindingContext
|
||||||
{
|
{
|
||||||
FallbackToEmptyPrefix = true,
|
FallbackToEmptyPrefix = true,
|
||||||
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(List<int>)),
|
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(List<int>)),
|
||||||
ModelName = "someName",
|
ModelName = "someName",
|
||||||
//ModelState = executionContext.Controller.ViewData.ModelState,
|
ModelState = new ModelStateDictionary(),
|
||||||
//PropertyFilter = _ => true,
|
|
||||||
ValueProvider = new SimpleValueProvider
|
ValueProvider = new SimpleValueProvider
|
||||||
{
|
{
|
||||||
{ "someOtherName", "dummyValue" }
|
{ "someOtherName", "dummyValue" }
|
||||||
}
|
},
|
||||||
|
ValidatorProviders = Enumerable.Empty<IModelValidatorProvider>()
|
||||||
};
|
};
|
||||||
|
|
||||||
Mock<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
|
var mockIntBinder = new Mock<IModelBinder>();
|
||||||
mockIntBinder
|
mockIntBinder
|
||||||
.Setup(o => o.BindModel(It.IsAny<ModelBindingContext>()))
|
.Setup(o => o.BindModel(It.IsAny<ModelBindingContext>()))
|
||||||
.Returns(
|
.Returns(
|
||||||
|
|
@ -93,12 +92,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
Assert.Same(bindingContext.ValueProvider, mbc.ValueProvider);
|
Assert.Same(bindingContext.ValueProvider, mbc.ValueProvider);
|
||||||
|
|
||||||
mbc.Model = expectedModel;
|
mbc.Model = expectedModel;
|
||||||
// TODO: Validation
|
mbc.ValidationNode.Validating += delegate { validationCalled = true; };
|
||||||
// mbc.ValidationNode.Validating += delegate { validationCalled = true; };
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
//binderProviders.RegisterBinderForType(typeof(List<int>), mockIntBinder.Object, false /* suppressPrefixCheck */);
|
|
||||||
IModelBinder shimBinder = new CompositeModelBinder(mockIntBinder.Object);
|
IModelBinder shimBinder = new CompositeModelBinder(mockIntBinder.Object);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -107,36 +104,34 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(isBound);
|
Assert.True(isBound);
|
||||||
Assert.Equal(expectedModel, bindingContext.Model);
|
Assert.Equal(expectedModel, bindingContext.Model);
|
||||||
// TODO: Validation
|
Assert.True(validationCalled);
|
||||||
// Assert.True(validationCalled);
|
Assert.True(bindingContext.ModelState.IsValid);
|
||||||
// Assert.True(bindingContext.ModelState.IsValid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void BindModel_UnsuccessfulBind_BinderFails_ReturnsNull()
|
public void BindModel_UnsuccessfulBind_BinderFails_ReturnsNull()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
Mock<IModelBinder> mockListBinder = new Mock<IModelBinder>();
|
var mockListBinder = new Mock<IModelBinder>();
|
||||||
mockListBinder.Setup(o => o.BindModel(It.IsAny<ModelBindingContext>()))
|
mockListBinder.Setup(o => o.BindModel(It.IsAny<ModelBindingContext>()))
|
||||||
.Returns(false)
|
.Returns(false)
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
|
|
||||||
IModelBinder shimBinder = (IModelBinder)mockListBinder.Object;
|
var shimBinder = (IModelBinder)mockListBinder.Object;
|
||||||
|
|
||||||
ModelBindingContext bindingContext = new ModelBindingContext
|
var bindingContext = new ModelBindingContext
|
||||||
{
|
{
|
||||||
FallbackToEmptyPrefix = false,
|
FallbackToEmptyPrefix = false,
|
||||||
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(List<int>)),
|
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(List<int>)),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
bool isBound = shimBinder.BindModel(bindingContext);
|
var isBound = shimBinder.BindModel(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(isBound);
|
Assert.False(isBound);
|
||||||
Assert.Null(bindingContext.Model);
|
Assert.Null(bindingContext.Model);
|
||||||
// TODO: Validation
|
Assert.True(bindingContext.ModelState.IsValid);
|
||||||
// Assert.True(bindingContext.ModelState.IsValid);
|
|
||||||
mockListBinder.Verify();
|
mockListBinder.Verify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -145,17 +140,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var innerBinder = Mock.Of<IModelBinder>();
|
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,
|
FallbackToEmptyPrefix = true,
|
||||||
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
|
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
|
||||||
//ModelState = executionContext.Controller.ViewData.ModelState
|
ModelState = new ModelStateDictionary()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
bool isBound = shimBinder.BindModel(bindingContext);
|
var isBound = shimBinder.BindModel(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(isBound);
|
Assert.False(isBound);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -11,7 +12,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var valueProvider = new SimpleHttpValueProvider();
|
var valueProvider = new SimpleHttpValueProvider();
|
||||||
ModelBindingContext bindingContext = GetBindingContext(valueProvider, Mock.Of<IModelBinder>());
|
var bindingContext = GetBindingContext(valueProvider, Mock.Of<IModelBinder>());
|
||||||
var binder = new KeyValuePairModelBinder<int, string>();
|
var binder = new KeyValuePairModelBinder<int, string>();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -20,8 +21,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(retVal);
|
Assert.False(retVal);
|
||||||
Assert.Null(bindingContext.Model);
|
Assert.Null(bindingContext.Model);
|
||||||
// TODO: Validation
|
Assert.Empty(bindingContext.ValidationNode.ChildNodes);
|
||||||
// Assert.Empty(bindingContext.ValidationNode.ChildNodes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -29,7 +29,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var valueProvider = new SimpleHttpValueProvider();
|
var valueProvider = new SimpleHttpValueProvider();
|
||||||
ModelBindingContext bindingContext = GetBindingContext(valueProvider);
|
var bindingContext = GetBindingContext(valueProvider);
|
||||||
var binder = new KeyValuePairModelBinder<int, string>();
|
var binder = new KeyValuePairModelBinder<int, string>();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -38,28 +38,26 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(retVal);
|
Assert.True(retVal);
|
||||||
Assert.Null(bindingContext.Model);
|
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]
|
[Fact]
|
||||||
public void BindModel_SubBindingSucceeds()
|
public void BindModel_SubBindingSucceeds()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
IModelBinder innerBinder = new CompositeModelBinder(CreateStringBinder(), CreateIntBinder());
|
var innerBinder = new CompositeModelBinder(CreateStringBinder(), CreateIntBinder());
|
||||||
var valueProvider = new SimpleHttpValueProvider();
|
var valueProvider = new SimpleHttpValueProvider();
|
||||||
ModelBindingContext bindingContext = GetBindingContext(valueProvider, innerBinder);
|
var bindingContext = GetBindingContext(valueProvider, innerBinder);
|
||||||
|
|
||||||
var binder = new KeyValuePairModelBinder<int, string>();
|
var binder = new KeyValuePairModelBinder<int, string>();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
bool retVal = binder.BindModel(bindingContext);
|
var retVal = binder.BindModel(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(retVal);
|
Assert.True(retVal);
|
||||||
Assert.Equal(new KeyValuePair<int, string>(42, "some-value"), bindingContext.Model);
|
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]
|
[Fact]
|
||||||
|
|
@ -71,13 +69,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
int model;
|
int model;
|
||||||
bool retVal = binder.TryBindStrongModel(bindingContext, "key", out model);
|
var retVal = binder.TryBindStrongModel(bindingContext, "key", out model);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(retVal);
|
Assert.True(retVal);
|
||||||
Assert.Equal(42, model);
|
Assert.Equal(42, model);
|
||||||
// TODO: Validation
|
Assert.Single(bindingContext.ValidationNode.ChildNodes);
|
||||||
// Assert.Single(bindingContext.ValidationNode.ChildNodes);
|
|
||||||
Assert.Empty(bindingContext.ModelState);
|
Assert.Empty(bindingContext.ModelState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,33 +97,33 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
int model;
|
int model;
|
||||||
bool retVal = binder.TryBindStrongModel(bindingContext, "key", out model);
|
var retVal = binder.TryBindStrongModel(bindingContext, "key", out model);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(retVal);
|
Assert.True(retVal);
|
||||||
Assert.Equal(default(int), model);
|
Assert.Equal(default(int), model);
|
||||||
// TODO: Validation
|
Assert.Single(bindingContext.ValidationNode.ChildNodes);
|
||||||
// Assert.Single(bindingContext.ValidationNode.ChildNodes);
|
|
||||||
Assert.Empty(bindingContext.ModelState);
|
Assert.Empty(bindingContext.ModelState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ModelBindingContext GetBindingContext(IValueProvider valueProvider, IModelBinder innerBinder = null)
|
private static ModelBindingContext GetBindingContext(IValueProvider valueProvider, IModelBinder innerBinder = null)
|
||||||
{
|
{
|
||||||
var metataProvider = new EmptyModelMetadataProvider();
|
var metataProvider = new EmptyModelMetadataProvider();
|
||||||
ModelBindingContext bindingContext = new ModelBindingContext
|
var bindingContext = new ModelBindingContext
|
||||||
{
|
{
|
||||||
ModelMetadata = metataProvider.GetMetadataForType(null, typeof(KeyValuePair<int, string>)),
|
ModelMetadata = metataProvider.GetMetadataForType(null, typeof(KeyValuePair<int, string>)),
|
||||||
ModelName = "someName",
|
ModelName = "someName",
|
||||||
ValueProvider = valueProvider,
|
ValueProvider = valueProvider,
|
||||||
ModelBinder = innerBinder ?? CreateIntBinder(),
|
ModelBinder = innerBinder ?? CreateIntBinder(),
|
||||||
MetadataProvider = metataProvider
|
MetadataProvider = metataProvider,
|
||||||
|
ValidatorProviders = Enumerable.Empty<IModelValidatorProvider>()
|
||||||
};
|
};
|
||||||
return bindingContext;
|
return bindingContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IModelBinder CreateIntBinder()
|
private static IModelBinder CreateIntBinder()
|
||||||
{
|
{
|
||||||
Mock<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
|
var mockIntBinder = new Mock<IModelBinder>();
|
||||||
mockIntBinder
|
mockIntBinder
|
||||||
.Setup(o => o.BindModel(It.IsAny<ModelBindingContext>()))
|
.Setup(o => o.BindModel(It.IsAny<ModelBindingContext>()))
|
||||||
.Returns((ModelBindingContext mbc) =>
|
.Returns((ModelBindingContext mbc) =>
|
||||||
|
|
@ -143,8 +140,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
|
|
||||||
private static IModelBinder CreateStringBinder()
|
private static IModelBinder CreateStringBinder()
|
||||||
{
|
{
|
||||||
Mock<IModelBinder> mockIntBinder = new Mock<IModelBinder>();
|
var mockStringBinder = new Mock<IModelBinder>();
|
||||||
mockIntBinder
|
mockStringBinder
|
||||||
.Setup(o => o.BindModel(It.IsAny<ModelBindingContext>()))
|
.Setup(o => o.BindModel(It.IsAny<ModelBindingContext>()))
|
||||||
.Returns((ModelBindingContext mbc) =>
|
.Returns((ModelBindingContext mbc) =>
|
||||||
{
|
{
|
||||||
|
|
@ -155,7 +152,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
return mockIntBinder.Object;
|
return mockStringBinder.Object;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
public void CopyConstructor()
|
public void CopyConstructor()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
ModelBindingContext originalBindingContext = new ModelBindingContext
|
var originalBindingContext = new ModelBindingContext
|
||||||
{
|
{
|
||||||
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(object)),
|
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(object)),
|
||||||
ModelName = "theName",
|
ModelName = "theName",
|
||||||
|
|
@ -18,7 +18,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
ModelBindingContext newBindingContext = new ModelBindingContext(originalBindingContext);
|
var newBindingContext = new ModelBindingContext(originalBindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Null(newBindingContext.ModelMetadata);
|
Assert.Null(newBindingContext.ModelMetadata);
|
||||||
|
|
@ -31,7 +31,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
public void ModelProperty_ThrowsIfModelMetadataDoesNotExist()
|
public void ModelProperty_ThrowsIfModelMetadataDoesNotExist()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
ModelBindingContext bindingContext = new ModelBindingContext();
|
var bindingContext = new ModelBindingContext();
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
ExceptionAssert.Throws<InvalidOperationException>(
|
ExceptionAssert.Throws<InvalidOperationException>(
|
||||||
|
|
@ -43,7 +43,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
public void ModelAndModelTypeAreFedFromModelMetadata()
|
public void ModelAndModelTypeAreFedFromModelMetadata()
|
||||||
{
|
{
|
||||||
// Act
|
// Act
|
||||||
ModelBindingContext bindingContext = new ModelBindingContext
|
var bindingContext = new ModelBindingContext
|
||||||
{
|
{
|
||||||
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => 42, typeof(int))
|
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => 42, typeof(int))
|
||||||
};
|
};
|
||||||
|
|
@ -53,38 +53,23 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
Assert.Equal(typeof(int), bindingContext.ModelType);
|
Assert.Equal(typeof(int), bindingContext.ModelType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Validation
|
[Fact]
|
||||||
//[Fact]
|
public void ValidationNodeProperty_DefaultValues()
|
||||||
//public void ValidationNodeProperty()
|
{
|
||||||
//{
|
// Act
|
||||||
// // Act
|
var bindingContext = new ModelBindingContext
|
||||||
// ModelBindingContext bindingContext = new ModelBindingContext
|
{
|
||||||
// {
|
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => 42, typeof(int)),
|
||||||
// ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => 42, typeof(int))
|
ModelName = "theInt"
|
||||||
// };
|
};
|
||||||
|
|
||||||
// // Act & assert
|
// Act
|
||||||
// MemberHelper.TestPropertyWithDefaultInstance(bindingContext, "ValidationNode", new ModelValidationNode(bindingContext.ModelMetadata, "someName"));
|
var validationNode = bindingContext.ValidationNode;
|
||||||
//}
|
|
||||||
|
|
||||||
// TODO: Validation
|
// Assert
|
||||||
//[Fact]
|
Assert.NotNull(validationNode);
|
||||||
//public void ValidationNodeProperty_DefaultValues()
|
Assert.Equal(bindingContext.ModelMetadata, validationNode.ModelMetadata);
|
||||||
//{
|
Assert.Equal(bindingContext.ModelName, validationNode.ModelStateKey);
|
||||||
// // 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);
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,17 +10,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test.Binders
|
||||||
{
|
{
|
||||||
// GetMetadataForProperties
|
// GetMetadataForProperties
|
||||||
|
|
||||||
[Fact]
|
//[Fact]
|
||||||
public void GetMetadataForPropertiesNullContainerTypeThrows()
|
//public void GetMetadataForPropertiesNullContainerTypeThrows()
|
||||||
{
|
//{
|
||||||
// Arrange
|
// // Arrange
|
||||||
TestableAssociatedMetadataProvider provider = new TestableAssociatedMetadataProvider();
|
// TestableAssociatedMetadataProvider provider = new TestableAssociatedMetadataProvider();
|
||||||
|
|
||||||
// Act & Assert
|
// // Act & Assert
|
||||||
ExceptionAssert.ThrowsArgumentNull(
|
// ExceptionAssert.ThrowsArgumentNull(
|
||||||
() => provider.GetMetadataForProperties(new Object(), containerType: null),
|
// () => provider.GetMetadataForProperties(new Object(), containerType: null),
|
||||||
"containerType");
|
// "containerType");
|
||||||
}
|
//}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetMetadataForPropertiesCreatesMetadataForAllPropertiesOnModelWithPropertyValues()
|
public void GetMetadataForPropertiesCreatesMetadataForAllPropertiesOnModelWithPropertyValues()
|
||||||
|
|
@ -72,17 +72,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test.Binders
|
||||||
|
|
||||||
// GetMetadataForProperty
|
// GetMetadataForProperty
|
||||||
|
|
||||||
[Fact]
|
//[Fact]
|
||||||
public void GetMetadataForPropertyNullContainerTypeThrows()
|
//public void GetMetadataForPropertyNullContainerTypeThrows()
|
||||||
{
|
//{
|
||||||
// Arrange
|
// // Arrange
|
||||||
TestableAssociatedMetadataProvider provider = new TestableAssociatedMetadataProvider();
|
// TestableAssociatedMetadataProvider provider = new TestableAssociatedMetadataProvider();
|
||||||
|
|
||||||
// Act & Assert
|
// // Act & Assert
|
||||||
ExceptionAssert.ThrowsArgumentNull(
|
// ExceptionAssert.ThrowsArgumentNull(
|
||||||
() => provider.GetMetadataForProperty(modelAccessor: null, containerType: null, propertyName: "propertyName"),
|
// () => provider.GetMetadataForProperty(modelAccessor: null, containerType: null, propertyName: "propertyName"),
|
||||||
"containerType");
|
// "containerType");
|
||||||
}
|
//}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetMetadataForPropertyNullOrEmptyPropertyNameThrows()
|
public void GetMetadataForPropertyNullOrEmptyPropertyNameThrows()
|
||||||
|
|
@ -167,17 +167,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test.Binders
|
||||||
|
|
||||||
// GetMetadataForType
|
// GetMetadataForType
|
||||||
|
|
||||||
[Fact]
|
//[Fact]
|
||||||
public void GetMetadataForTypeNullModelTypeThrows()
|
//public void GetMetadataForTypeNullModelTypeThrows()
|
||||||
{
|
//{
|
||||||
// Arrange
|
// // Arrange
|
||||||
TestableAssociatedMetadataProvider provider = new TestableAssociatedMetadataProvider();
|
// TestableAssociatedMetadataProvider provider = new TestableAssociatedMetadataProvider();
|
||||||
|
|
||||||
// Act & Assert
|
// // Act & Assert
|
||||||
ExceptionAssert.ThrowsArgumentNull(
|
// ExceptionAssert.ThrowsArgumentNull(
|
||||||
() => provider.GetMetadataForType(() => new Object(), modelType: null),
|
// () => provider.GetMetadataForType(() => new Object(), modelType: null),
|
||||||
"modelType");
|
// "modelType");
|
||||||
}
|
//}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetMetadataForTypeIncludesAttributesOnType()
|
public void GetMetadataForTypeIncludesAttributesOnType()
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,16 @@
|
||||||
"Xunit.extensions": "1.9.1"
|
"Xunit.extensions": "1.9.1"
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"net45": { }
|
"net45": {
|
||||||
|
dependencies: {
|
||||||
|
"System.ComponentModel.DataAnnotations": "",
|
||||||
|
"System.Runtime.Serialization": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"k10" : {
|
||||||
|
dependencies: {
|
||||||
|
"Microsoft.ComponentModel.DataAnnotations" : "0.1-alpha-*"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,9 +3,9 @@ using System.Globalization;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Xunit;
|
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 _defaultCultureName = "en-GB";
|
||||||
private const string _defaultUICultureName = "en-US";
|
private const string _defaultUICultureName = "en-US";
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
public static class ExceptionAssert
|
internal static class ExceptionAssert
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Verifies that an exception of the given type (or optionally a derived type) is thrown.
|
/// Verifies that an exception of the given type (or optionally a derived type) is thrown.
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue