Merging Model Validation for body and non body validation.
This also fixes #1503. Currently all model binders except mutable object binder are independent of validation code. The mutable object binder which needs to do some validation ( for scenarios involving [BindRequired] and [BindNever]). We would be going with an approach where required validaiton happens in input formatters and model binders. This is needed as validation for value types can best be done at creation time. Followup PRs: Introduce support for skipping validation (and not binding) for a particular property/type etc.
This commit is contained in:
parent
cf36a601c6
commit
f19c2e493d
12
Mvc.sln
12
Mvc.sln
|
|
@ -828,6 +828,18 @@ Global
|
||||||
{C651F432-4EBE-41A6-BAD2-3E07CCBA209C}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
{C651F432-4EBE-41A6-BAD2-3E07CCBA209C}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
{C651F432-4EBE-41A6-BAD2-3E07CCBA209C}.Release|x86.ActiveCfg = Release|Any CPU
|
{C651F432-4EBE-41A6-BAD2-3E07CCBA209C}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{C651F432-4EBE-41A6-BAD2-3E07CCBA209C}.Release|x86.Build.0 = Release|Any CPU
|
{C651F432-4EBE-41A6-BAD2-3E07CCBA209C}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{22019146-BDFA-442E-8C8E-345FB9644578}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{22019146-BDFA-442E-8C8E-345FB9644578}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{22019146-BDFA-442E-8C8E-345FB9644578}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
|
{22019146-BDFA-442E-8C8E-345FB9644578}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||||
|
{22019146-BDFA-442E-8C8E-345FB9644578}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{22019146-BDFA-442E-8C8E-345FB9644578}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{22019146-BDFA-442E-8C8E-345FB9644578}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{22019146-BDFA-442E-8C8E-345FB9644578}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{22019146-BDFA-442E-8C8E-345FB9644578}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{22019146-BDFA-442E-8C8E-345FB9644578}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{22019146-BDFA-442E-8C8E-345FB9644578}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{22019146-BDFA-442E-8C8E-345FB9644578}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{A19022EF-9BA3-4349-94E4-F48E13E1C8AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{A19022EF-9BA3-4349-94E4-F48E13E1C8AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{A19022EF-9BA3-4349-94E4-F48E13E1C8AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{A19022EF-9BA3-4349-94E4-F48E13E1C8AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{A19022EF-9BA3-4349-94E4-F48E13E1C8AE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
{A19022EF-9BA3-4349-94E4-F48E13E1C8AE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,9 @@ namespace Microsoft.AspNet.Mvc
|
||||||
[FromServices]
|
[FromServices]
|
||||||
public IUrlHelper Url { get; set; }
|
public IUrlHelper Url { get; set; }
|
||||||
|
|
||||||
|
[FromServices]
|
||||||
|
public IObjectModelValidator ObjectValidator { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the <see cref="ClaimsPrincipal"/> for user associated with the executing action.
|
/// Gets or sets the <see cref="ClaimsPrincipal"/> for user associated with the executing action.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -938,6 +941,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
MetadataProvider,
|
MetadataProvider,
|
||||||
BindingContext.ModelBinder,
|
BindingContext.ModelBinder,
|
||||||
valueProvider,
|
valueProvider,
|
||||||
|
ObjectValidator,
|
||||||
BindingContext.ValidatorProvider);
|
BindingContext.ValidatorProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -975,6 +979,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
MetadataProvider,
|
MetadataProvider,
|
||||||
BindingContext.ModelBinder,
|
BindingContext.ModelBinder,
|
||||||
BindingContext.ValueProvider,
|
BindingContext.ValueProvider,
|
||||||
|
ObjectValidator,
|
||||||
BindingContext.ValidatorProvider,
|
BindingContext.ValidatorProvider,
|
||||||
includeExpressions);
|
includeExpressions);
|
||||||
}
|
}
|
||||||
|
|
@ -1012,6 +1017,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
MetadataProvider,
|
MetadataProvider,
|
||||||
BindingContext.ModelBinder,
|
BindingContext.ModelBinder,
|
||||||
BindingContext.ValueProvider,
|
BindingContext.ValueProvider,
|
||||||
|
ObjectValidator,
|
||||||
BindingContext.ValidatorProvider,
|
BindingContext.ValidatorProvider,
|
||||||
predicate);
|
predicate);
|
||||||
}
|
}
|
||||||
|
|
@ -1052,6 +1058,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
MetadataProvider,
|
MetadataProvider,
|
||||||
BindingContext.ModelBinder,
|
BindingContext.ModelBinder,
|
||||||
valueProvider,
|
valueProvider,
|
||||||
|
ObjectValidator,
|
||||||
BindingContext.ValidatorProvider,
|
BindingContext.ValidatorProvider,
|
||||||
includeExpressions);
|
includeExpressions);
|
||||||
}
|
}
|
||||||
|
|
@ -1091,6 +1098,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
MetadataProvider,
|
MetadataProvider,
|
||||||
BindingContext.ModelBinder,
|
BindingContext.ModelBinder,
|
||||||
valueProvider,
|
valueProvider,
|
||||||
|
ObjectValidator,
|
||||||
BindingContext.ValidatorProvider,
|
BindingContext.ValidatorProvider,
|
||||||
predicate);
|
predicate);
|
||||||
}
|
}
|
||||||
|
|
@ -1128,21 +1136,15 @@ namespace Microsoft.AspNet.Mvc
|
||||||
modelAccessor: () => model,
|
modelAccessor: () => model,
|
||||||
modelType: model.GetType());
|
modelType: model.GetType());
|
||||||
|
|
||||||
|
var modelName = prefix ?? string.Empty;
|
||||||
var validationContext = new ModelValidationContext(
|
var validationContext = new ModelValidationContext(
|
||||||
MetadataProvider,
|
modelName,
|
||||||
BindingContext.ValidatorProvider,
|
BindingContext.ValidatorProvider,
|
||||||
ModelState,
|
ModelState,
|
||||||
modelMetadata,
|
modelMetadata,
|
||||||
containerMetadata: null);
|
containerMetadata: null);
|
||||||
|
|
||||||
var modelName = prefix ?? string.Empty;
|
ObjectValidator.Validate(validationContext);
|
||||||
|
|
||||||
var validationNode = new ModelValidationNode(modelMetadata, modelName)
|
|
||||||
{
|
|
||||||
ValidateAllProperties = true
|
|
||||||
};
|
|
||||||
validationNode.Validate(validationContext);
|
|
||||||
|
|
||||||
return ModelState.IsValid;
|
return ModelState.IsValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,16 @@ namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
private readonly IModelMetadataProvider _modelMetadataProvider;
|
private readonly IModelMetadataProvider _modelMetadataProvider;
|
||||||
private readonly MvcOptions _options;
|
private readonly MvcOptions _options;
|
||||||
|
private readonly IObjectModelValidator _validator;
|
||||||
|
|
||||||
public DefaultControllerActionArgumentBinder(
|
public DefaultControllerActionArgumentBinder(
|
||||||
IModelMetadataProvider modelMetadataProvider,
|
IModelMetadataProvider modelMetadataProvider,
|
||||||
|
IObjectModelValidator validator,
|
||||||
IOptions<MvcOptions> optionsAccessor)
|
IOptions<MvcOptions> optionsAccessor)
|
||||||
{
|
{
|
||||||
_modelMetadataProvider = modelMetadataProvider;
|
_modelMetadataProvider = modelMetadataProvider;
|
||||||
_options = optionsAccessor.Options;
|
_options = optionsAccessor.Options;
|
||||||
|
_validator = validator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IDictionary<string, object>> GetActionArgumentsAsync(
|
public async Task<IDictionary<string, object>> GetActionArgumentsAsync(
|
||||||
|
|
@ -91,15 +94,21 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
var modelState = actionContext.ModelState;
|
var modelState = actionContext.ModelState;
|
||||||
modelState.MaxAllowedErrors = _options.MaxModelValidationErrors;
|
modelState.MaxAllowedErrors = _options.MaxModelValidationErrors;
|
||||||
|
|
||||||
foreach (var parameter in parameterMetadata)
|
foreach (var parameter in parameterMetadata)
|
||||||
{
|
{
|
||||||
var parameterType = parameter.ModelType;
|
var parameterType = parameter.ModelType;
|
||||||
var modelBindingContext = GetModelBindingContext(parameter, modelState, operationBindingContext);
|
var modelBindingContext = GetModelBindingContext(parameter, modelState, operationBindingContext);
|
||||||
if (await bindingContext.ModelBinder.BindModelAsync(modelBindingContext) &&
|
var modelBindingResult = await bindingContext.ModelBinder.BindModelAsync(modelBindingContext);
|
||||||
modelBindingContext.IsModelSet)
|
if (modelBindingResult != null && modelBindingResult.IsModelSet)
|
||||||
{
|
{
|
||||||
arguments[parameter.PropertyName] = modelBindingContext.Model;
|
arguments[parameter.PropertyName] = modelBindingResult.Model;
|
||||||
|
var validationContext = new ModelValidationContext(
|
||||||
|
modelBindingResult.Key,
|
||||||
|
bindingContext.ValidatorProvider,
|
||||||
|
actionContext.ModelState,
|
||||||
|
parameter,
|
||||||
|
containerMetadata: null);
|
||||||
|
_validator.Validate(validationContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
private readonly ActionContext _actionContext;
|
private readonly ActionContext _actionContext;
|
||||||
private readonly IScopedInstance<ActionBindingContext> _bindingContext;
|
private readonly IScopedInstance<ActionBindingContext> _bindingContext;
|
||||||
private readonly IInputFormatterSelector _formatterSelector;
|
private readonly IInputFormatterSelector _formatterSelector;
|
||||||
private readonly IBodyModelValidator _bodyModelValidator;
|
|
||||||
private readonly IValidationExcludeFiltersProvider _bodyValidationExcludeFiltersProvider;
|
private readonly IValidationExcludeFiltersProvider _bodyValidationExcludeFiltersProvider;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -28,26 +27,23 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
/// <param name="context">An accessor to the <see cref="ActionContext"/>.</param>
|
/// <param name="context">An accessor to the <see cref="ActionContext"/>.</param>
|
||||||
/// <param name="bindingContext">An accessor to the <see cref="ActionBindingContext"/>.</param>
|
/// <param name="bindingContext">An accessor to the <see cref="ActionBindingContext"/>.</param>
|
||||||
/// <param name="selector">The <see cref="IInputFormatterSelector"/>.</param>
|
/// <param name="selector">The <see cref="IInputFormatterSelector"/>.</param>
|
||||||
/// <param name="bodyModelValidator">The <see cref="IBodyModelValidator"/>.</param>
|
|
||||||
/// <param name="bodyValidationExcludeFiltersProvider">
|
/// <param name="bodyValidationExcludeFiltersProvider">
|
||||||
/// The <see cref="IValidationExcludeFiltersProvider"/>.
|
/// The <see cref="IValidationExcludeFiltersProvider"/>.
|
||||||
/// </param>
|
/// </param>
|
||||||
public BodyModelBinder([NotNull] IScopedInstance<ActionContext> context,
|
public BodyModelBinder([NotNull] IScopedInstance<ActionContext> context,
|
||||||
[NotNull] IScopedInstance<ActionBindingContext> bindingContext,
|
[NotNull] IScopedInstance<ActionBindingContext> bindingContext,
|
||||||
[NotNull] IInputFormatterSelector selector,
|
[NotNull] IInputFormatterSelector selector,
|
||||||
[NotNull] IBodyModelValidator bodyModelValidator,
|
|
||||||
[NotNull] IValidationExcludeFiltersProvider bodyValidationExcludeFiltersProvider)
|
[NotNull] IValidationExcludeFiltersProvider bodyValidationExcludeFiltersProvider)
|
||||||
: base(BindingSource.Body)
|
: base(BindingSource.Body)
|
||||||
{
|
{
|
||||||
_actionContext = context.Value;
|
_actionContext = context.Value;
|
||||||
_bindingContext = bindingContext;
|
_bindingContext = bindingContext;
|
||||||
_formatterSelector = selector;
|
_formatterSelector = selector;
|
||||||
_bodyModelValidator = bodyModelValidator;
|
|
||||||
_bodyValidationExcludeFiltersProvider = bodyValidationExcludeFiltersProvider;
|
_bodyValidationExcludeFiltersProvider = bodyValidationExcludeFiltersProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected async override Task BindModelCoreAsync([NotNull] ModelBindingContext bindingContext)
|
protected async override Task<ModelBindingResult> BindModelCoreAsync([NotNull] ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
var formatters = _bindingContext.Value.InputFormatters;
|
var formatters = _bindingContext.Value.InputFormatters;
|
||||||
|
|
||||||
|
|
@ -59,20 +55,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var unsupportedContentType = Resources.FormatUnsupportedContentType(
|
var unsupportedContentType = Resources.FormatUnsupportedContentType(
|
||||||
bindingContext.OperationBindingContext.HttpContext.Request.ContentType);
|
bindingContext.OperationBindingContext.HttpContext.Request.ContentType);
|
||||||
bindingContext.ModelState.AddModelError(bindingContext.ModelName, unsupportedContentType);
|
bindingContext.ModelState.AddModelError(bindingContext.ModelName, unsupportedContentType);
|
||||||
return;
|
return new ModelBindingResult(null, bindingContext.ModelName, isModelSet: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
bindingContext.Model = await formatter.ReadAsync(formatterContext);
|
var model = await formatter.ReadAsync(formatterContext);
|
||||||
|
return new ModelBindingResult(model, bindingContext.ModelName, isModelSet: true);
|
||||||
// Validate the deserialized object
|
|
||||||
var validationContext = new ModelValidationContext(
|
|
||||||
bindingContext.OperationBindingContext.MetadataProvider,
|
|
||||||
bindingContext.OperationBindingContext.ValidatorProvider,
|
|
||||||
bindingContext.ModelState,
|
|
||||||
bindingContext.ModelMetadata,
|
|
||||||
containerMetadata: null,
|
|
||||||
excludeFromValidationFilters: _bodyValidationExcludeFiltersProvider.ExcludeFilters);
|
|
||||||
_bodyModelValidator.Validate(validationContext, bindingContext.ModelName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override Task BindModelCoreAsync([NotNull] ModelBindingContext bindingContext)
|
protected override Task<ModelBindingResult> BindModelCoreAsync([NotNull] ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
var requestServices = bindingContext.OperationBindingContext.HttpContext.RequestServices;
|
var requestServices = bindingContext.OperationBindingContext.HttpContext.RequestServices;
|
||||||
bindingContext.Model = requestServices.GetRequiredService(bindingContext.ModelType);
|
var model = requestServices.GetRequiredService(bindingContext.ModelType);
|
||||||
return Task.FromResult(true);
|
return Task.FromResult(new ModelBindingResult(model, bindingContext.ModelName, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,8 @@ namespace Microsoft.AspNet.Mvc
|
||||||
/// <param name="metadataProvider">The provider used for reading metadata for the model type.</param>
|
/// <param name="metadataProvider">The provider used for reading metadata for the model type.</param>
|
||||||
/// <param name="modelBinder">The <see cref="IModelBinder"/> used for binding.</param>
|
/// <param name="modelBinder">The <see cref="IModelBinder"/> used for binding.</param>
|
||||||
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
|
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
|
||||||
|
/// <param name="objectModelValidator">The <see cref="IObjectModelValidator"/> used for validating the
|
||||||
|
/// bound values.</param>
|
||||||
/// <param name="validatorProvider">The <see cref="IModelValidatorProvider"/> used for executing validation
|
/// <param name="validatorProvider">The <see cref="IModelValidatorProvider"/> used for executing validation
|
||||||
/// on the model instance.</param>
|
/// on the model instance.</param>
|
||||||
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful</returns>
|
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful</returns>
|
||||||
|
|
@ -40,6 +42,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
[NotNull] IModelMetadataProvider metadataProvider,
|
[NotNull] IModelMetadataProvider metadataProvider,
|
||||||
[NotNull] IModelBinder modelBinder,
|
[NotNull] IModelBinder modelBinder,
|
||||||
[NotNull] IValueProvider valueProvider,
|
[NotNull] IValueProvider valueProvider,
|
||||||
|
[NotNull] IObjectModelValidator objectModelValidator,
|
||||||
[NotNull] IModelValidatorProvider validatorProvider)
|
[NotNull] IModelValidatorProvider validatorProvider)
|
||||||
where TModel : class
|
where TModel : class
|
||||||
{
|
{
|
||||||
|
|
@ -52,6 +55,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
metadataProvider,
|
metadataProvider,
|
||||||
modelBinder,
|
modelBinder,
|
||||||
valueProvider,
|
valueProvider,
|
||||||
|
objectModelValidator,
|
||||||
validatorProvider,
|
validatorProvider,
|
||||||
predicate: (context, propertyName) => true);
|
predicate: (context, propertyName) => true);
|
||||||
}
|
}
|
||||||
|
|
@ -71,6 +75,8 @@ namespace Microsoft.AspNet.Mvc
|
||||||
/// <param name="metadataProvider">The provider used for reading metadata for the model type.</param>
|
/// <param name="metadataProvider">The provider used for reading metadata for the model type.</param>
|
||||||
/// <param name="modelBinder">The <see cref="IModelBinder"/> used for binding.</param>
|
/// <param name="modelBinder">The <see cref="IModelBinder"/> used for binding.</param>
|
||||||
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
|
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
|
||||||
|
/// <param name="objectModelValidator">The <see cref="IObjectModelValidator"/> used for validating the
|
||||||
|
/// bound values.</param>
|
||||||
/// <param name="validatorProvider">The <see cref="IModelValidatorProvider"/> used for executing validation
|
/// <param name="validatorProvider">The <see cref="IModelValidatorProvider"/> used for executing validation
|
||||||
/// on the model
|
/// on the model
|
||||||
/// instance.</param>
|
/// instance.</param>
|
||||||
|
|
@ -85,6 +91,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
[NotNull] IModelMetadataProvider metadataProvider,
|
[NotNull] IModelMetadataProvider metadataProvider,
|
||||||
[NotNull] IModelBinder modelBinder,
|
[NotNull] IModelBinder modelBinder,
|
||||||
[NotNull] IValueProvider valueProvider,
|
[NotNull] IValueProvider valueProvider,
|
||||||
|
[NotNull] IObjectModelValidator objectModelValidator,
|
||||||
[NotNull] IModelValidatorProvider validatorProvider,
|
[NotNull] IModelValidatorProvider validatorProvider,
|
||||||
[NotNull] params Expression<Func<TModel, object>>[] includeExpressions)
|
[NotNull] params Expression<Func<TModel, object>>[] includeExpressions)
|
||||||
where TModel : class
|
where TModel : class
|
||||||
|
|
@ -100,6 +107,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
metadataProvider,
|
metadataProvider,
|
||||||
modelBinder,
|
modelBinder,
|
||||||
valueProvider,
|
valueProvider,
|
||||||
|
objectModelValidator,
|
||||||
validatorProvider,
|
validatorProvider,
|
||||||
predicate: predicate);
|
predicate: predicate);
|
||||||
}
|
}
|
||||||
|
|
@ -119,6 +127,8 @@ namespace Microsoft.AspNet.Mvc
|
||||||
/// <param name="metadataProvider">The provider used for reading metadata for the model type.</param>
|
/// <param name="metadataProvider">The provider used for reading metadata for the model type.</param>
|
||||||
/// <param name="modelBinder">The <see cref="IModelBinder"/> used for binding.</param>
|
/// <param name="modelBinder">The <see cref="IModelBinder"/> used for binding.</param>
|
||||||
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
|
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
|
||||||
|
/// /// <param name="objectModelValidator">The <see cref="IObjectModelValidator"/> used for validating the
|
||||||
|
/// bound values.</param>
|
||||||
/// <param name="validatorProvider">The <see cref="IModelValidatorProvider"/> used for executing validation
|
/// <param name="validatorProvider">The <see cref="IModelValidatorProvider"/> used for executing validation
|
||||||
/// on the model instance.</param>
|
/// on the model instance.</param>
|
||||||
/// <param name="predicate">A predicate which can be used to
|
/// <param name="predicate">A predicate which can be used to
|
||||||
|
|
@ -132,12 +142,13 @@ namespace Microsoft.AspNet.Mvc
|
||||||
[NotNull] IModelMetadataProvider metadataProvider,
|
[NotNull] IModelMetadataProvider metadataProvider,
|
||||||
[NotNull] IModelBinder modelBinder,
|
[NotNull] IModelBinder modelBinder,
|
||||||
[NotNull] IValueProvider valueProvider,
|
[NotNull] IValueProvider valueProvider,
|
||||||
|
[NotNull] IObjectModelValidator objectModelValidator,
|
||||||
[NotNull] IModelValidatorProvider validatorProvider,
|
[NotNull] IModelValidatorProvider validatorProvider,
|
||||||
[NotNull] Func<ModelBindingContext, string, bool> predicate)
|
[NotNull] Func<ModelBindingContext, string, bool> predicate)
|
||||||
where TModel : class
|
where TModel : class
|
||||||
{
|
{
|
||||||
var modelMetadata = metadataProvider.GetMetadataForType(
|
var modelMetadata = metadataProvider.GetMetadataForType(
|
||||||
modelAccessor: null,
|
modelAccessor: () => model,
|
||||||
modelType: model.GetType());
|
modelType: model.GetType());
|
||||||
|
|
||||||
var operationBindingContext = new OperationBindingContext
|
var operationBindingContext = new OperationBindingContext
|
||||||
|
|
@ -152,7 +163,6 @@ namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
ModelMetadata = modelMetadata,
|
ModelMetadata = modelMetadata,
|
||||||
ModelName = prefix,
|
ModelName = prefix,
|
||||||
Model = model,
|
|
||||||
ModelState = modelState,
|
ModelState = modelState,
|
||||||
ValueProvider = valueProvider,
|
ValueProvider = valueProvider,
|
||||||
FallbackToEmptyPrefix = true,
|
FallbackToEmptyPrefix = true,
|
||||||
|
|
@ -160,8 +170,12 @@ namespace Microsoft.AspNet.Mvc
|
||||||
PropertyFilter = predicate
|
PropertyFilter = predicate
|
||||||
};
|
};
|
||||||
|
|
||||||
if (await modelBinder.BindModelAsync(modelBindingContext))
|
var modelBindingResult = await modelBinder.BindModelAsync(modelBindingContext);
|
||||||
|
if (modelBindingResult != null)
|
||||||
{
|
{
|
||||||
|
var modelValidationContext = new ModelValidationContext(modelBindingContext, modelMetadata);
|
||||||
|
modelValidationContext.RootPrefix = prefix;
|
||||||
|
objectModelValidator.Validate(modelValidationContext);
|
||||||
return modelState.IsValid;
|
return modelState.IsValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,21 +9,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
public class ArrayModelBinder<TElement> : CollectionModelBinder<TElement>
|
public class ArrayModelBinder<TElement> : CollectionModelBinder<TElement>
|
||||||
{
|
{
|
||||||
public override Task<bool> BindModelAsync(ModelBindingContext bindingContext)
|
public override Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
if (bindingContext.ModelMetadata.IsReadOnly)
|
if (bindingContext.ModelMetadata.IsReadOnly)
|
||||||
{
|
{
|
||||||
return Task.FromResult(false);
|
return Task.FromResult<ModelBindingResult>(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.BindModelAsync(bindingContext);
|
return base.BindModelAsync(bindingContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool CreateOrReplaceCollection(ModelBindingContext bindingContext,
|
protected override object GetModel(IEnumerable<TElement> newCollection)
|
||||||
IList<TElement> newCollection)
|
|
||||||
{
|
{
|
||||||
bindingContext.Model = newCollection.ToArray();
|
return newCollection.ToArray();
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,13 +21,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
new ConcurrentDictionary<Type, ObjectFactory>();
|
new ConcurrentDictionary<Type, ObjectFactory>();
|
||||||
|
|
||||||
|
|
||||||
public async Task<bool> BindModelAsync(ModelBindingContext bindingContext)
|
public async Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
if (bindingContext.ModelMetadata.BinderType == null)
|
if (bindingContext.ModelMetadata.BinderType == null)
|
||||||
{
|
{
|
||||||
// Return false so that we are able to continue with the default set of model binders,
|
// Return null so that we are able to continue with the default set of model binders,
|
||||||
// if there is no specific model binder provided.
|
// if there is no specific model binder provided.
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var requestServices = bindingContext.OperationBindingContext.HttpContext.RequestServices;
|
var requestServices = bindingContext.OperationBindingContext.HttpContext.RequestServices;
|
||||||
|
|
@ -52,11 +52,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await modelBinder.BindModelAsync(bindingContext);
|
var result = await modelBinder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// return true here, because this binder will handle all cases where the model binder is
|
var modelBindingResult = result != null ?
|
||||||
// specified by metadata.
|
new ModelBindingResult(result.Model, result.Key, result.IsModelSet) :
|
||||||
return true;
|
new ModelBindingResult(null, bindingContext.ModelName, false);
|
||||||
|
|
||||||
|
// return a non null modelbinding result here, because this binder will handle all cases where the
|
||||||
|
// model binder is specified by metadata.
|
||||||
|
return modelBindingResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,10 +58,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// A <see cref="Task"/> which will complete when model binding has completed.
|
/// A <see cref="Task"/> which will complete when model binding has completed.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
protected abstract Task BindModelCoreAsync([NotNull] ModelBindingContext bindingContext);
|
protected abstract Task<ModelBindingResult> BindModelCoreAsync([NotNull] ModelBindingContext bindingContext);
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<bool> BindModelAsync(ModelBindingContext context)
|
public async Task<ModelBindingResult> BindModelAsync(ModelBindingContext context)
|
||||||
{
|
{
|
||||||
var bindingSourceMetadata = context.ModelMetadata.BinderMetadata as IBindingSourceMetadata;
|
var bindingSourceMetadata = context.ModelMetadata.BinderMetadata as IBindingSourceMetadata;
|
||||||
var allowedBindingSource = bindingSourceMetadata?.BindingSource;
|
var allowedBindingSource = bindingSourceMetadata?.BindingSource;
|
||||||
|
|
@ -70,14 +70,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
// Binding Sources are opt-in. This model either didn't specify one or specified something
|
// Binding Sources are opt-in. This model either didn't specify one or specified something
|
||||||
// incompatible so let other binders run.
|
// incompatible so let other binders run.
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
await BindModelCoreAsync(context);
|
var result = await BindModelCoreAsync(context);
|
||||||
|
|
||||||
|
var modelBindingResult =
|
||||||
|
result != null ?
|
||||||
|
new ModelBindingResult(result.Model, result.Key, result.IsModelSet) :
|
||||||
|
new ModelBindingResult(null, context.ModelName, false);
|
||||||
|
|
||||||
// Prevent other model binders from running because this model binder is the only handler for
|
// Prevent other model binders from running because this model binder is the only handler for
|
||||||
// its binding source.
|
// its binding source.
|
||||||
return true;
|
return modelBindingResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
public class ByteArrayModelBinder : IModelBinder
|
public class ByteArrayModelBinder : IModelBinder
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<bool> BindModelAsync([NotNull] ModelBindingContext bindingContext)
|
public async Task<ModelBindingResult> BindModelAsync([NotNull] ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
if (bindingContext.ModelType != typeof(byte[]))
|
if (bindingContext.ModelType != typeof(byte[]))
|
||||||
{
|
{
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var valueProviderResult = await bindingContext.ValueProvider.GetValueAsync(bindingContext.ModelName);
|
var valueProviderResult = await bindingContext.ValueProvider.GetValueAsync(bindingContext.ModelName);
|
||||||
|
|
@ -25,7 +25,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
// case 1: there was no <input ... /> element containing this data
|
// case 1: there was no <input ... /> element containing this data
|
||||||
if (valueProviderResult == null)
|
if (valueProviderResult == null)
|
||||||
{
|
{
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var value = valueProviderResult.AttemptedValue;
|
var value = valueProviderResult.AttemptedValue;
|
||||||
|
|
@ -33,19 +33,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
// case 2: there was an <input ... /> element but it was left blank
|
// case 2: there was an <input ... /> element but it was left blank
|
||||||
if (string.IsNullOrEmpty(value))
|
if (string.IsNullOrEmpty(value))
|
||||||
{
|
{
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
bindingContext.Model = Convert.FromBase64String(value);
|
var model = Convert.FromBase64String(value);
|
||||||
|
return new ModelBindingResult(model, bindingContext.ModelName, true);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, ex);
|
bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new ModelBindingResult(model: null, key: bindingContext.ModelName, isModelSet: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -12,15 +12,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
public class CancellationTokenModelBinder : IModelBinder
|
public class CancellationTokenModelBinder : IModelBinder
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task<bool> BindModelAsync(ModelBindingContext bindingContext)
|
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
if (bindingContext.ModelType == typeof(CancellationToken))
|
if (bindingContext.ModelType == typeof(CancellationToken))
|
||||||
{
|
{
|
||||||
bindingContext.Model = bindingContext.OperationBindingContext.HttpContext.RequestAborted;
|
var model = bindingContext.OperationBindingContext.HttpContext.RequestAborted;
|
||||||
return Task.FromResult(true);
|
return Task.FromResult(new ModelBindingResult(model, bindingContext.ModelName, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.FromResult(false);
|
return Task.FromResult<ModelBindingResult>(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
public class CollectionModelBinder<TElement> : IModelBinder
|
public class CollectionModelBinder<TElement> : IModelBinder
|
||||||
{
|
{
|
||||||
public virtual async Task<bool> BindModelAsync(ModelBindingContext bindingContext)
|
public virtual async Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
ModelBindingHelper.ValidateBindingContext(bindingContext);
|
ModelBindingHelper.ValidateBindingContext(bindingContext);
|
||||||
|
|
||||||
if (!await bindingContext.ValueProvider.ContainsPrefixAsync(bindingContext.ModelName))
|
if (!await bindingContext.ValueProvider.ContainsPrefixAsync(bindingContext.ModelName))
|
||||||
{
|
{
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var valueProviderResult = await bindingContext.ValueProvider.GetValueAsync(bindingContext.ModelName);
|
var valueProviderResult = await bindingContext.ValueProvider.GetValueAsync(bindingContext.ModelName);
|
||||||
|
|
@ -27,13 +27,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
BindSimpleCollection(bindingContext, valueProviderResult.RawValue, valueProviderResult.Culture) :
|
BindSimpleCollection(bindingContext, valueProviderResult.RawValue, valueProviderResult.Culture) :
|
||||||
BindComplexCollection(bindingContext);
|
BindComplexCollection(bindingContext);
|
||||||
var boundCollection = await bindCollectionTask;
|
var boundCollection = await bindCollectionTask;
|
||||||
|
var model = GetModel(boundCollection);
|
||||||
return CreateOrReplaceCollection(bindingContext, boundCollection);
|
return new ModelBindingResult(model, bindingContext.ModelName, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used when the ValueProvider contains the collection to be bound as a single element, e.g. the raw value
|
// Used when the ValueProvider contains the collection to be bound as a single element, e.g. the raw value
|
||||||
// is [ "1", "2" ] and needs to be converted to an int[].
|
// is [ "1", "2" ] and needs to be converted to an int[].
|
||||||
internal async Task<List<TElement>> BindSimpleCollection(ModelBindingContext bindingContext,
|
internal async Task<IEnumerable<TElement>> BindSimpleCollection(ModelBindingContext bindingContext,
|
||||||
object rawValue,
|
object rawValue,
|
||||||
CultureInfo culture)
|
CultureInfo culture)
|
||||||
{
|
{
|
||||||
|
|
@ -62,10 +62,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
};
|
};
|
||||||
|
|
||||||
object boundValue = null;
|
object boundValue = null;
|
||||||
if (await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(innerBindingContext))
|
var result = await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(innerBindingContext);
|
||||||
|
if (result != null)
|
||||||
{
|
{
|
||||||
boundValue = innerBindingContext.Model;
|
boundValue = result.Model;
|
||||||
bindingContext.ValidationNode.ChildNodes.Add(innerBindingContext.ValidationNode);
|
|
||||||
}
|
}
|
||||||
boundCollection.Add(ModelBindingHelper.CastOrDefault<TElement>(boundValue));
|
boundCollection.Add(ModelBindingHelper.CastOrDefault<TElement>(boundValue));
|
||||||
}
|
}
|
||||||
|
|
@ -74,7 +74,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used when the ValueProvider contains the collection to be bound as multiple elements, e.g. foo[0], foo[1].
|
// Used when the ValueProvider contains the collection to be bound as multiple elements, e.g. foo[0], foo[1].
|
||||||
private async Task<List<TElement>> BindComplexCollection(ModelBindingContext bindingContext)
|
private async Task<IEnumerable<TElement>> BindComplexCollection(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
var indexPropertyName = ModelBindingHelper.CreatePropertyModelName(bindingContext.ModelName, "index");
|
var indexPropertyName = ModelBindingHelper.CreatePropertyModelName(bindingContext.ModelName, "index");
|
||||||
var valueProviderResultIndex = await bindingContext.ValueProvider.GetValueAsync(indexPropertyName);
|
var valueProviderResultIndex = await bindingContext.ValueProvider.GetValueAsync(indexPropertyName);
|
||||||
|
|
@ -82,7 +82,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
return await BindComplexCollectionFromIndexes(bindingContext, indexNames);
|
return await BindComplexCollectionFromIndexes(bindingContext, indexNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<List<TElement>> BindComplexCollectionFromIndexes(ModelBindingContext bindingContext,
|
internal async Task<IEnumerable<TElement>> BindComplexCollectionFromIndexes(ModelBindingContext bindingContext,
|
||||||
IEnumerable<string> indexNames)
|
IEnumerable<string> indexNames)
|
||||||
{
|
{
|
||||||
bool indexNamesIsFinite;
|
bool indexNamesIsFinite;
|
||||||
|
|
@ -110,13 +110,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
var modelType = bindingContext.ModelType;
|
var modelType = bindingContext.ModelType;
|
||||||
|
|
||||||
if (await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(childBindingContext))
|
var result = await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(childBindingContext);
|
||||||
|
if (result != null)
|
||||||
{
|
{
|
||||||
didBind = true;
|
didBind = true;
|
||||||
boundValue = childBindingContext.Model;
|
boundValue = result.Model;
|
||||||
|
|
||||||
// merge validation up
|
|
||||||
bindingContext.ValidationNode.ChildNodes.Add(childBindingContext.ValidationNode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// infinite size collection stops on first bind failure
|
// infinite size collection stops on first bind failure
|
||||||
|
|
@ -133,11 +131,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
// Extensibility point that allows the bound collection to be manipulated or transformed before
|
// Extensibility point that allows the bound collection to be manipulated or transformed before
|
||||||
// being returned from the binder.
|
// being returned from the binder.
|
||||||
protected virtual bool CreateOrReplaceCollection(ModelBindingContext bindingContext,
|
protected virtual object GetModel(IEnumerable<TElement> newCollection)
|
||||||
IList<TElement> newCollection)
|
|
||||||
{
|
{
|
||||||
CreateOrReplaceCollection(bindingContext, newCollection, () => new List<TElement>());
|
return newCollection;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static object[] RawValueToObjectArray(object rawValue)
|
internal static object[] RawValueToObjectArray(object rawValue)
|
||||||
|
|
@ -165,23 +161,5 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
// fallback
|
// fallback
|
||||||
return new[] { rawValue };
|
return new[] { rawValue };
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void CreateOrReplaceCollection(ModelBindingContext bindingContext,
|
|
||||||
IEnumerable<TElement> incomingElements,
|
|
||||||
Func<ICollection<TElement>> creator)
|
|
||||||
{
|
|
||||||
var collection = bindingContext.Model as ICollection<TElement>;
|
|
||||||
if (collection == null || collection.IsReadOnly)
|
|
||||||
{
|
|
||||||
collection = creator();
|
|
||||||
bindingContext.Model = collection;
|
|
||||||
}
|
|
||||||
|
|
||||||
collection.Clear();
|
|
||||||
foreach (var element in incomingElements)
|
|
||||||
{
|
|
||||||
collection.Add(element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
ModelMetadata = modelMetadata;
|
ModelMetadata = modelMetadata;
|
||||||
PropertyMetadata = new Collection<ModelMetadata>(propertyMetadata.ToList());
|
PropertyMetadata = new Collection<ModelMetadata>(propertyMetadata.ToList());
|
||||||
Results = new Dictionary<ModelMetadata, ComplexModelDtoResult>();
|
Results = new Dictionary<ModelMetadata, ModelBindingResult>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModelMetadata ModelMetadata { get; private set; }
|
public ModelMetadata ModelMetadata { get; private set; }
|
||||||
|
|
@ -26,6 +26,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
// attempted. If binding failed, the entry's value will be null. If binding
|
// attempted. If binding failed, the entry's value will be null. If binding
|
||||||
// was never attempted, this dictionary will not contain a corresponding
|
// was never attempted, this dictionary will not contain a corresponding
|
||||||
// entry.
|
// entry.
|
||||||
public IDictionary<ModelMetadata, ComplexModelDtoResult> Results { get; private set; }
|
public IDictionary<ModelMetadata, ModelBindingResult> Results { get; private set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,37 +8,38 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
public sealed class ComplexModelDtoModelBinder : IModelBinder
|
public sealed class ComplexModelDtoModelBinder : IModelBinder
|
||||||
{
|
{
|
||||||
public async Task<bool> BindModelAsync(ModelBindingContext bindingContext)
|
public async Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
if (bindingContext.ModelType == typeof(ComplexModelDto))
|
if (bindingContext.ModelType != typeof(ComplexModelDto))
|
||||||
{
|
{
|
||||||
ModelBindingHelper.ValidateBindingContext(bindingContext,
|
return null;
|
||||||
typeof(ComplexModelDto),
|
|
||||||
allowNullModel: false);
|
|
||||||
|
|
||||||
var dto = (ComplexModelDto)bindingContext.Model;
|
|
||||||
foreach (var propertyMetadata in dto.PropertyMetadata)
|
|
||||||
{
|
|
||||||
var propertyModelName = ModelBindingHelper.CreatePropertyModelName(bindingContext.ModelName,
|
|
||||||
propertyMetadata.PropertyName);
|
|
||||||
|
|
||||||
var propertyBindingContext = new ModelBindingContext(bindingContext,
|
|
||||||
propertyModelName,
|
|
||||||
propertyMetadata);
|
|
||||||
|
|
||||||
// bind and propagate the values
|
|
||||||
// If we can't bind then leave the result missing (don't add a null).
|
|
||||||
if (await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(propertyBindingContext))
|
|
||||||
{
|
|
||||||
var result = ComplexModelDtoResult.FromBindingContext(propertyBindingContext);
|
|
||||||
dto.Results[propertyMetadata] = result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
ModelBindingHelper.ValidateBindingContext(bindingContext,
|
||||||
|
typeof(ComplexModelDto),
|
||||||
|
allowNullModel: false);
|
||||||
|
|
||||||
|
var dto = (ComplexModelDto)bindingContext.ModelMetadata.Model;
|
||||||
|
foreach (var propertyMetadata in dto.PropertyMetadata)
|
||||||
|
{
|
||||||
|
var propertyModelName = ModelBindingHelper.CreatePropertyModelName(bindingContext.ModelName,
|
||||||
|
propertyMetadata.PropertyName);
|
||||||
|
|
||||||
|
var propertyBindingContext = new ModelBindingContext(bindingContext,
|
||||||
|
propertyModelName,
|
||||||
|
propertyMetadata);
|
||||||
|
|
||||||
|
// bind and propagate the values
|
||||||
|
// If we can't bind then leave the result missing (don't add a null).
|
||||||
|
var modelBindingResult =
|
||||||
|
await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(propertyBindingContext);
|
||||||
|
if (modelBindingResult != null)
|
||||||
|
{
|
||||||
|
dto.Results[propertyMetadata] = modelBindingResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ModelBindingResult(dto, bindingContext.ModelName, isModelSet: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
|
||||||
{
|
|
||||||
public sealed class ComplexModelDtoResult
|
|
||||||
{
|
|
||||||
public static ComplexModelDtoResult FromBindingContext([NotNull] ModelBindingContext context)
|
|
||||||
{
|
|
||||||
return new ComplexModelDtoResult(context.Model, context.IsModelSet, context.ValidationNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ComplexModelDtoResult(
|
|
||||||
object model,
|
|
||||||
bool isModelBound,
|
|
||||||
[NotNull] ModelValidationNode validationNode)
|
|
||||||
{
|
|
||||||
Model = model;
|
|
||||||
IsModelBound = isModelBound;
|
|
||||||
ValidationNode = validationNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsModelBound { get; }
|
|
||||||
|
|
||||||
public object Model { get; set; }
|
|
||||||
|
|
||||||
public ModelValidationNode ValidationNode { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
|
|
@ -30,97 +31,86 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IReadOnlyList<IModelBinder> ModelBinders { get; }
|
public IReadOnlyList<IModelBinder> ModelBinders { get; }
|
||||||
|
|
||||||
public virtual async Task<bool> BindModelAsync([NotNull] ModelBindingContext bindingContext)
|
public virtual async Task<ModelBindingResult> BindModelAsync([NotNull] ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
var newBindingContext = CreateNewBindingContext(bindingContext,
|
var newBindingContext = CreateNewBindingContext(bindingContext,
|
||||||
bindingContext.ModelName,
|
bindingContext.ModelName);
|
||||||
reuseValidationNode: true);
|
|
||||||
|
|
||||||
var boundSuccessfully = await TryBind(newBindingContext);
|
var modelBindingResult = await TryBind(newBindingContext);
|
||||||
if (!boundSuccessfully && !string.IsNullOrEmpty(bindingContext.ModelName)
|
if (modelBindingResult == null && !string.IsNullOrEmpty(bindingContext.ModelName)
|
||||||
&& bindingContext.FallbackToEmptyPrefix)
|
&& bindingContext.FallbackToEmptyPrefix)
|
||||||
{
|
{
|
||||||
// fallback to empty prefix?
|
// fallback to empty prefix?
|
||||||
newBindingContext = CreateNewBindingContext(bindingContext,
|
newBindingContext = CreateNewBindingContext(bindingContext,
|
||||||
modelName: string.Empty,
|
modelName: string.Empty);
|
||||||
reuseValidationNode: false);
|
modelBindingResult = await TryBind(newBindingContext);
|
||||||
boundSuccessfully = await TryBind(newBindingContext);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!boundSuccessfully)
|
if (modelBindingResult == null)
|
||||||
{
|
{
|
||||||
return false; // something went wrong
|
return null; // something went wrong
|
||||||
}
|
|
||||||
|
|
||||||
// Only perform validation at the root of the object graph. ValidationNode will recursively walk the graph.
|
|
||||||
// Ignore ComplexModelDto since it essentially wraps the primary object.
|
|
||||||
if (newBindingContext.IsModelSet && IsBindingAtRootOfObjectGraph(newBindingContext))
|
|
||||||
{
|
|
||||||
// run validation and return the model
|
|
||||||
// If we fell back to an empty prefix above and are dealing with simple types,
|
|
||||||
// propagate the non-blank model name through for user clarity in validation errors.
|
|
||||||
// Complex types will reveal their individual properties as model names and do not require this.
|
|
||||||
if (!newBindingContext.ModelMetadata.IsComplexType &&
|
|
||||||
string.IsNullOrEmpty(newBindingContext.ModelName))
|
|
||||||
{
|
|
||||||
newBindingContext.ValidationNode = new ModelValidationNode(newBindingContext.ModelMetadata,
|
|
||||||
bindingContext.ModelName);
|
|
||||||
}
|
|
||||||
|
|
||||||
var validationContext = new ModelValidationContext(
|
|
||||||
bindingContext.OperationBindingContext.MetadataProvider,
|
|
||||||
bindingContext.OperationBindingContext.ValidatorProvider,
|
|
||||||
bindingContext.ModelState,
|
|
||||||
bindingContext.ModelMetadata,
|
|
||||||
containerMetadata: null);
|
|
||||||
|
|
||||||
newBindingContext.ValidationNode.Validate(validationContext, parentNode: null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bindingContext.OperationBindingContext.BodyBindingState =
|
bindingContext.OperationBindingContext.BodyBindingState =
|
||||||
newBindingContext.OperationBindingContext.BodyBindingState;
|
newBindingContext.OperationBindingContext.BodyBindingState;
|
||||||
|
|
||||||
if (newBindingContext.IsModelSet)
|
if (modelBindingResult.IsModelSet)
|
||||||
{
|
{
|
||||||
bindingContext.Model = newBindingContext.Model;
|
bindingContext.ModelMetadata.Model = modelBindingResult.Model;
|
||||||
|
|
||||||
|
// Update the model state key if we are bound using an empty prefix and it is a complex type.
|
||||||
|
// This is needed as validation uses the model state key to log errors. The client validation expects
|
||||||
|
// the errors with property names rather than the full name.
|
||||||
|
if (newBindingContext.ModelMetadata.IsComplexType && string.IsNullOrEmpty(modelBindingResult.Key))
|
||||||
|
{
|
||||||
|
// For non-complex types, if we fell back to the empty prefix, we should still be using the name
|
||||||
|
// of the parameter/property. Complex types have their own property names which acts as model
|
||||||
|
// state keys and do not need special treatment.
|
||||||
|
// For example :
|
||||||
|
//
|
||||||
|
// public class Model
|
||||||
|
// {
|
||||||
|
// public int SimpleType { get; set; }
|
||||||
|
// }
|
||||||
|
// public void Action(int id, Model model)
|
||||||
|
// {
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// In this case, for the model parameter the key would be SimpleType instead of model.SimpleType.
|
||||||
|
// (i.e here the prefix for the model key is empty).
|
||||||
|
// For the id parameter the key would be id.
|
||||||
|
return modelBindingResult;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new ModelBindingResult(
|
||||||
|
modelBindingResult.Model,
|
||||||
|
bindingContext.ModelName,
|
||||||
|
modelBindingResult.IsModelSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> TryBind(ModelBindingContext bindingContext)
|
private async Task<ModelBindingResult> TryBind(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
RuntimeHelpers.EnsureSufficientExecutionStack();
|
RuntimeHelpers.EnsureSufficientExecutionStack();
|
||||||
|
|
||||||
foreach (var binder in ModelBinders)
|
foreach (var binder in ModelBinders)
|
||||||
{
|
{
|
||||||
if (await binder.BindModelAsync(bindingContext))
|
var result = await binder.BindModelAsync(bindingContext);
|
||||||
|
if (result != null)
|
||||||
{
|
{
|
||||||
return true;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Either we couldn't find a binder, or the binder couldn't bind. Distinction is not important.
|
// Either we couldn't find a binder, or the binder couldn't bind. Distinction is not important.
|
||||||
return false;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsBindingAtRootOfObjectGraph(ModelBindingContext bindingContext)
|
|
||||||
{
|
|
||||||
// We're at the root of the object graph if the model does does not have a container.
|
|
||||||
// This statement is true for complex types at the root twice over - once with the actual model
|
|
||||||
// and once when when it is represented by a ComplexModelDto. Ignore the latter case.
|
|
||||||
|
|
||||||
return bindingContext.ModelMetadata.ContainerType == null &&
|
|
||||||
bindingContext.ModelMetadata.ModelType != typeof(ComplexModelDto);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ModelBindingContext CreateNewBindingContext(ModelBindingContext oldBindingContext,
|
private static ModelBindingContext CreateNewBindingContext(ModelBindingContext oldBindingContext,
|
||||||
string modelName,
|
string modelName)
|
||||||
bool reuseValidationNode)
|
|
||||||
{
|
{
|
||||||
var newBindingContext = new ModelBindingContext
|
var newBindingContext = new ModelBindingContext
|
||||||
{
|
{
|
||||||
IsModelSet = oldBindingContext.IsModelSet,
|
|
||||||
ModelMetadata = oldBindingContext.ModelMetadata,
|
ModelMetadata = oldBindingContext.ModelMetadata,
|
||||||
ModelName = modelName,
|
ModelName = modelName,
|
||||||
ModelState = oldBindingContext.ModelState,
|
ModelState = oldBindingContext.ModelState,
|
||||||
|
|
@ -129,12 +119,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
PropertyFilter = oldBindingContext.PropertyFilter,
|
PropertyFilter = oldBindingContext.PropertyFilter,
|
||||||
};
|
};
|
||||||
|
|
||||||
// validation is expensive to create, so copy it over if we can
|
|
||||||
if (reuseValidationNode)
|
|
||||||
{
|
|
||||||
newBindingContext.ValidationNode = oldBindingContext.ValidationNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
newBindingContext.OperationBindingContext.BodyBindingState = GetBodyBindingState(oldBindingContext);
|
newBindingContext.OperationBindingContext.BodyBindingState = GetBodyBindingState(oldBindingContext);
|
||||||
|
|
||||||
// If the property has a specified data binding sources, we need to filter the set of value providers
|
// If the property has a specified data binding sources, we need to filter the set of value providers
|
||||||
|
|
|
||||||
|
|
@ -3,37 +3,15 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
public class DictionaryModelBinder<TKey, TValue> : CollectionModelBinder<KeyValuePair<TKey, TValue>>
|
public class DictionaryModelBinder<TKey, TValue> : CollectionModelBinder<KeyValuePair<TKey, TValue>>
|
||||||
{
|
{
|
||||||
protected override bool CreateOrReplaceCollection(ModelBindingContext bindingContext,
|
protected override object GetModel(IEnumerable<KeyValuePair<TKey, TValue>> newCollection)
|
||||||
IList<KeyValuePair<TKey, TValue>> newCollection)
|
|
||||||
{
|
{
|
||||||
CreateOrReplaceDictionary(bindingContext, newCollection, () => new Dictionary<TKey, TValue>());
|
return newCollection.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void CreateOrReplaceDictionary(ModelBindingContext bindingContext,
|
|
||||||
IEnumerable<KeyValuePair<TKey, TValue>> incomingElements,
|
|
||||||
Func<IDictionary<TKey, TValue>> creator)
|
|
||||||
{
|
|
||||||
var dictionary = bindingContext.Model as IDictionary<TKey, TValue>;
|
|
||||||
if (dictionary == null || dictionary.IsReadOnly)
|
|
||||||
{
|
|
||||||
dictionary = creator();
|
|
||||||
bindingContext.Model = dictionary;
|
|
||||||
}
|
|
||||||
|
|
||||||
dictionary.Clear();
|
|
||||||
foreach (var element in incomingElements)
|
|
||||||
{
|
|
||||||
if (element.Key != null)
|
|
||||||
{
|
|
||||||
dictionary[element.Key] = element.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,35 +16,36 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
public class FormCollectionModelBinder : IModelBinder
|
public class FormCollectionModelBinder : IModelBinder
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<bool> BindModelAsync([NotNull] ModelBindingContext bindingContext)
|
public async Task<ModelBindingResult> BindModelAsync([NotNull] ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
if (bindingContext.ModelType != typeof(IFormCollection) &&
|
if (bindingContext.ModelType != typeof(IFormCollection) &&
|
||||||
bindingContext.ModelType != typeof(FormCollection))
|
bindingContext.ModelType != typeof(FormCollection))
|
||||||
{
|
{
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object model = null;
|
||||||
var request = bindingContext.OperationBindingContext.HttpContext.Request;
|
var request = bindingContext.OperationBindingContext.HttpContext.Request;
|
||||||
if (request.HasFormContentType)
|
if (request.HasFormContentType)
|
||||||
{
|
{
|
||||||
var form = await request.ReadFormAsync();
|
var form = await request.ReadFormAsync();
|
||||||
if (bindingContext.ModelType.IsAssignableFrom(form.GetType()))
|
if (bindingContext.ModelType.IsAssignableFrom(form.GetType()))
|
||||||
{
|
{
|
||||||
bindingContext.Model = form;
|
model = form;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var formValuesLookup = form.ToDictionary(p => p.Key,
|
var formValuesLookup = form.ToDictionary(p => p.Key,
|
||||||
p => p.Value);
|
p => p.Value);
|
||||||
bindingContext.Model = new FormCollection(formValuesLookup, form.Files);
|
model = new FormCollection(formValuesLookup, form.Files);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
bindingContext.Model = new FormCollection(new Dictionary<string, string[]>());
|
model = new FormCollection(new Dictionary<string, string[]>());
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new ModelBindingResult(model, bindingContext.ModelName, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -18,33 +18,23 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
public class FormFileModelBinder : IModelBinder
|
public class FormFileModelBinder : IModelBinder
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<bool> BindModelAsync([NotNull] ModelBindingContext bindingContext)
|
public async Task<ModelBindingResult> BindModelAsync([NotNull] ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
if (bindingContext.ModelType == typeof(IFormFile))
|
if (bindingContext.ModelType == typeof(IFormFile))
|
||||||
{
|
{
|
||||||
var postedFiles = await GetFormFilesAsync(bindingContext);
|
var postedFiles = await GetFormFilesAsync(bindingContext);
|
||||||
var value = postedFiles.FirstOrDefault();
|
var value = postedFiles.FirstOrDefault();
|
||||||
if (value != null)
|
return new ModelBindingResult(value, bindingContext.ModelName, value != null);
|
||||||
{
|
|
||||||
bindingContext.Model = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
else if (typeof(IEnumerable<IFormFile>).GetTypeInfo().IsAssignableFrom(
|
else if (typeof(IEnumerable<IFormFile>).GetTypeInfo().IsAssignableFrom(
|
||||||
bindingContext.ModelType.GetTypeInfo()))
|
bindingContext.ModelType.GetTypeInfo()))
|
||||||
{
|
{
|
||||||
var postedFiles = await GetFormFilesAsync(bindingContext);
|
var postedFiles = await GetFormFilesAsync(bindingContext);
|
||||||
var value = ModelBindingHelper.ConvertValuesToCollectionType(bindingContext.ModelType, postedFiles);
|
var value = ModelBindingHelper.ConvertValuesToCollectionType(bindingContext.ModelType, postedFiles);
|
||||||
if (value != null)
|
return new ModelBindingResult(value, bindingContext.ModelName, value != null);
|
||||||
{
|
|
||||||
bindingContext.Model = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<IFormFile>> GetFormFilesAsync(ModelBindingContext bindingContext)
|
private async Task<List<IFormFile>> GetFormFilesAsync(ModelBindingContext bindingContext)
|
||||||
|
|
|
||||||
|
|
@ -12,20 +12,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
public class GenericModelBinder : IModelBinder
|
public class GenericModelBinder : IModelBinder
|
||||||
{
|
{
|
||||||
public async Task<bool> BindModelAsync(ModelBindingContext bindingContext)
|
public async Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
var binderType = ResolveBinderType(bindingContext.ModelType);
|
var binderType = ResolveBinderType(bindingContext.ModelType);
|
||||||
if (binderType != null)
|
if (binderType != null)
|
||||||
{
|
{
|
||||||
var binder = (IModelBinder)Activator.CreateInstance(binderType);
|
var binder = (IModelBinder)Activator.CreateInstance(binderType);
|
||||||
await binder.BindModelAsync(bindingContext);
|
var result = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
|
var modelBindingResult = result != null ?
|
||||||
|
new ModelBindingResult(result.Model, result.Key, result.IsModelSet) :
|
||||||
|
new ModelBindingResult(null, bindingContext.ModelName, false);
|
||||||
|
|
||||||
// Was able to resolve a binder type, hence we should tell the model binding system to return
|
// Was able to resolve a binder type, hence we should tell the model binding system to return
|
||||||
// true so that none of the other model binders participate.
|
// true so that none of the other model binders participate.
|
||||||
return true;
|
return modelBindingResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Type ResolveBinderType(Type modelType)
|
private static Type ResolveBinderType(Type modelType)
|
||||||
|
|
|
||||||
|
|
@ -23,19 +23,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override Task BindModelCoreAsync([NotNull] ModelBindingContext bindingContext)
|
protected override Task<ModelBindingResult> BindModelCoreAsync([NotNull] ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
var request = bindingContext.OperationBindingContext.HttpContext.Request;
|
var request = bindingContext.OperationBindingContext.HttpContext.Request;
|
||||||
var modelMetadata = bindingContext.ModelMetadata;
|
var modelMetadata = bindingContext.ModelMetadata;
|
||||||
|
|
||||||
// Property name can be null if the model metadata represents a type (rahter than a property or parameter).
|
// Property name can be null if the model metadata represents a type (rahter than a property or parameter).
|
||||||
var headerName = modelMetadata.BinderModelName ?? modelMetadata.PropertyName ?? bindingContext.ModelName;
|
var headerName = modelMetadata.BinderModelName ?? modelMetadata.PropertyName ?? bindingContext.ModelName;
|
||||||
|
object model = null;
|
||||||
if (bindingContext.ModelType == typeof(string))
|
if (bindingContext.ModelType == typeof(string))
|
||||||
{
|
{
|
||||||
var value = request.Headers.Get(headerName);
|
var value = request.Headers.Get(headerName);
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
bindingContext.Model = value;
|
model = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (typeof(IEnumerable<string>).GetTypeInfo().IsAssignableFrom(
|
else if (typeof(IEnumerable<string>).GetTypeInfo().IsAssignableFrom(
|
||||||
|
|
@ -44,14 +45,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var values = request.Headers.GetCommaSeparatedValues(headerName);
|
var values = request.Headers.GetCommaSeparatedValues(headerName);
|
||||||
if (values != null)
|
if (values != null)
|
||||||
{
|
{
|
||||||
bindingContext.Model = ModelBindingHelper.ConvertValuesToCollectionType(
|
model = ModelBindingHelper.ConvertValuesToCollectionType(
|
||||||
bindingContext.ModelType,
|
bindingContext.ModelType,
|
||||||
values);
|
values);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always return true as header model binder is supposed to always handle IHeaderBinderMetadata.
|
return Task.FromResult(new ModelBindingResult(model, bindingContext.ModelName, model != null));
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -14,7 +14,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
/// Async function to bind to a particular model.
|
/// Async function to bind to a particular model.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="bindingContext">The binding context which has the object to be bound.</param>
|
/// <param name="bindingContext">The binding context which has the object to be bound.</param>
|
||||||
/// <returns>A Task with a bool implying the success or failure of the operation.</returns>
|
/// <returns>A Task which on completion returns a <see cref="ModelBindingResult"/> which represents the result
|
||||||
Task<bool> BindModelAsync(ModelBindingContext bindingContext);
|
/// of the model binding process.
|
||||||
|
/// </returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// A <c>null</c> return value means that this model binder was not able to handle the request.
|
||||||
|
/// Returning <c>null</c> ensures that subsequent model binders are run. If a non <c>null</c> value indicates
|
||||||
|
/// that the model binder was able to handle the request.
|
||||||
|
/// </remarks>
|
||||||
|
Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
|
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
|
||||||
|
|
||||||
|
|
@ -9,23 +10,39 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
public sealed class KeyValuePairModelBinder<TKey, TValue> : IModelBinder
|
public sealed class KeyValuePairModelBinder<TKey, TValue> : IModelBinder
|
||||||
{
|
{
|
||||||
public async Task<bool> BindModelAsync(ModelBindingContext bindingContext)
|
public async Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
ModelBindingHelper.ValidateBindingContext(bindingContext,
|
ModelBindingHelper.ValidateBindingContext(bindingContext,
|
||||||
typeof(KeyValuePair<TKey, TValue>),
|
typeof(KeyValuePair<TKey, TValue>),
|
||||||
allowNullModel: true);
|
allowNullModel: true);
|
||||||
|
|
||||||
var keyResult = await TryBindStrongModel<TKey>(bindingContext, "key");
|
var keyResult = await TryBindStrongModel<TKey>(bindingContext, "Key");
|
||||||
var valueResult = await TryBindStrongModel<TValue>(bindingContext, "value");
|
var valueResult = await TryBindStrongModel<TValue>(bindingContext, "Value");
|
||||||
|
var model = bindingContext.ModelMetadata.Model;
|
||||||
if (keyResult.Success && valueResult.Success)
|
var isModelSet = false;
|
||||||
|
if (keyResult.IsModelSet && valueResult.IsModelSet)
|
||||||
{
|
{
|
||||||
bindingContext.Model = new KeyValuePair<TKey, TValue>(keyResult.Model, valueResult.Model);
|
model = new KeyValuePair<TKey, TValue>(
|
||||||
|
ModelBindingHelper.CastOrDefault<TKey>(keyResult.Model),
|
||||||
|
ModelBindingHelper.CastOrDefault<TValue>(valueResult.Model));
|
||||||
|
isModelSet = true;
|
||||||
}
|
}
|
||||||
return keyResult.Success || valueResult.Success;
|
else if (!keyResult.IsModelSet && valueResult.IsModelSet)
|
||||||
|
{
|
||||||
|
bindingContext.ModelState.TryAddModelError(keyResult.Key,
|
||||||
|
Resources.KeyValuePair_BothKeyAndValueMustBePresent);
|
||||||
|
}
|
||||||
|
else if (keyResult.IsModelSet && !valueResult.IsModelSet)
|
||||||
|
{
|
||||||
|
bindingContext.ModelState.TryAddModelError(valueResult.Key,
|
||||||
|
Resources.KeyValuePair_BothKeyAndValueMustBePresent);
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = new ModelBindingResult(model, bindingContext.ModelName, isModelSet);
|
||||||
|
return (keyResult.IsModelSet || valueResult.IsModelSet) ? result : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<BindResult<TModel>> TryBindStrongModel<TModel>(ModelBindingContext parentBindingContext,
|
internal async Task<ModelBindingResult> TryBindStrongModel<TModel>(ModelBindingContext parentBindingContext,
|
||||||
string propertyName)
|
string propertyName)
|
||||||
{
|
{
|
||||||
var propertyModelMetadata =
|
var propertyModelMetadata =
|
||||||
|
|
@ -35,29 +52,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
ModelBindingHelper.CreatePropertyModelName(parentBindingContext.ModelName, propertyName);
|
ModelBindingHelper.CreatePropertyModelName(parentBindingContext.ModelName, propertyName);
|
||||||
var propertyBindingContext =
|
var propertyBindingContext =
|
||||||
new ModelBindingContext(parentBindingContext, propertyModelName, propertyModelMetadata);
|
new ModelBindingContext(parentBindingContext, propertyModelName, propertyModelMetadata);
|
||||||
|
var modelBindingResult =
|
||||||
if (await propertyBindingContext.OperationBindingContext.ModelBinder.BindModelAsync(propertyBindingContext))
|
await propertyBindingContext.OperationBindingContext.ModelBinder.BindModelAsync(propertyBindingContext);
|
||||||
|
if (modelBindingResult != null)
|
||||||
{
|
{
|
||||||
var untypedModel = propertyBindingContext.Model;
|
return modelBindingResult;
|
||||||
var model = ModelBindingHelper.CastOrDefault<TModel>(untypedModel);
|
|
||||||
parentBindingContext.ValidationNode.ChildNodes.Add(propertyBindingContext.ValidationNode);
|
|
||||||
return new BindResult<TModel>(success: true, model: model);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new BindResult<TModel>(success: false, model: default(TModel));
|
return new ModelBindingResult(model: default(TModel), key: propertyModelName, isModelSet: false);
|
||||||
}
|
|
||||||
|
|
||||||
internal sealed class BindResult<TModel>
|
|
||||||
{
|
|
||||||
public BindResult(bool success, TModel model)
|
|
||||||
{
|
|
||||||
Success = success;
|
|
||||||
Model = model;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Success { get; private set; }
|
|
||||||
|
|
||||||
public TModel Model { get; private set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
public class MutableObjectModelBinder : IModelBinder
|
public class MutableObjectModelBinder : IModelBinder
|
||||||
{
|
{
|
||||||
public virtual async Task<bool> BindModelAsync(ModelBindingContext bindingContext)
|
public virtual async Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
ModelBindingHelper.ValidateBindingContext(bindingContext);
|
ModelBindingHelper.ValidateBindingContext(bindingContext);
|
||||||
if (!CanBindType(bindingContext.ModelType))
|
if (!CanBindType(bindingContext.ModelType))
|
||||||
{
|
{
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var mutableObjectBinderContext = new MutableObjectBinderContext()
|
var mutableObjectBinderContext = new MutableObjectBinderContext()
|
||||||
|
|
@ -30,17 +30,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
if (!(await CanCreateModel(mutableObjectBinderContext)))
|
if (!(await CanCreateModel(mutableObjectBinderContext)))
|
||||||
{
|
{
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
EnsureModel(bindingContext);
|
EnsureModel(bindingContext);
|
||||||
var dto = await CreateAndPopulateDto(bindingContext, mutableObjectBinderContext.PropertyMetadata);
|
var result = await CreateAndPopulateDto(bindingContext, mutableObjectBinderContext.PropertyMetadata);
|
||||||
|
|
||||||
// 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, (ComplexModelDto)result.Model);
|
||||||
// complex models require full validation
|
return
|
||||||
bindingContext.ValidationNode.ValidateAllProperties = true;
|
new ModelBindingResult(bindingContext.ModelMetadata.Model, bindingContext.ModelName, isModelSet: true);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual bool CanUpdateProperty(ModelMetadata propertyMetadata)
|
protected virtual bool CanUpdateProperty(ModelMetadata propertyMetadata)
|
||||||
|
|
@ -236,7 +235,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<ComplexModelDto> CreateAndPopulateDto(ModelBindingContext bindingContext,
|
private async Task<ModelBindingResult> CreateAndPopulateDto(ModelBindingContext bindingContext,
|
||||||
IEnumerable<ModelMetadata> propertyMetadatas)
|
IEnumerable<ModelMetadata> propertyMetadatas)
|
||||||
{
|
{
|
||||||
// create a DTO and call into the DTO binder
|
// create a DTO and call into the DTO binder
|
||||||
|
|
@ -247,8 +246,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var dtoBindingContext =
|
var dtoBindingContext =
|
||||||
new ModelBindingContext(bindingContext, bindingContext.ModelName, complexModelDtoMetadata);
|
new ModelBindingContext(bindingContext, bindingContext.ModelName, complexModelDtoMetadata);
|
||||||
|
|
||||||
await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(dtoBindingContext);
|
return await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(dtoBindingContext);
|
||||||
return (ComplexModelDto)dtoBindingContext.Model;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual object CreateModel(ModelBindingContext bindingContext)
|
protected virtual object CreateModel(ModelBindingContext bindingContext)
|
||||||
|
|
@ -258,36 +256,11 @@ 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.
|
|
||||||
internal static EventHandler<ModelValidatedEventArgs> CreateNullCheckFailedHandler(ModelMetadata modelMetadata,
|
|
||||||
object incomingValue)
|
|
||||||
{
|
|
||||||
return (sender, e) =>
|
|
||||||
{
|
|
||||||
var validationNode = (ModelValidationNode)sender;
|
|
||||||
var modelState = e.ValidationContext.ModelState;
|
|
||||||
var validationState = modelState.GetFieldValidationState(validationNode.ModelStateKey);
|
|
||||||
|
|
||||||
if (validationState == ModelValidationState.Unvalidated)
|
|
||||||
{
|
|
||||||
// TODO: https://github.com/aspnet/Mvc/issues/450 Revive ModelBinderConfig
|
|
||||||
// var errorMessage = ModelBinderConfig.ValueRequiredErrorMessageProvider(e.ValidationContext,
|
|
||||||
// modelMetadata,
|
|
||||||
// incomingValue);
|
|
||||||
var errorMessage = Resources.ModelBinderConfig_ValueRequired;
|
|
||||||
if (errorMessage != null)
|
|
||||||
{
|
|
||||||
modelState.TryAddModelError(validationNode.ModelStateKey, errorMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void EnsureModel(ModelBindingContext bindingContext)
|
protected virtual void EnsureModel(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
if (bindingContext.Model == null)
|
if (bindingContext.ModelMetadata.Model == null)
|
||||||
{
|
{
|
||||||
bindingContext.Model = CreateModel(bindingContext);
|
bindingContext.ModelMetadata.Model = CreateModel(bindingContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -378,14 +351,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var validationInfo = GetPropertyValidationInfo(bindingContext);
|
var validationInfo = GetPropertyValidationInfo(bindingContext);
|
||||||
|
|
||||||
// Eliminate provided properties from requiredProperties; leaving just *missing* required properties.
|
// Eliminate provided properties from requiredProperties; leaving just *missing* required properties.
|
||||||
var boundProperties = dto.Results.Where(p => p.Value.IsModelBound).Select(p => p.Key.PropertyName);
|
var boundProperties = dto.Results.Where(p => p.Value.IsModelSet).Select(p => p.Key.PropertyName);
|
||||||
validationInfo.RequiredProperties.ExceptWith(boundProperties);
|
validationInfo.RequiredProperties.ExceptWith(boundProperties);
|
||||||
|
|
||||||
foreach (var missingRequiredProperty in validationInfo.RequiredProperties)
|
foreach (var missingRequiredProperty in validationInfo.RequiredProperties)
|
||||||
{
|
{
|
||||||
var addedError = false;
|
var addedError = false;
|
||||||
var modelStateKey = ModelBindingHelper.CreatePropertyModelName(
|
var modelStateKey = ModelBindingHelper.CreatePropertyModelName(
|
||||||
bindingContext.ValidationNode.ModelStateKey, missingRequiredProperty);
|
bindingContext.ModelName, missingRequiredProperty);
|
||||||
|
|
||||||
// Update Model as SetProperty() would: Place null value where validator will check for non-null. This
|
// 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.
|
// ensures a failure result from a required validator (if any) even for a non-nullable property.
|
||||||
|
|
@ -421,14 +394,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
validationInfo.RequiredValidators.TryGetValue(propertyMetadata.PropertyName,
|
validationInfo.RequiredValidators.TryGetValue(propertyMetadata.PropertyName,
|
||||||
out requiredValidator);
|
out requiredValidator);
|
||||||
SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||||
bindingContext.ValidationNode.ChildNodes.Add(dtoResult.ValidationNode);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void SetProperty(ModelBindingContext bindingContext,
|
protected virtual void SetProperty(ModelBindingContext bindingContext,
|
||||||
ModelMetadata propertyMetadata,
|
ModelMetadata propertyMetadata,
|
||||||
ComplexModelDtoResult dtoResult,
|
ModelBindingResult dtoResult,
|
||||||
IModelValidator requiredValidator)
|
IModelValidator requiredValidator)
|
||||||
{
|
{
|
||||||
var bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase;
|
var bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase;
|
||||||
|
|
@ -443,7 +415,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
object value;
|
object value;
|
||||||
var hasDefaultValue = false;
|
var hasDefaultValue = false;
|
||||||
if (dtoResult.IsModelBound)
|
if (dtoResult.IsModelSet)
|
||||||
{
|
{
|
||||||
value = dtoResult.Model;
|
value = dtoResult.Model;
|
||||||
}
|
}
|
||||||
|
|
@ -458,7 +430,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
// the property setters throw, e.g. if we're setting entity keys to null.
|
// the property setters throw, e.g. if we're setting entity keys to null.
|
||||||
if (value == null)
|
if (value == null)
|
||||||
{
|
{
|
||||||
var modelStateKey = dtoResult.ValidationNode.ModelStateKey;
|
var modelStateKey = dtoResult.Key;
|
||||||
var validationState = bindingContext.ModelState.GetFieldValidationState(modelStateKey);
|
var validationState = bindingContext.ModelState.GetFieldValidationState(modelStateKey);
|
||||||
if (validationState == ModelValidationState.Unvalidated)
|
if (validationState == ModelValidationState.Unvalidated)
|
||||||
{
|
{
|
||||||
|
|
@ -473,7 +445,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dtoResult.IsModelBound && !hasDefaultValue)
|
if (!dtoResult.IsModelSet && !hasDefaultValue)
|
||||||
{
|
{
|
||||||
// If we don't have a value, don't set it on the model and trounce a pre-initialized
|
// If we don't have a value, don't set it on the model and trounce a pre-initialized
|
||||||
// value.
|
// value.
|
||||||
|
|
@ -484,7 +456,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
property.SetValue(bindingContext.Model, value);
|
property.SetValue(bindingContext.ModelMetadata.Model, value);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -495,7 +467,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
ex = targetInvocationException.InnerException;
|
ex = targetInvocationException.InnerException;
|
||||||
}
|
}
|
||||||
var modelStateKey = dtoResult.ValidationNode.ModelStateKey;
|
var modelStateKey = dtoResult.Key;
|
||||||
var validationState = bindingContext.ModelState.GetFieldValidationState(modelStateKey);
|
var validationState = bindingContext.ModelState.GetFieldValidationState(modelStateKey);
|
||||||
if (validationState == ModelValidationState.Unvalidated)
|
if (validationState == ModelValidationState.Unvalidated)
|
||||||
{
|
{
|
||||||
|
|
@ -506,11 +478,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
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
|
||||||
var modelStateKey = dtoResult.ValidationNode.ModelStateKey;
|
var modelStateKey = dtoResult.Key;
|
||||||
var validationState = bindingContext.ModelState.GetFieldValidationState(modelStateKey);
|
var validationState = bindingContext.ModelState.GetFieldValidationState(modelStateKey);
|
||||||
if (validationState == ModelValidationState.Unvalidated)
|
if (validationState == ModelValidationState.Unvalidated)
|
||||||
{
|
{
|
||||||
dtoResult.ValidationNode.Validated += CreateNullCheckFailedHandler(propertyMetadata, value);
|
var errorMessage = Resources.ModelBinderConfig_ValueRequired;
|
||||||
|
bindingContext.ModelState.TryAddModelError(modelStateKey, errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,20 +9,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
public sealed class TypeConverterModelBinder : IModelBinder
|
public sealed class TypeConverterModelBinder : IModelBinder
|
||||||
{
|
{
|
||||||
public async Task<bool> BindModelAsync(ModelBindingContext bindingContext)
|
public async Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
ModelBindingHelper.ValidateBindingContext(bindingContext);
|
ModelBindingHelper.ValidateBindingContext(bindingContext);
|
||||||
|
|
||||||
if (!TypeHelper.HasStringConverter(bindingContext.ModelType))
|
if (!TypeHelper.HasStringConverter(bindingContext.ModelType))
|
||||||
{
|
{
|
||||||
// this type cannot be converted
|
// this type cannot be converted
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var valueProviderResult = await bindingContext.ValueProvider.GetValueAsync(bindingContext.ModelName);
|
var valueProviderResult = await bindingContext.ValueProvider.GetValueAsync(bindingContext.ModelName);
|
||||||
if (valueProviderResult == null)
|
if (valueProviderResult == null)
|
||||||
{
|
{
|
||||||
return false; // no entry
|
return null; // no entry
|
||||||
}
|
}
|
||||||
|
|
||||||
object newModel;
|
object newModel;
|
||||||
|
|
@ -31,14 +31,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
newModel = valueProviderResult.ConvertTo(bindingContext.ModelType);
|
newModel = valueProviderResult.ConvertTo(bindingContext.ModelType);
|
||||||
ModelBindingHelper.ReplaceEmptyStringWithNull(bindingContext.ModelMetadata, ref newModel);
|
ModelBindingHelper.ReplaceEmptyStringWithNull(bindingContext.ModelMetadata, ref newModel);
|
||||||
bindingContext.Model = newModel;
|
return new ModelBindingResult(newModel, bindingContext.ModelName, true);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, ex);
|
bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new ModelBindingResult(null, bindingContext.ModelName, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,21 +8,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
public sealed class TypeMatchModelBinder : IModelBinder
|
public sealed class TypeMatchModelBinder : IModelBinder
|
||||||
{
|
{
|
||||||
public async Task<bool> BindModelAsync(ModelBindingContext bindingContext)
|
public async Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
var valueProviderResult = await GetCompatibleValueProviderResult(bindingContext);
|
var valueProviderResult = await GetCompatibleValueProviderResult(bindingContext);
|
||||||
if (valueProviderResult == null)
|
if (valueProviderResult == null)
|
||||||
{
|
{
|
||||||
// conversion would have failed
|
// conversion would have failed
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
|
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
|
||||||
var model = valueProviderResult.RawValue;
|
var model = valueProviderResult.RawValue;
|
||||||
ModelBindingHelper.ReplaceEmptyStringWithNull(bindingContext.ModelMetadata, ref model);
|
ModelBindingHelper.ReplaceEmptyStringWithNull(bindingContext.ModelMetadata, ref model);
|
||||||
bindingContext.Model = model;
|
return new ModelBindingResult(model, bindingContext.ModelName, true);
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static async Task<ValueProviderResult> GetCompatibleValueProviderResult(ModelBindingContext context)
|
internal static async Task<ValueProviderResult> GetCompatibleValueProviderResult(ModelBindingContext context)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides methods to validate an object graph.
|
||||||
|
/// </summary>
|
||||||
|
public interface IObjectModelValidator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Validates the given model in <see cref="ModelValidationContext.ModelMetadata"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="validationContext">The <see cref="ModelValidationContext"/> associated with the current call.
|
||||||
|
/// </param>
|
||||||
|
void Validate(ModelValidationContext validationContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,25 +19,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Internal
|
||||||
indexNames = indexes;
|
indexNames = indexes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return indexNames;
|
return indexNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void CreateOrReplaceCollection<TElement>(ModelBindingContext bindingContext,
|
|
||||||
IEnumerable<TElement> incomingElements,
|
|
||||||
Func<ICollection<TElement>> creator)
|
|
||||||
{
|
|
||||||
var collection = bindingContext.Model as ICollection<TElement>;
|
|
||||||
if (collection == null || collection.IsReadOnly)
|
|
||||||
{
|
|
||||||
collection = creator();
|
|
||||||
bindingContext.Model = collection;
|
|
||||||
}
|
|
||||||
|
|
||||||
collection.Clear();
|
|
||||||
foreach (var element in incomingElements)
|
|
||||||
{
|
|
||||||
collection.Add(element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,17 +80,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Internal
|
||||||
throw new ArgumentException(message, "bindingContext");
|
throw new ArgumentException(message, "bindingContext");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!allowNullModel && bindingContext.Model == null)
|
if (!allowNullModel && bindingContext.ModelMetadata.Model == null)
|
||||||
{
|
{
|
||||||
var message = Resources.FormatModelBinderUtil_ModelCannotBeNull(requiredType);
|
var message = Resources.FormatModelBinderUtil_ModelCannotBeNull(requiredType);
|
||||||
throw new ArgumentException(message, "bindingContext");
|
throw new ArgumentException(message, "bindingContext");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bindingContext.Model != null &&
|
if (bindingContext.ModelMetadata.Model != null &&
|
||||||
!bindingContext.ModelType.GetTypeInfo().IsAssignableFrom(requiredType.GetTypeInfo()))
|
!bindingContext.ModelType.GetTypeInfo().IsAssignableFrom(requiredType.GetTypeInfo()))
|
||||||
{
|
{
|
||||||
var message = Resources.FormatModelBinderUtil_ModelInstanceIsWrong(
|
var message = Resources.FormatModelBinderUtil_ModelInstanceIsWrong(
|
||||||
bindingContext.Model.GetType(),
|
bindingContext.ModelMetadata.Model.GetType(),
|
||||||
requiredType);
|
requiredType);
|
||||||
throw new ArgumentException(message, "bindingContext");
|
throw new ArgumentException(message, "bindingContext");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
private string _modelName;
|
private string _modelName;
|
||||||
private ModelStateDictionary _modelState;
|
private ModelStateDictionary _modelState;
|
||||||
private ModelValidationNode _validationNode;
|
|
||||||
private Func<ModelBindingContext, string, bool> _propertyFilter;
|
private Func<ModelBindingContext, string, bool> _propertyFilter;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -55,36 +54,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public OperationBindingContext OperationBindingContext { get; set; }
|
public OperationBindingContext OperationBindingContext { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the model associated with this context.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// The <see cref="ModelMetadata"/> property must be set to access this property.
|
|
||||||
/// </remarks>
|
|
||||||
public object Model
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
EnsureModelMetadata();
|
|
||||||
return ModelMetadata.Model;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
IsModelSet = true;
|
|
||||||
|
|
||||||
EnsureModelMetadata();
|
|
||||||
ModelMetadata.Model = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not the <see cref="Model"/> value has been set.
|
|
||||||
///
|
|
||||||
/// This property can be used to distinguish between a model binder which does not find a value and
|
|
||||||
/// the case where a model binder sets the <c>null</c> value.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsModelSet { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the metadata for the model associated with this context.
|
/// Gets or sets the metadata for the model associated with this context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -164,23 +133,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
set { _propertyFilter = value; }
|
set { _propertyFilter = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the <see cref="ModelValidationNode"/> instance used as a container for
|
|
||||||
/// validation information.
|
|
||||||
/// </summary>
|
|
||||||
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)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains the result of model binding.
|
||||||
|
/// </summary>
|
||||||
|
public class ModelBindingResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="ModelBindingResult"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="model">The model which was created by the <see cref="IModelBinder"/>.</param>
|
||||||
|
/// <param name="key">The key using which was used to attempt binding the model.</param>
|
||||||
|
/// <param name="isModelSet">A value that represents if the model has been set by the
|
||||||
|
/// <see cref="IModelBinder"/>.</param>
|
||||||
|
public ModelBindingResult(object model, string key, bool isModelSet)
|
||||||
|
{
|
||||||
|
Model = model;
|
||||||
|
Key = key;
|
||||||
|
IsModelSet = isModelSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the model associated with this context.
|
||||||
|
/// </summary>
|
||||||
|
public object Model { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the model name which was used to bind the model.
|
||||||
|
///
|
||||||
|
/// This property can be used during validation to add model state for a bound model.
|
||||||
|
/// </summary>
|
||||||
|
public string Key { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not the <see cref="Model"/> value has been set.
|
||||||
|
///
|
||||||
|
/// This property can be used to distinguish between a model binder which does not find a value and
|
||||||
|
/// the case where a model binder sets the <c>null</c> value.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsModelSet { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -132,10 +132,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
get { return _innerDictionary.Values; }
|
get { return _innerDictionary.Values; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
|
/// Gets a value that indicates whether any model state values in this model state dictionary is invalid or not validated.
|
||||||
|
/// </summary>
|
||||||
public bool IsValid
|
public bool IsValid
|
||||||
{
|
{
|
||||||
get { return ValidationState == ModelValidationState.Valid; }
|
get
|
||||||
|
{
|
||||||
|
return ValidationState == ModelValidationState.Valid || ValidationState == ModelValidationState.Skipped;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -284,6 +289,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
return GetValidity(entries);
|
return GetValidity(entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns <see cref="ModelValidationState"/> for the <paramref name="key"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The key to look up model state errors for.</param>
|
||||||
|
/// <returns>Returns <see cref="ModelValidationState.Unvalidated"/> if no entry is found for the specified
|
||||||
|
/// key, <see cref="ModelValidationState.Invalid"/> if an instance is found with one or more model
|
||||||
|
/// state errors; <see cref="ModelValidationState.Valid"/> otherwise.</returns>
|
||||||
|
public ModelValidationState GetValidationState([NotNull] string key)
|
||||||
|
{
|
||||||
|
ModelState validationState;
|
||||||
|
if (TryGetValue(key, out validationState))
|
||||||
|
{
|
||||||
|
return validationState.ValidationState;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ModelValidationState.Unvalidated;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Marks the <see cref="ModelState.ValidationState"/> for the entry with the specified <paramref name="key"/>
|
/// Marks the <see cref="ModelState.ValidationState"/> for the entry with the specified <paramref name="key"/>
|
||||||
/// as <see cref="ModelValidationState.Valid"/>.
|
/// as <see cref="ModelValidationState.Valid"/>.
|
||||||
|
|
@ -300,6 +323,22 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
modelState.ValidationState = ModelValidationState.Valid;
|
modelState.ValidationState = ModelValidationState.Valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks the <see cref="ModelState.ValidationState"/> for the entry with the specified <paramref name="key"/>
|
||||||
|
/// as <see cref="ModelValidationState.Skipped"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The key of the <see cref="ModelState"/> to mark as skipped.</param>
|
||||||
|
public void MarkFieldSkipped([NotNull] string key)
|
||||||
|
{
|
||||||
|
var modelState = GetModelStateForKey(key);
|
||||||
|
if (modelState.ValidationState == ModelValidationState.Invalid)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(Resources.Validation_InvalidFieldCannotBeReset_ToSkipped);
|
||||||
|
}
|
||||||
|
|
||||||
|
modelState.ValidationState = ModelValidationState.Skipped;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Copies the values from the specified <paramref name="dictionary"/> into this instance, overwriting
|
/// Copies the values from the specified <paramref name="dictionary"/> into this instance, overwriting
|
||||||
/// existing values if keys are the same.
|
/// existing values if keys are the same.
|
||||||
|
|
|
||||||
|
|
@ -8,5 +8,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
Unvalidated,
|
Unvalidated,
|
||||||
Invalid,
|
Invalid,
|
||||||
Valid,
|
Valid,
|
||||||
|
Skipped
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -42,6 +42,22 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
return GetString("ArgumentCannotBeNullOrEmpty");
|
return GetString("ArgumentCannotBeNullOrEmpty");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A value is required.
|
||||||
|
/// </summary>
|
||||||
|
internal static string KeyValuePair_BothKeyAndValueMustBePresent
|
||||||
|
{
|
||||||
|
get { return GetString("KeyValuePair_BothKeyAndValueMustBePresent"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A value is required.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatKeyValuePair_BothKeyAndValueMustBePresent()
|
||||||
|
{
|
||||||
|
return GetString("KeyValuePair_BothKeyAndValueMustBePresent");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The property {0}.{1} could not be found.
|
/// The property {0}.{1} could not be found.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -330,6 +346,22 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
return GetString("Validation_InvalidFieldCannotBeReset");
|
return GetString("Validation_InvalidFieldCannotBeReset");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A field previously marked invalid should not be marked skipped.
|
||||||
|
/// </summary>
|
||||||
|
internal static string Validation_InvalidFieldCannotBeReset_ToSkipped
|
||||||
|
{
|
||||||
|
get { return GetString("Validation_InvalidFieldCannotBeReset_ToSkipped"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A field previously marked invalid should not be marked skipped.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatValidation_InvalidFieldCannotBeReset_ToSkipped()
|
||||||
|
{
|
||||||
|
return GetString("Validation_InvalidFieldCannotBeReset_ToSkipped");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A value is required but was not present in the request.
|
/// A value is required but was not present in the request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,9 @@
|
||||||
<data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve">
|
<data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve">
|
||||||
<value>Value cannot be null or empty.</value>
|
<value>Value cannot be null or empty.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="KeyValuePair_BothKeyAndValueMustBePresent" xml:space="preserve">
|
||||||
|
<value>A value is required.</value>
|
||||||
|
</data>
|
||||||
<data name="Common_PropertyNotFound" xml:space="preserve">
|
<data name="Common_PropertyNotFound" xml:space="preserve">
|
||||||
<value>The property {0}.{1} could not be found.</value>
|
<value>The property {0}.{1} could not be found.</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
@ -177,6 +180,9 @@
|
||||||
<data name="Validation_InvalidFieldCannotBeReset" xml:space="preserve">
|
<data name="Validation_InvalidFieldCannotBeReset" xml:space="preserve">
|
||||||
<value>A field previously marked invalid should not be marked valid.</value>
|
<value>A field previously marked invalid should not be marked valid.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Validation_InvalidFieldCannotBeReset_ToSkipped" xml:space="preserve">
|
||||||
|
<value>A field previously marked invalid should not be marked skipped.</value>
|
||||||
|
</data>
|
||||||
<data name="Validation_ValueNotFound" xml:space="preserve">
|
<data name="Validation_ValueNotFound" xml:space="preserve">
|
||||||
<value>A value is required but was not present in the request.</value>
|
<value>A value is required but was not present in the request.</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
foreach (var actualType in actualTypes)
|
foreach (var actualType in actualTypes)
|
||||||
{
|
{
|
||||||
var underlyingType = Nullable.GetUnderlyingType(actualType) ?? actualType;
|
var underlyingType = Nullable.GetUnderlyingType(actualType) ?? actualType;
|
||||||
|
|
||||||
if (!IsSimpleType(underlyingType))
|
if (!IsSimpleType(underlyingType))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -1,264 +0,0 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Recursively validate an object.
|
|
||||||
/// </summary>
|
|
||||||
public class DefaultBodyModelValidator : IBodyModelValidator
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool Validate(
|
|
||||||
[NotNull] ModelValidationContext modelValidationContext,
|
|
||||||
string keyPrefix)
|
|
||||||
{
|
|
||||||
var metadata = modelValidationContext.ModelMetadata;
|
|
||||||
var validationContext = new ValidationContext()
|
|
||||||
{
|
|
||||||
ModelValidationContext = modelValidationContext,
|
|
||||||
Visited = new HashSet<object>(ReferenceEqualityComparer.Instance),
|
|
||||||
KeyBuilders = new Stack<IKeyBuilder>(),
|
|
||||||
RootPrefix = keyPrefix
|
|
||||||
};
|
|
||||||
|
|
||||||
return ValidateNonVisitedNodeAndChildren(metadata, validationContext, validators: null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool ValidateNonVisitedNodeAndChildren(
|
|
||||||
ModelMetadata metadata, ValidationContext validationContext, IEnumerable<IModelValidator> validators)
|
|
||||||
{
|
|
||||||
// Recursion guard to avoid stack overflows
|
|
||||||
RuntimeHelpers.EnsureSufficientExecutionStack();
|
|
||||||
|
|
||||||
var isValid = true;
|
|
||||||
if (validators == null)
|
|
||||||
{
|
|
||||||
// The validators are not null in the case of validating an array. Since the validators are
|
|
||||||
// the same for all the elements of the array, we do not do GetValidators for each element,
|
|
||||||
// instead we just pass them over. See ValidateElements function.
|
|
||||||
validators = validationContext.ModelValidationContext.ValidatorProvider.GetValidators(metadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't need to recursively traverse the graph for null values
|
|
||||||
if (metadata.Model == null)
|
|
||||||
{
|
|
||||||
return ShallowValidate(metadata, validationContext, validators);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't need to recursively traverse the graph for types that shouldn't be validated
|
|
||||||
var modelType = metadata.Model.GetType();
|
|
||||||
if (IsTypeExcludedFromValidation(
|
|
||||||
validationContext.ModelValidationContext.ExcludeFromValidationFilters,
|
|
||||||
modelType))
|
|
||||||
{
|
|
||||||
return ShallowValidate(metadata, validationContext, validators);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check to avoid infinite recursion. This can happen with cycles in an object graph.
|
|
||||||
if (validationContext.Visited.Contains(metadata.Model))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
validationContext.Visited.Add(metadata.Model);
|
|
||||||
|
|
||||||
// Validate the children first - depth-first traversal
|
|
||||||
var enumerableModel = metadata.Model as IEnumerable;
|
|
||||||
if (enumerableModel == null)
|
|
||||||
{
|
|
||||||
isValid = ValidateProperties(metadata, validationContext);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
isValid = ValidateElements(enumerableModel, validationContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isValid)
|
|
||||||
{
|
|
||||||
// Don't bother to validate this node if children failed.
|
|
||||||
isValid = ShallowValidate(metadata, validationContext, validators);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pop the object so that it can be validated again in a different path
|
|
||||||
validationContext.Visited.Remove(metadata.Model);
|
|
||||||
|
|
||||||
return isValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool ValidateProperties(ModelMetadata metadata, ValidationContext validationContext)
|
|
||||||
{
|
|
||||||
var isValid = true;
|
|
||||||
var propertyScope = new PropertyScope();
|
|
||||||
validationContext.KeyBuilders.Push(propertyScope);
|
|
||||||
foreach (var childMetadata in
|
|
||||||
validationContext.ModelValidationContext.MetadataProvider.GetMetadataForProperties(
|
|
||||||
metadata.Model, metadata.RealModelType))
|
|
||||||
{
|
|
||||||
propertyScope.PropertyName = childMetadata.PropertyName;
|
|
||||||
if (!ValidateNonVisitedNodeAndChildren(childMetadata, validationContext, validators: null))
|
|
||||||
{
|
|
||||||
isValid = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
validationContext.KeyBuilders.Pop();
|
|
||||||
return isValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool ValidateElements(IEnumerable model, ValidationContext validationContext)
|
|
||||||
{
|
|
||||||
var isValid = true;
|
|
||||||
var elementType = GetElementType(model.GetType());
|
|
||||||
var elementMetadata =
|
|
||||||
validationContext.ModelValidationContext.MetadataProvider.GetMetadataForType(
|
|
||||||
modelAccessor: null, modelType: elementType);
|
|
||||||
|
|
||||||
var elementScope = new ElementScope() { Index = 0 };
|
|
||||||
validationContext.KeyBuilders.Push(elementScope);
|
|
||||||
var validators = validationContext.ModelValidationContext.ValidatorProvider.GetValidators(elementMetadata);
|
|
||||||
|
|
||||||
// If there are no validators or the object is null we bail out quickly
|
|
||||||
// when there are large arrays of null, this will save a significant amount of processing
|
|
||||||
// with minimal impact to other scenarios.
|
|
||||||
var anyValidatorsDefined = validators.Any();
|
|
||||||
|
|
||||||
foreach (var element in model)
|
|
||||||
{
|
|
||||||
// If the element is non null, the recursive calls might find more validators.
|
|
||||||
// If it's null, then a shallow validation will be performed.
|
|
||||||
if (element != null || anyValidatorsDefined)
|
|
||||||
{
|
|
||||||
elementMetadata.Model = element;
|
|
||||||
|
|
||||||
if (!ValidateNonVisitedNodeAndChildren(elementMetadata, validationContext, validators))
|
|
||||||
{
|
|
||||||
isValid = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
elementScope.Index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
validationContext.KeyBuilders.Pop();
|
|
||||||
return isValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validates a single node (not including children)
|
|
||||||
// Returns true if validation passes successfully
|
|
||||||
private static bool ShallowValidate(
|
|
||||||
ModelMetadata metadata,
|
|
||||||
ValidationContext validationContext,
|
|
||||||
[NotNull] IEnumerable<IModelValidator> validators)
|
|
||||||
{
|
|
||||||
var isValid = true;
|
|
||||||
string modelKey = null;
|
|
||||||
|
|
||||||
// When the are no validators we bail quickly. This saves a GetEnumerator allocation.
|
|
||||||
// In a large array (tens of thousands or more) scenario it's very significant.
|
|
||||||
var validatorsAsCollection = validators as ICollection;
|
|
||||||
if (validatorsAsCollection != null && validatorsAsCollection.Count == 0)
|
|
||||||
{
|
|
||||||
return isValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
var modelValidationContext =
|
|
||||||
new ModelValidationContext(validationContext.ModelValidationContext, metadata);
|
|
||||||
foreach (var validator in validators)
|
|
||||||
{
|
|
||||||
foreach (var error in validator.Validate(modelValidationContext))
|
|
||||||
{
|
|
||||||
if (modelKey == null)
|
|
||||||
{
|
|
||||||
modelKey = validationContext.RootPrefix;
|
|
||||||
// This constructs the object heirarchy
|
|
||||||
// Example: prefix.Parent.Child
|
|
||||||
foreach (var keyBuilder in validationContext.KeyBuilders.Reverse())
|
|
||||||
{
|
|
||||||
modelKey = keyBuilder.AppendTo(modelKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var errorKey = ModelBindingHelper.CreatePropertyModelName(modelKey, error.MemberName);
|
|
||||||
validationContext.ModelValidationContext.ModelState.AddModelError(errorKey, error.Message);
|
|
||||||
isValid = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return isValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsTypeExcludedFromValidation(
|
|
||||||
IReadOnlyList<IExcludeTypeValidationFilter> filters, Type type)
|
|
||||||
{
|
|
||||||
// This can be set to null in ModelBinding scenarios which does not flow through this path.
|
|
||||||
if (filters == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return filters.Any(filter => filter.IsTypeExcluded(type));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Type GetElementType(Type type)
|
|
||||||
{
|
|
||||||
Debug.Assert(typeof(IEnumerable).IsAssignableFrom(type));
|
|
||||||
if (type.IsArray)
|
|
||||||
{
|
|
||||||
return type.GetElementType();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var implementedInterface in type.GetInterfaces())
|
|
||||||
{
|
|
||||||
if (implementedInterface.IsGenericType() &&
|
|
||||||
implementedInterface.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
|
||||||
{
|
|
||||||
return implementedInterface.GetGenericArguments()[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return typeof(object);
|
|
||||||
}
|
|
||||||
|
|
||||||
private interface IKeyBuilder
|
|
||||||
{
|
|
||||||
string AppendTo(string prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class PropertyScope : IKeyBuilder
|
|
||||||
{
|
|
||||||
public string PropertyName { get; set; }
|
|
||||||
|
|
||||||
public string AppendTo(string prefix)
|
|
||||||
{
|
|
||||||
return ModelBindingHelper.CreatePropertyModelName(prefix, PropertyName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ElementScope : IKeyBuilder
|
|
||||||
{
|
|
||||||
public int Index { get; set; }
|
|
||||||
|
|
||||||
public string AppendTo(string prefix)
|
|
||||||
{
|
|
||||||
return ModelBindingHelper.CreateIndexModelName(prefix, Index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ValidationContext
|
|
||||||
{
|
|
||||||
public ModelValidationContext ModelValidationContext { get; set; }
|
|
||||||
public HashSet<object> Visited { get; set; }
|
|
||||||
public Stack<IKeyBuilder> KeyBuilders { get; set; }
|
|
||||||
public string RootPrefix { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,299 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Recursively validate an object.
|
||||||
|
/// </summary>
|
||||||
|
public class DefaultObjectValidator : IObjectModelValidator
|
||||||
|
{
|
||||||
|
private readonly IValidationExcludeFiltersProvider _excludeFilterProvider;
|
||||||
|
private readonly IModelMetadataProvider _modelMetadataProvider;
|
||||||
|
|
||||||
|
public DefaultObjectValidator(
|
||||||
|
IValidationExcludeFiltersProvider excludeFilterProvider,
|
||||||
|
IModelMetadataProvider modelMetadataProvider)
|
||||||
|
{
|
||||||
|
_excludeFilterProvider = excludeFilterProvider;
|
||||||
|
_modelMetadataProvider = modelMetadataProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Validate([NotNull] ModelValidationContext modelValidationContext)
|
||||||
|
{
|
||||||
|
var metadata = modelValidationContext.ModelMetadata;
|
||||||
|
var validationContext = new ValidationContext()
|
||||||
|
{
|
||||||
|
ModelValidationContext = modelValidationContext,
|
||||||
|
Visited = new HashSet<object>(ReferenceEqualityComparer.Instance),
|
||||||
|
};
|
||||||
|
|
||||||
|
ValidateNonVisitedNodeAndChildren(
|
||||||
|
modelValidationContext.RootPrefix, metadata, validationContext, validators: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ValidateNonVisitedNodeAndChildren(string modelKey,
|
||||||
|
ModelMetadata metadata, ValidationContext validationContext, IEnumerable<IModelValidator> validators)
|
||||||
|
{
|
||||||
|
// Recursion guard to avoid stack overflows
|
||||||
|
RuntimeHelpers.EnsureSufficientExecutionStack();
|
||||||
|
|
||||||
|
var modelState = validationContext.ModelValidationContext.ModelState;
|
||||||
|
var bindingSourceMetadata = metadata.BinderMetadata as IBindingSourceMetadata;
|
||||||
|
var bindingSource = bindingSourceMetadata?.BindingSource;
|
||||||
|
|
||||||
|
if (bindingSource != null && !bindingSource.IsFromRequest)
|
||||||
|
{
|
||||||
|
// Short circuit if the metadata represents something that was not bound using request data.
|
||||||
|
// For example model bound using [FromServices]. Treat such objects as skipped.
|
||||||
|
var validationState = modelState.GetFieldValidationState(modelKey);
|
||||||
|
if (validationState == ModelValidationState.Unvalidated)
|
||||||
|
{
|
||||||
|
validationContext.ModelValidationContext.ModelState.MarkFieldSkipped(modelKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For validation purposes this model is valid.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modelState.HasReachedMaxErrors)
|
||||||
|
{
|
||||||
|
// Short circuit if max errors have been recorded. In which case we treat this as invalid.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var isValid = true;
|
||||||
|
if (validators == null)
|
||||||
|
{
|
||||||
|
// The validators are not null in the case of validating an array. Since the validators are
|
||||||
|
// the same for all the elements of the array, we do not do GetValidators for each element,
|
||||||
|
// instead we just pass them over. See ValidateElements function.
|
||||||
|
validators = validationContext.ModelValidationContext.ValidatorProvider.GetValidators(metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't need to recursively traverse the graph for null values
|
||||||
|
if (metadata.Model == null)
|
||||||
|
{
|
||||||
|
return ShallowValidate(modelKey, metadata, validationContext, validators);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't need to recursively traverse the graph for types that shouldn't be validated
|
||||||
|
var modelType = metadata.Model.GetType();
|
||||||
|
if (IsTypeExcludedFromValidation(_excludeFilterProvider.ExcludeFilters, modelType))
|
||||||
|
{
|
||||||
|
var result = ShallowValidate(modelKey, metadata, validationContext, validators);
|
||||||
|
MarkPropertiesAsSkipped(modelKey, metadata, validationContext);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to avoid infinite recursion. This can happen with cycles in an object graph.
|
||||||
|
if (validationContext.Visited.Contains(metadata.Model))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
validationContext.Visited.Add(metadata.Model);
|
||||||
|
|
||||||
|
// Validate the children first - depth-first traversal
|
||||||
|
var enumerableModel = metadata.Model as IEnumerable;
|
||||||
|
if (enumerableModel == null)
|
||||||
|
{
|
||||||
|
isValid = ValidateProperties(modelKey, metadata, validationContext);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
isValid = ValidateElements(modelKey, enumerableModel, validationContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isValid)
|
||||||
|
{
|
||||||
|
// Don't bother to validate this node if children failed.
|
||||||
|
isValid = ShallowValidate(modelKey, metadata, validationContext, validators);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop the object so that it can be validated again in a different path
|
||||||
|
validationContext.Visited.Remove(metadata.Model);
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MarkPropertiesAsSkipped(string currentModelKey, ModelMetadata metadata, ValidationContext validationContext)
|
||||||
|
{
|
||||||
|
var modelState = validationContext.ModelValidationContext.ModelState;
|
||||||
|
var fieldValidationState = modelState.GetFieldValidationState(currentModelKey);
|
||||||
|
|
||||||
|
// Since shallow validation is done, if the modelvalidation state is still marked as unvalidated,
|
||||||
|
// it is because some properties in the subtree are marked as unvalidated. Mark all such properties
|
||||||
|
// as skipped. Models which have their subtrees as Valid or Invalid do not need to be marked as skipped.
|
||||||
|
if (fieldValidationState != ModelValidationState.Unvalidated)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var childMetadata in metadata.Properties)
|
||||||
|
{
|
||||||
|
var childKey = ModelBindingHelper.CreatePropertyModelName(currentModelKey, childMetadata.PropertyName);
|
||||||
|
var validationState = modelState.GetFieldValidationState(childKey);
|
||||||
|
|
||||||
|
if (validationState == ModelValidationState.Unvalidated)
|
||||||
|
{
|
||||||
|
validationContext.ModelValidationContext.ModelState.MarkFieldSkipped(childKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ValidateProperties(string currentModelKey, ModelMetadata metadata, ValidationContext validationContext)
|
||||||
|
{
|
||||||
|
var isValid = true;
|
||||||
|
|
||||||
|
foreach (var childMetadata in metadata.Properties)
|
||||||
|
{
|
||||||
|
var childKey = ModelBindingHelper.CreatePropertyModelName(currentModelKey, childMetadata.PropertyName);
|
||||||
|
if (!ValidateNonVisitedNodeAndChildren(childKey, childMetadata, validationContext, validators: null))
|
||||||
|
{
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ValidateElements(string currentKey, IEnumerable model, ValidationContext validationContext)
|
||||||
|
{
|
||||||
|
var elementType = GetElementType(model.GetType());
|
||||||
|
var elementMetadata = _modelMetadataProvider.GetMetadataForType(
|
||||||
|
modelAccessor: null,
|
||||||
|
modelType: elementType);
|
||||||
|
|
||||||
|
var validators = validationContext.ModelValidationContext.ValidatorProvider.GetValidators(elementMetadata);
|
||||||
|
|
||||||
|
// If there are no validators or the object is null we bail out quickly
|
||||||
|
// when there are large arrays of null, this will save a significant amount of processing
|
||||||
|
// with minimal impact to other scenarios.
|
||||||
|
var anyValidatorsDefined = validators.Any();
|
||||||
|
var index = 0;
|
||||||
|
var isValid = true;
|
||||||
|
foreach (var element in model)
|
||||||
|
{
|
||||||
|
// If the element is non null, the recursive calls might find more validators.
|
||||||
|
// If it's null, then a shallow validation will be performed.
|
||||||
|
if (element != null || anyValidatorsDefined)
|
||||||
|
{
|
||||||
|
elementMetadata.Model = element;
|
||||||
|
var elementKey = ModelBindingHelper.CreateIndexModelName(currentKey, index);
|
||||||
|
if (!ValidateNonVisitedNodeAndChildren(elementKey, elementMetadata, validationContext, validators))
|
||||||
|
{
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validates a single node (not including children)
|
||||||
|
// Returns true if validation passes successfully
|
||||||
|
private static bool ShallowValidate(
|
||||||
|
string modelKey,
|
||||||
|
ModelMetadata metadata,
|
||||||
|
ValidationContext validationContext,
|
||||||
|
IEnumerable<IModelValidator> validators)
|
||||||
|
{
|
||||||
|
var isValid = true;
|
||||||
|
|
||||||
|
// When the are no validators we bail quickly. This saves a GetEnumerator allocation.
|
||||||
|
// In a large array (tens of thousands or more) scenario it's very significant.
|
||||||
|
var validatorsAsCollection = validators as ICollection;
|
||||||
|
if (validatorsAsCollection == null || validatorsAsCollection.Count > 0)
|
||||||
|
{
|
||||||
|
var modelValidationContext =
|
||||||
|
new ModelValidationContext(validationContext.ModelValidationContext, metadata);
|
||||||
|
var modelState = validationContext.ModelValidationContext.ModelState;
|
||||||
|
var modelValidationState = modelState.GetValidationState(modelKey);
|
||||||
|
var fieldValidationState = modelState.GetFieldValidationState(modelKey);
|
||||||
|
|
||||||
|
// If either the model or its properties are unvalidated, validate them now.
|
||||||
|
if (modelValidationState == ModelValidationState.Unvalidated ||
|
||||||
|
fieldValidationState == ModelValidationState.Unvalidated)
|
||||||
|
{
|
||||||
|
foreach (var validator in validators)
|
||||||
|
{
|
||||||
|
foreach (var error in validator.Validate(modelValidationContext))
|
||||||
|
{
|
||||||
|
var errorKey = ModelBindingHelper.CreatePropertyModelName(modelKey, error.MemberName);
|
||||||
|
if (!modelState.TryAddModelError(errorKey, error.Message) &&
|
||||||
|
modelState.GetFieldValidationState(errorKey) == ModelValidationState.Unvalidated)
|
||||||
|
{
|
||||||
|
|
||||||
|
// If we are not able to add a model error
|
||||||
|
// for instance when the max error count is reached, mark the model as skipped.
|
||||||
|
modelState.MarkFieldSkipped(errorKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (fieldValidationState == ModelValidationState.Invalid)
|
||||||
|
{
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isValid)
|
||||||
|
{
|
||||||
|
validationContext.ModelValidationContext.ModelState.MarkFieldValid(modelKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsTypeExcludedFromValidation(IReadOnlyList<IExcludeTypeValidationFilter> filters, Type type)
|
||||||
|
{
|
||||||
|
// This can be set to null in ModelBinding scenarios which does not flow through this path.
|
||||||
|
if (filters == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return filters.Any(filter => filter.IsTypeExcluded(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Type GetElementType(Type type)
|
||||||
|
{
|
||||||
|
Debug.Assert(typeof(IEnumerable).IsAssignableFrom(type));
|
||||||
|
if (type.IsArray)
|
||||||
|
{
|
||||||
|
return type.GetElementType();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var implementedInterface in type.GetInterfaces())
|
||||||
|
{
|
||||||
|
if (implementedInterface.IsGenericType() &&
|
||||||
|
implementedInterface.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
||||||
|
{
|
||||||
|
return implementedInterface.GetGenericArguments()[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeof(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ValidationContext
|
||||||
|
{
|
||||||
|
public ModelValidationContext ModelValidationContext { get; set; }
|
||||||
|
|
||||||
|
public HashSet<object> Visited { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Validates the body parameter of an action after the parameter
|
|
||||||
/// has been read by the Input Formatters.
|
|
||||||
/// </summary>
|
|
||||||
public interface IBodyModelValidator
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Determines whether the Model is valid
|
|
||||||
/// and adds any validation errors to the <see cref="ModelStateDictionary"/>
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="modelValidaitonContext">The validation context which contains the model, metadata
|
|
||||||
/// and the validator providers.</param>
|
|
||||||
/// <param name="keyPrefix">The <see cref="string"/> to append to the key for any validation errors.</param>
|
|
||||||
/// <returns><c>true</c>if the model is valid, <c>false</c> otherwise.</returns>
|
|
||||||
bool Validate(ModelValidationContext modelValidationContext, string keyPrefix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -9,7 +9,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
public ModelValidationContext([NotNull] ModelBindingContext bindingContext,
|
public ModelValidationContext([NotNull] ModelBindingContext bindingContext,
|
||||||
[NotNull] ModelMetadata metadata)
|
[NotNull] ModelMetadata metadata)
|
||||||
: this(bindingContext.OperationBindingContext.MetadataProvider,
|
: this(bindingContext.ModelName,
|
||||||
bindingContext.OperationBindingContext.ValidatorProvider,
|
bindingContext.OperationBindingContext.ValidatorProvider,
|
||||||
bindingContext.ModelState,
|
bindingContext.ModelState,
|
||||||
metadata,
|
metadata,
|
||||||
|
|
@ -17,33 +17,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModelValidationContext([NotNull] IModelMetadataProvider metadataProvider,
|
public ModelValidationContext(string rootPrefix,
|
||||||
[NotNull] IModelValidatorProvider validatorProvider,
|
[NotNull] IModelValidatorProvider validatorProvider,
|
||||||
[NotNull] ModelStateDictionary modelState,
|
[NotNull] ModelStateDictionary modelState,
|
||||||
[NotNull] ModelMetadata metadata,
|
[NotNull] ModelMetadata metadata,
|
||||||
ModelMetadata containerMetadata)
|
ModelMetadata containerMetadata)
|
||||||
: this(metadataProvider,
|
|
||||||
validatorProvider,
|
|
||||||
modelState,
|
|
||||||
metadata,
|
|
||||||
containerMetadata,
|
|
||||||
excludeFromValidationFilters: null)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public ModelValidationContext([NotNull] IModelMetadataProvider metadataProvider,
|
|
||||||
[NotNull] IModelValidatorProvider validatorProvider,
|
|
||||||
[NotNull] ModelStateDictionary modelState,
|
|
||||||
[NotNull] ModelMetadata metadata,
|
|
||||||
ModelMetadata containerMetadata,
|
|
||||||
IReadOnlyList<IExcludeTypeValidationFilter> excludeFromValidationFilters)
|
|
||||||
{
|
{
|
||||||
ModelMetadata = metadata;
|
ModelMetadata = metadata;
|
||||||
ModelState = modelState;
|
ModelState = modelState;
|
||||||
MetadataProvider = metadataProvider;
|
RootPrefix = rootPrefix;
|
||||||
ValidatorProvider = validatorProvider;
|
ValidatorProvider = validatorProvider;
|
||||||
ContainerMetadata = containerMetadata;
|
ContainerMetadata = containerMetadata;
|
||||||
ExcludeFromValidationFilters = excludeFromValidationFilters;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModelValidationContext([NotNull] ModelValidationContext parentContext,
|
public ModelValidationContext([NotNull] ModelValidationContext parentContext,
|
||||||
|
|
@ -52,9 +36,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
ModelMetadata = metadata;
|
ModelMetadata = metadata;
|
||||||
ContainerMetadata = parentContext.ModelMetadata;
|
ContainerMetadata = parentContext.ModelMetadata;
|
||||||
ModelState = parentContext.ModelState;
|
ModelState = parentContext.ModelState;
|
||||||
MetadataProvider = parentContext.MetadataProvider;
|
RootPrefix = parentContext.RootPrefix;
|
||||||
ValidatorProvider = parentContext.ValidatorProvider;
|
ValidatorProvider = parentContext.ValidatorProvider;
|
||||||
ExcludeFromValidationFilters = parentContext.ExcludeFromValidationFilters;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModelMetadata ModelMetadata { get; }
|
public ModelMetadata ModelMetadata { get; }
|
||||||
|
|
@ -63,10 +46,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
public ModelStateDictionary ModelState { get; }
|
public ModelStateDictionary ModelState { get; }
|
||||||
|
|
||||||
public IModelMetadataProvider MetadataProvider { get; }
|
public string RootPrefix { get; set; }
|
||||||
|
|
||||||
public IModelValidatorProvider ValidatorProvider { get; }
|
public IModelValidatorProvider ValidatorProvider { get; }
|
||||||
|
|
||||||
public IReadOnlyList<IExcludeTypeValidationFilter> ExcludeFromValidationFilters { get; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,213 +8,4 @@ using Microsoft.AspNet.Mvc.ModelBinding.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
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([NotNull] ModelValidationContext validationContext, ModelValidationNode parentNode)
|
|
||||||
{
|
|
||||||
if (SuppressValidation || validationContext.ModelState.HasReachedMaxErrors)
|
|
||||||
{
|
|
||||||
// Short circuit if validation does not need to be applied or if we've reached the max number of
|
|
||||||
// validation errors.
|
|
||||||
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);
|
|
||||||
|
|
||||||
var modelState = validationContext.ModelState;
|
|
||||||
if (modelState.GetFieldValidationState(ModelStateKey) != ModelValidationState.Invalid)
|
|
||||||
{
|
|
||||||
// If a node or its subtree were not marked invalid, we can consider it valid at this point.
|
|
||||||
modelState.MarkFieldValid(ModelStateKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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.GetFieldValidationState(propertyKeyRoot) == ModelValidationState.Unvalidated)
|
|
||||||
{
|
|
||||||
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.TryAddModelError(thisErrorKey, propertyResult.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ValidateThis(ModelValidationContext validationContext, ModelValidationNode parentNode)
|
|
||||||
{
|
|
||||||
var modelState = validationContext.ModelState;
|
|
||||||
if (modelState.GetFieldValidationState(ModelStateKey) == ModelValidationState.Invalid)
|
|
||||||
{
|
|
||||||
// If any item in the key's subtree has been identified as invalid, short-circuit
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
{
|
|
||||||
modelState.TryAddModelError(ModelStateKey, 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 currentModelStateKey = ModelBindingHelper.CreatePropertyModelName(ModelStateKey,
|
|
||||||
validationResult.MemberName);
|
|
||||||
modelState.TryAddModelError(currentModelStateKey, validationResult.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerable<IModelValidator> GetValidators(ModelValidationContext validationContext,
|
|
||||||
ModelMetadata metadata)
|
|
||||||
{
|
|
||||||
return validationContext.ValidatorProvider.GetValidators(metadata);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,10 @@ namespace System.Web.Http
|
||||||
[FromServices]
|
[FromServices]
|
||||||
public IModelMetadataProvider MetadataProvider { get; set; }
|
public IModelMetadataProvider MetadataProvider { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
[FromServices]
|
||||||
|
public IObjectModelValidator ObjectValidator { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets model state after the model binding process. This ModelState will be empty before model binding
|
/// Gets model state after the model binding process. This ModelState will be empty before model binding
|
||||||
/// happens.
|
/// happens.
|
||||||
|
|
@ -417,18 +421,14 @@ namespace System.Web.Http
|
||||||
{
|
{
|
||||||
var modelMetadata = MetadataProvider.GetMetadataForType(() => entity, typeof(TEntity));
|
var modelMetadata = MetadataProvider.GetMetadataForType(() => entity, typeof(TEntity));
|
||||||
|
|
||||||
var bodyValidationExcludeFiltersProvider = Context.RequestServices
|
|
||||||
.GetRequiredService<IValidationExcludeFiltersProvider>();
|
|
||||||
var validator = Context.RequestServices.GetRequiredService<IBodyModelValidator>();
|
|
||||||
|
|
||||||
var modelValidationContext = new ModelValidationContext(
|
var modelValidationContext = new ModelValidationContext(
|
||||||
MetadataProvider,
|
keyPrefix,
|
||||||
BindingContext.ValidatorProvider,
|
BindingContext.ValidatorProvider,
|
||||||
ModelState,
|
ModelState,
|
||||||
modelMetadata,
|
modelMetadata,
|
||||||
containerMetadata: null,
|
containerMetadata: null);
|
||||||
excludeFromValidationFilters: bodyValidationExcludeFiltersProvider.ExcludeFilters);
|
|
||||||
validator.Validate(modelValidationContext, keyPrefix);
|
ObjectValidator.Validate(modelValidationContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
|
|
|
||||||
|
|
@ -9,15 +9,15 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
||||||
{
|
{
|
||||||
public class HttpRequestMessageModelBinder : IModelBinder
|
public class HttpRequestMessageModelBinder : IModelBinder
|
||||||
{
|
{
|
||||||
public Task<bool> BindModelAsync(ModelBindingContext bindingContext)
|
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
if (bindingContext.ModelType == typeof(HttpRequestMessage))
|
if (bindingContext.ModelType == typeof(HttpRequestMessage))
|
||||||
{
|
{
|
||||||
bindingContext.Model = bindingContext.OperationBindingContext.HttpContext.GetHttpRequestMessage();
|
var model = bindingContext.OperationBindingContext.HttpContext.GetHttpRequestMessage();
|
||||||
return Task.FromResult(true);
|
return Task.FromResult(new ModelBindingResult(model, bindingContext.ModelName, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.FromResult(false);
|
return Task.FromResult<ModelBindingResult>(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Net.Http;
|
||||||
using System.Net.Http.Formatting;
|
using System.Net.Http.Formatting;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
using Microsoft.AspNet.Mvc.WebApiCompatShim;
|
using Microsoft.AspNet.Mvc.WebApiCompatShim;
|
||||||
|
|
||||||
namespace Microsoft.Framework.DependencyInjection
|
namespace Microsoft.Framework.DependencyInjection
|
||||||
|
|
@ -15,6 +17,10 @@ namespace Microsoft.Framework.DependencyInjection
|
||||||
// The constructors on DefaultContentNegotiator aren't DI friendly, so just
|
// The constructors on DefaultContentNegotiator aren't DI friendly, so just
|
||||||
// new it up.
|
// new it up.
|
||||||
services.AddInstance<IContentNegotiator>(new DefaultContentNegotiator());
|
services.AddInstance<IContentNegotiator>(new DefaultContentNegotiator());
|
||||||
|
services.Configure<MvcOptions>(options =>
|
||||||
|
{
|
||||||
|
options.ValidationExcludeFilters.Add(typeof(HttpRequestMessage));
|
||||||
|
});
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,12 @@ namespace Microsoft.AspNet.Mvc
|
||||||
options.ValidationExcludeFilters.Add(typeof(XObject));
|
options.ValidationExcludeFilters.Add(typeof(XObject));
|
||||||
options.ValidationExcludeFilters.Add(typeof(Type));
|
options.ValidationExcludeFilters.Add(typeof(Type));
|
||||||
options.ValidationExcludeFilters.Add(typeof(JToken));
|
options.ValidationExcludeFilters.Add(typeof(JToken));
|
||||||
|
|
||||||
|
// Any 'known' types that we bind should be marked as excluded from validation.
|
||||||
|
options.ValidationExcludeFilters.Add(typeof(System.Threading.CancellationToken));
|
||||||
|
options.ValidationExcludeFilters.Add(typeof(Http.IFormFile));
|
||||||
|
options.ValidationExcludeFilters.Add(typeof(Http.IFormCollection));
|
||||||
|
|
||||||
options.ValidationExcludeFilters.Add(typeFullName: "System.Xml.XmlNode");
|
options.ValidationExcludeFilters.Add(typeFullName: "System.Xml.XmlNode");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
yield return describe.Singleton<IActionSelectorDecisionTreeProvider, ActionSelectorDecisionTreeProvider>();
|
yield return describe.Singleton<IActionSelectorDecisionTreeProvider, ActionSelectorDecisionTreeProvider>();
|
||||||
yield return describe.Scoped<IActionSelector, DefaultActionSelector>();
|
yield return describe.Scoped<IActionSelector, DefaultActionSelector>();
|
||||||
yield return describe.Transient<IControllerActionArgumentBinder, DefaultControllerActionArgumentBinder>();
|
yield return describe.Transient<IControllerActionArgumentBinder, DefaultControllerActionArgumentBinder>();
|
||||||
|
yield return describe.Transient<IObjectModelValidator, DefaultObjectValidator>();
|
||||||
|
|
||||||
yield return describe.Transient<INestedProvider<ActionDescriptorProviderContext>,
|
yield return describe.Transient<INestedProvider<ActionDescriptorProviderContext>,
|
||||||
ControllerActionDescriptorProvider>();
|
ControllerActionDescriptorProvider>();
|
||||||
|
|
@ -91,7 +92,6 @@ namespace Microsoft.AspNet.Mvc
|
||||||
yield return describe.Instance<JsonOutputFormatter>(new JsonOutputFormatter());
|
yield return describe.Instance<JsonOutputFormatter>(new JsonOutputFormatter());
|
||||||
|
|
||||||
yield return describe.Transient<IModelValidatorProviderProvider, DefaultModelValidatorProviderProvider>();
|
yield return describe.Transient<IModelValidatorProviderProvider, DefaultModelValidatorProviderProvider>();
|
||||||
yield return describe.Transient<IBodyModelValidator, DefaultBodyModelValidator>();
|
|
||||||
yield return describe.Transient<IValidationExcludeFiltersProvider,
|
yield return describe.Transient<IValidationExcludeFiltersProvider,
|
||||||
DefaultValidationExcludeFiltersProvider>();
|
DefaultValidationExcludeFiltersProvider>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,9 @@ namespace Microsoft.AspNet.Mvc
|
||||||
public class BodyModelBinderTests
|
public class BodyModelBinderTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task BindModel_CallsValidationAndSelectedInputFormatterOnce()
|
public async Task BindModel_CallsSelectedInputFormatterOnce()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var mockValidator = new Mock<IBodyModelValidator>();
|
|
||||||
mockValidator.Setup(o => o.Validate(It.IsAny<ModelValidationContext>(), It.IsAny<string>()))
|
|
||||||
.Returns(true)
|
|
||||||
.Verifiable();
|
|
||||||
var mockInputFormatter = new Mock<IInputFormatter>();
|
var mockInputFormatter = new Mock<IInputFormatter>();
|
||||||
mockInputFormatter.Setup(o => o.ReadAsync(It.IsAny<InputFormatterContext>()))
|
mockInputFormatter.Setup(o => o.ReadAsync(It.IsAny<InputFormatterContext>()))
|
||||||
.Returns(Task.FromResult<object>(new Person()))
|
.Returns(Task.FromResult<object>(new Person()))
|
||||||
|
|
@ -32,13 +28,12 @@ namespace Microsoft.AspNet.Mvc
|
||||||
var bindingContext = GetBindingContext(typeof(Person), inputFormatter: mockInputFormatter.Object);
|
var bindingContext = GetBindingContext(typeof(Person), inputFormatter: mockInputFormatter.Object);
|
||||||
bindingContext.ModelMetadata.BinderMetadata = new FromBodyAttribute();
|
bindingContext.ModelMetadata.BinderMetadata = new FromBodyAttribute();
|
||||||
|
|
||||||
var binder = GetBodyBinder(mockInputFormatter.Object, mockValidator.Object);
|
var binder = GetBodyBinder(mockInputFormatter.Object);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var binderResult = await binder.BindModelAsync(bindingContext);
|
var binderResult = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
mockValidator.Verify(v => v.Validate(It.IsAny<ModelValidationContext>(), It.IsAny<string>()), Times.Once);
|
|
||||||
mockInputFormatter.Verify(v => v.ReadAsync(It.IsAny<InputFormatterContext>()), Times.Once);
|
mockInputFormatter.Verify(v => v.ReadAsync(It.IsAny<InputFormatterContext>()), Times.Once);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,8 +52,9 @@ namespace Microsoft.AspNet.Mvc
|
||||||
// Assert
|
// Assert
|
||||||
|
|
||||||
// Returns true because it understands the metadata type.
|
// Returns true because it understands the metadata type.
|
||||||
Assert.True(binderResult);
|
Assert.NotNull(binderResult);
|
||||||
Assert.Null(bindingContext.Model);
|
Assert.False(binderResult.IsModelSet);
|
||||||
|
Assert.Null(binderResult.Model);
|
||||||
Assert.True(bindingContext.ModelState.ContainsKey("someName"));
|
Assert.True(bindingContext.ModelState.ContainsKey("someName"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,7 +74,8 @@ namespace Microsoft.AspNet.Mvc
|
||||||
var binderResult = await binder.BindModelAsync(bindingContext);
|
var binderResult = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(binderResult);
|
Assert.NotNull(binderResult);
|
||||||
|
Assert.False(binderResult.IsModelSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -97,7 +94,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
var binderResult = await binder.BindModelAsync(bindingContext);
|
var binderResult = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(binderResult);
|
Assert.Null(binderResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -116,7 +113,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
var binderResult = await binder.BindModelAsync(bindingContext);
|
var binderResult = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(binderResult);
|
Assert.Null(binderResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ModelBindingContext GetBindingContext(Type modelType, IInputFormatter inputFormatter)
|
private static ModelBindingContext GetBindingContext(Type modelType, IInputFormatter inputFormatter)
|
||||||
|
|
@ -124,7 +121,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
var metadataProvider = new EmptyModelMetadataProvider();
|
var metadataProvider = new EmptyModelMetadataProvider();
|
||||||
var operationBindingContext = new OperationBindingContext
|
var operationBindingContext = new OperationBindingContext
|
||||||
{
|
{
|
||||||
ModelBinder = GetBodyBinder(inputFormatter, null),
|
ModelBinder = GetBodyBinder(inputFormatter),
|
||||||
MetadataProvider = metadataProvider,
|
MetadataProvider = metadataProvider,
|
||||||
HttpContext = new DefaultHttpContext(),
|
HttpContext = new DefaultHttpContext(),
|
||||||
};
|
};
|
||||||
|
|
@ -141,8 +138,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
return bindingContext;
|
return bindingContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BodyModelBinder GetBodyBinder(
|
private static BodyModelBinder GetBodyBinder(IInputFormatter inputFormatter)
|
||||||
IInputFormatter inputFormatter, IBodyModelValidator validator)
|
|
||||||
{
|
{
|
||||||
var actionContext = CreateActionContext(new DefaultHttpContext());
|
var actionContext = CreateActionContext(new DefaultHttpContext());
|
||||||
var inputFormatterSelector = new Mock<IInputFormatterSelector>();
|
var inputFormatterSelector = new Mock<IInputFormatterSelector>();
|
||||||
|
|
@ -152,15 +148,6 @@ namespace Microsoft.AspNet.Mvc
|
||||||
It.IsAny<InputFormatterContext>()))
|
It.IsAny<InputFormatterContext>()))
|
||||||
.Returns(inputFormatter);
|
.Returns(inputFormatter);
|
||||||
|
|
||||||
if (validator == null)
|
|
||||||
{
|
|
||||||
var mockValidator = new Mock<IBodyModelValidator>();
|
|
||||||
mockValidator.Setup(o => o.Validate(It.IsAny<ModelValidationContext>(), It.IsAny<string>()))
|
|
||||||
.Returns(true)
|
|
||||||
.Verifiable();
|
|
||||||
validator = mockValidator.Object;
|
|
||||||
}
|
|
||||||
|
|
||||||
var bodyValidationPredicatesProvider = new Mock<IValidationExcludeFiltersProvider>();
|
var bodyValidationPredicatesProvider = new Mock<IValidationExcludeFiltersProvider>();
|
||||||
bodyValidationPredicatesProvider.SetupGet(o => o.ExcludeFilters)
|
bodyValidationPredicatesProvider.SetupGet(o => o.ExcludeFilters)
|
||||||
.Returns(new List<IExcludeTypeValidationFilter>());
|
.Returns(new List<IExcludeTypeValidationFilter>());
|
||||||
|
|
@ -179,7 +166,6 @@ namespace Microsoft.AspNet.Mvc
|
||||||
actionContext,
|
actionContext,
|
||||||
bindingContextAccessor,
|
bindingContextAccessor,
|
||||||
inputFormatterSelector.Object,
|
inputFormatterSelector.Object,
|
||||||
validator,
|
|
||||||
bodyValidationPredicatesProvider.Object);
|
bodyValidationPredicatesProvider.Object);
|
||||||
|
|
||||||
return binder;
|
return binder;
|
||||||
|
|
|
||||||
|
|
@ -1990,7 +1990,9 @@ namespace Microsoft.AspNet.Mvc
|
||||||
var inputFormattersProvider = new Mock<IInputFormattersProvider>();
|
var inputFormattersProvider = new Mock<IInputFormattersProvider>();
|
||||||
inputFormattersProvider.SetupGet(o => o.InputFormatters)
|
inputFormattersProvider.SetupGet(o => o.InputFormatters)
|
||||||
.Returns(new List<IInputFormatter>());
|
.Returns(new List<IInputFormatter>());
|
||||||
|
var excludeFilterProvider = new Mock<IValidationExcludeFiltersProvider>();
|
||||||
|
excludeFilterProvider.SetupGet(o => o.ExcludeFilters)
|
||||||
|
.Returns(new List<IExcludeTypeValidationFilter>());
|
||||||
var invoker = new TestControllerActionInvoker(
|
var invoker = new TestControllerActionInvoker(
|
||||||
actionContext,
|
actionContext,
|
||||||
filterProvider.Object,
|
filterProvider.Object,
|
||||||
|
|
@ -2029,7 +2031,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
var binder = new Mock<IModelBinder>();
|
var binder = new Mock<IModelBinder>();
|
||||||
binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||||
.Returns(Task.FromResult(result: false));
|
.Returns(Task.FromResult<ModelBindingResult>(result: null));
|
||||||
var context = new Mock<HttpContext>();
|
var context = new Mock<HttpContext>();
|
||||||
context.SetupGet(c => c.Items)
|
context.SetupGet(c => c.Items)
|
||||||
.Returns(new Dictionary<object, object>());
|
.Returns(new Dictionary<object, object>());
|
||||||
|
|
@ -2042,18 +2044,21 @@ namespace Microsoft.AspNet.Mvc
|
||||||
var inputFormattersProvider = new Mock<IInputFormattersProvider>();
|
var inputFormattersProvider = new Mock<IInputFormattersProvider>();
|
||||||
inputFormattersProvider.SetupGet(o => o.InputFormatters)
|
inputFormattersProvider.SetupGet(o => o.InputFormatters)
|
||||||
.Returns(new List<IInputFormatter>());
|
.Returns(new List<IInputFormatter>());
|
||||||
|
var metadataProvider = new EmptyModelMetadataProvider();
|
||||||
var invoker = new ControllerActionInvoker(
|
var invoker = new ControllerActionInvoker(
|
||||||
actionContext,
|
actionContext,
|
||||||
Mock.Of<INestedProviderManager<FilterProviderContext>>(),
|
Mock.Of<INestedProviderManager<FilterProviderContext>>(),
|
||||||
controllerFactory.Object,
|
controllerFactory.Object,
|
||||||
actionDescriptor,
|
actionDescriptor,
|
||||||
inputFormattersProvider.Object,
|
inputFormattersProvider.Object,
|
||||||
new DefaultControllerActionArgumentBinder(new EmptyModelMetadataProvider(), new MockMvcOptionsAccessor()),
|
new DefaultControllerActionArgumentBinder(
|
||||||
new MockModelBinderProvider() { ModelBinders = new List<IModelBinder>() { binder.Object } },
|
metadataProvider,
|
||||||
new MockModelValidatorProviderProvider(),
|
new DefaultObjectValidator(Mock.Of<IValidationExcludeFiltersProvider>(), metadataProvider),
|
||||||
new MockValueProviderFactoryProvider(),
|
new MockMvcOptionsAccessor()),
|
||||||
new MockScopedInstance<ActionBindingContext>());
|
new MockModelBinderProvider() { ModelBinders = new List<IModelBinder>() { binder.Object } },
|
||||||
|
new MockModelValidatorProviderProvider(),
|
||||||
|
new MockValueProviderFactoryProvider(),
|
||||||
|
new MockScopedInstance<ActionBindingContext>());
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await invoker.InvokeAsync();
|
await invoker.InvokeAsync();
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
using Microsoft.AspNet.Routing;
|
using Microsoft.AspNet.Routing;
|
||||||
using Microsoft.AspNet.Testing;
|
using Microsoft.AspNet.Testing;
|
||||||
using Microsoft.AspNet.WebUtilities;
|
using Microsoft.AspNet.WebUtilities;
|
||||||
|
using Microsoft.AspNet.Http.Core;
|
||||||
|
using Microsoft.Framework.DependencyInjection;
|
||||||
#if ASPNET50
|
#if ASPNET50
|
||||||
using Moq;
|
using Moq;
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -917,7 +919,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
||||||
Assert.True(context.PropertyFilter(context, "Property1"));
|
Assert.True(context.PropertyFilter(context, "Property1"));
|
||||||
Assert.True(context.PropertyFilter(context, "Property2"));
|
Assert.True(context.PropertyFilter(context, "Property2"));
|
||||||
})
|
})
|
||||||
.Returns(Task.FromResult(false))
|
.Returns(Task.FromResult<ModelBindingResult>(null))
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
|
|
||||||
var controller = GetController(binder.Object, valueProvider);
|
var controller = GetController(binder.Object, valueProvider);
|
||||||
|
|
@ -950,7 +952,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
||||||
Assert.True(context.PropertyFilter(context, "Property1"));
|
Assert.True(context.PropertyFilter(context, "Property1"));
|
||||||
Assert.True(context.PropertyFilter(context, "Property2"));
|
Assert.True(context.PropertyFilter(context, "Property2"));
|
||||||
})
|
})
|
||||||
.Returns(Task.FromResult(false))
|
.Returns(Task.FromResult<ModelBindingResult>(null))
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
|
|
||||||
var controller = GetController(binder.Object, valueProvider);
|
var controller = GetController(binder.Object, valueProvider);
|
||||||
|
|
@ -982,7 +984,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
||||||
Assert.True(context.PropertyFilter(context, "Property1"));
|
Assert.True(context.PropertyFilter(context, "Property1"));
|
||||||
Assert.True(context.PropertyFilter(context, "Property2"));
|
Assert.True(context.PropertyFilter(context, "Property2"));
|
||||||
})
|
})
|
||||||
.Returns(Task.FromResult(false))
|
.Returns(Task.FromResult<ModelBindingResult>(null))
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
|
|
||||||
var controller = GetController(binder.Object, provider: null);
|
var controller = GetController(binder.Object, provider: null);
|
||||||
|
|
@ -1019,7 +1021,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
||||||
Assert.False(context.PropertyFilter(context, "exclude1"));
|
Assert.False(context.PropertyFilter(context, "exclude1"));
|
||||||
Assert.False(context.PropertyFilter(context, "exclude2"));
|
Assert.False(context.PropertyFilter(context, "exclude2"));
|
||||||
})
|
})
|
||||||
.Returns(Task.FromResult(true))
|
.Returns(Task.FromResult<ModelBindingResult>(null))
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
|
|
||||||
var controller = GetController(binder.Object, valueProvider);
|
var controller = GetController(binder.Object, valueProvider);
|
||||||
|
|
@ -1056,7 +1058,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
||||||
Assert.False(context.PropertyFilter(context, "exclude1"));
|
Assert.False(context.PropertyFilter(context, "exclude1"));
|
||||||
Assert.False(context.PropertyFilter(context, "exclude2"));
|
Assert.False(context.PropertyFilter(context, "exclude2"));
|
||||||
})
|
})
|
||||||
.Returns(Task.FromResult(true))
|
.Returns(Task.FromResult<ModelBindingResult>(null))
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
|
|
||||||
var controller = GetController(binder.Object, provider: null);
|
var controller = GetController(binder.Object, provider: null);
|
||||||
|
|
@ -1090,7 +1092,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
||||||
Assert.False(context.PropertyFilter(context, "exclude1"));
|
Assert.False(context.PropertyFilter(context, "exclude1"));
|
||||||
Assert.False(context.PropertyFilter(context, "exclude2"));
|
Assert.False(context.PropertyFilter(context, "exclude2"));
|
||||||
})
|
})
|
||||||
.Returns(Task.FromResult(true))
|
.Returns(Task.FromResult<ModelBindingResult>(null))
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1125,7 +1127,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
||||||
Assert.False(context.PropertyFilter(context, "exclude1"));
|
Assert.False(context.PropertyFilter(context, "exclude1"));
|
||||||
Assert.False(context.PropertyFilter(context, "exclude2"));
|
Assert.False(context.PropertyFilter(context, "exclude2"));
|
||||||
})
|
})
|
||||||
.Returns(Task.FromResult(true))
|
.Returns(Task.FromResult<ModelBindingResult>(null))
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
|
|
||||||
var controller = GetController(binder.Object, provider: null);
|
var controller = GetController(binder.Object, provider: null);
|
||||||
|
|
@ -1336,7 +1338,8 @@ namespace Microsoft.AspNet.Mvc.Test
|
||||||
private static Controller GetController(IModelBinder binder, IValueProvider provider)
|
private static Controller GetController(IModelBinder binder, IValueProvider provider)
|
||||||
{
|
{
|
||||||
var metadataProvider = new DataAnnotationsModelMetadataProvider();
|
var metadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||||
var actionContext = new ActionContext(Mock.Of<HttpContext>(), new RouteData(), new ActionDescriptor());
|
var httpContext = new DefaultHttpContext();
|
||||||
|
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
||||||
|
|
||||||
var viewData = new ViewDataDictionary(metadataProvider, new ModelStateDictionary());
|
var viewData = new ViewDataDictionary(metadataProvider, new ModelStateDictionary());
|
||||||
|
|
||||||
|
|
@ -1344,6 +1347,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
||||||
{
|
{
|
||||||
ModelBinder = binder,
|
ModelBinder = binder,
|
||||||
ValueProvider = provider,
|
ValueProvider = provider,
|
||||||
|
ValidatorProvider = new DataAnnotationsModelValidatorProvider()
|
||||||
};
|
};
|
||||||
|
|
||||||
return new TestableController()
|
return new TestableController()
|
||||||
|
|
@ -1351,7 +1355,8 @@ namespace Microsoft.AspNet.Mvc.Test
|
||||||
ActionContext = actionContext,
|
ActionContext = actionContext,
|
||||||
BindingContext = bindingContext,
|
BindingContext = bindingContext,
|
||||||
MetadataProvider = metadataProvider,
|
MetadataProvider = metadataProvider,
|
||||||
ViewData = viewData
|
ViewData = viewData,
|
||||||
|
ObjectValidator = new DefaultObjectValidator(Mock.Of<IValidationExcludeFiltersProvider>(), metadataProvider)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -268,13 +268,16 @@ namespace Microsoft.AspNet.Mvc.Core
|
||||||
|
|
||||||
private IServiceProvider GetServices()
|
private IServiceProvider GetServices()
|
||||||
{
|
{
|
||||||
|
var metadataProvider = new EmptyModelMetadataProvider();
|
||||||
var services = new Mock<IServiceProvider>();
|
var services = new Mock<IServiceProvider>();
|
||||||
services.Setup(s => s.GetService(typeof(IUrlHelper)))
|
services.Setup(s => s.GetService(typeof(IUrlHelper)))
|
||||||
.Returns(Mock.Of<IUrlHelper>());
|
.Returns(Mock.Of<IUrlHelper>());
|
||||||
services.Setup(s => s.GetService(typeof(IModelMetadataProvider)))
|
services.Setup(s => s.GetService(typeof(IModelMetadataProvider)))
|
||||||
.Returns(new EmptyModelMetadataProvider());
|
.Returns(metadataProvider);
|
||||||
services.Setup(s => s.GetService(typeof(TestService)))
|
services.Setup(s => s.GetService(typeof(TestService)))
|
||||||
.Returns(new TestService());
|
.Returns(new TestService());
|
||||||
|
services.Setup(s => s.GetService(typeof(IObjectModelValidator)))
|
||||||
|
.Returns(new DefaultObjectValidator(Mock.Of<IValidationExcludeFiltersProvider>(), metadataProvider));
|
||||||
services
|
services
|
||||||
.Setup(s => s.GetService(typeof(IScopedInstance<ActionBindingContext>)))
|
.Setup(s => s.GetService(typeof(IScopedInstance<ActionBindingContext>)))
|
||||||
.Returns(new MockScopedInstance<ActionBindingContext>());
|
.Returns(new MockScopedInstance<ActionBindingContext>());
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ namespace Microsoft.AspNet.Mvc.OptionDescriptors
|
||||||
|
|
||||||
public ITestService Service { get; private set; }
|
public ITestService Service { get; private set; }
|
||||||
|
|
||||||
public Task<bool> BindModelAsync(ModelBindingContext bindingContext)
|
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ namespace Microsoft.AspNet.Mvc.OptionDescriptors
|
||||||
|
|
||||||
private class TestModelBinder : IModelBinder
|
private class TestModelBinder : IModelBinder
|
||||||
{
|
{
|
||||||
public Task<bool> BindModelAsync(ModelBindingContext bindingContext)
|
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -169,12 +169,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
var binder = new Mock<IModelBinder>();
|
var binder = new Mock<IModelBinder>();
|
||||||
binder
|
binder
|
||||||
.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||||
.Callback<ModelBindingContext>(c =>
|
.Returns(Task.FromResult<ModelBindingResult>(result: null));
|
||||||
{
|
|
||||||
// This value won't go into the arguments, because we return false.
|
|
||||||
c.Model = "Hello";
|
|
||||||
})
|
|
||||||
.Returns(Task.FromResult(result: false));
|
|
||||||
|
|
||||||
var actionContext = new ActionContext(
|
var actionContext = new ActionContext(
|
||||||
new DefaultHttpContext(),
|
new DefaultHttpContext(),
|
||||||
|
|
@ -186,13 +181,14 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
ModelBinder = binder.Object,
|
ModelBinder = binder.Object,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var modelMetadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||||
var inputFormattersProvider = new Mock<IInputFormattersProvider>();
|
var inputFormattersProvider = new Mock<IInputFormattersProvider>();
|
||||||
inputFormattersProvider
|
inputFormattersProvider
|
||||||
.SetupGet(o => o.InputFormatters)
|
.SetupGet(o => o.InputFormatters)
|
||||||
.Returns(new List<IInputFormatter>());
|
.Returns(new List<IInputFormatter>());
|
||||||
|
|
||||||
var invoker = new DefaultControllerActionArgumentBinder(
|
var invoker = new DefaultControllerActionArgumentBinder(
|
||||||
new DataAnnotationsModelMetadataProvider(),
|
modelMetadataProvider,
|
||||||
|
new DefaultObjectValidator(Mock.Of<IValidationExcludeFiltersProvider>(), modelMetadataProvider),
|
||||||
new MockMvcOptionsAccessor());
|
new MockMvcOptionsAccessor());
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -223,11 +219,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
var binder = new Mock<IModelBinder>();
|
var binder = new Mock<IModelBinder>();
|
||||||
binder
|
binder
|
||||||
.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||||
.Callback<ModelBindingContext>(c =>
|
.Returns(Task.FromResult(new ModelBindingResult(null, "", false)));
|
||||||
{
|
|
||||||
Assert.False(c.IsModelSet);
|
|
||||||
})
|
|
||||||
.Returns(Task.FromResult(result: true));
|
|
||||||
|
|
||||||
var actionContext = new ActionContext(
|
var actionContext = new ActionContext(
|
||||||
new DefaultHttpContext(),
|
new DefaultHttpContext(),
|
||||||
|
|
@ -244,8 +236,10 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
.SetupGet(o => o.InputFormatters)
|
.SetupGet(o => o.InputFormatters)
|
||||||
.Returns(new List<IInputFormatter>());
|
.Returns(new List<IInputFormatter>());
|
||||||
|
|
||||||
|
var modelMetadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||||
var invoker = new DefaultControllerActionArgumentBinder(
|
var invoker = new DefaultControllerActionArgumentBinder(
|
||||||
new DataAnnotationsModelMetadataProvider(),
|
modelMetadataProvider,
|
||||||
|
new DefaultObjectValidator(Mock.Of<IValidationExcludeFiltersProvider>(), modelMetadataProvider),
|
||||||
new MockMvcOptionsAccessor());
|
new MockMvcOptionsAccessor());
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -284,10 +278,8 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
context.ModelMetadata = metadataProvider.GetMetadataForType(
|
context.ModelMetadata = metadataProvider.GetMetadataForType(
|
||||||
modelAccessor: null,
|
modelAccessor: null,
|
||||||
modelType: typeof(string));
|
modelType: typeof(string));
|
||||||
|
|
||||||
context.Model = value;
|
|
||||||
})
|
})
|
||||||
.Returns(Task.FromResult(result: true));
|
.Returns(Task.FromResult(result: new ModelBindingResult(value, "", true)));
|
||||||
|
|
||||||
var actionContext = new ActionContext(
|
var actionContext = new ActionContext(
|
||||||
new DefaultHttpContext(),
|
new DefaultHttpContext(),
|
||||||
|
|
@ -299,8 +291,12 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
ModelBinder = binder.Object,
|
ModelBinder = binder.Object,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var mockValidatorProvider = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
||||||
|
mockValidatorProvider.Setup(o => o.Validate(It.IsAny<ModelValidationContext>()));
|
||||||
|
|
||||||
var invoker = new DefaultControllerActionArgumentBinder(
|
var invoker = new DefaultControllerActionArgumentBinder(
|
||||||
metadataProvider,
|
metadataProvider,
|
||||||
|
mockValidatorProvider.Object,
|
||||||
new MockMvcOptionsAccessor());
|
new MockMvcOptionsAccessor());
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -311,6 +307,107 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
Assert.Equal(value, result["foo"]);
|
Assert.Equal(value, result["foo"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetActionArgumentsAsync_CallsValidator_IfModelBinderSucceeds()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Func<object, int> method = foo => 1;
|
||||||
|
var actionDescriptor = new ControllerActionDescriptor
|
||||||
|
{
|
||||||
|
MethodInfo = method.Method,
|
||||||
|
Parameters = new List<ParameterDescriptor>
|
||||||
|
{
|
||||||
|
new ParameterDescriptor
|
||||||
|
{
|
||||||
|
Name = "foo",
|
||||||
|
ParameterType = typeof(object),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var actionContext = new ActionContext(
|
||||||
|
new DefaultHttpContext(),
|
||||||
|
new RouteData(),
|
||||||
|
actionDescriptor);
|
||||||
|
|
||||||
|
var binder = new Mock<IModelBinder>();
|
||||||
|
binder
|
||||||
|
.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||||
|
.Returns(Task.FromResult(result: new ModelBindingResult(
|
||||||
|
model: null,
|
||||||
|
key: string.Empty,
|
||||||
|
isModelSet: true)));
|
||||||
|
|
||||||
|
var actionBindingContext = new ActionBindingContext()
|
||||||
|
{
|
||||||
|
ModelBinder = binder.Object,
|
||||||
|
};
|
||||||
|
|
||||||
|
var mockValidatorProvider = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
||||||
|
mockValidatorProvider
|
||||||
|
.Setup(o => o.Validate(It.IsAny<ModelValidationContext>()))
|
||||||
|
.Verifiable();
|
||||||
|
var invoker = new DefaultControllerActionArgumentBinder(
|
||||||
|
new DataAnnotationsModelMetadataProvider(),
|
||||||
|
mockValidatorProvider.Object,
|
||||||
|
new MockMvcOptionsAccessor());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await invoker.GetActionArgumentsAsync(actionContext, actionBindingContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
mockValidatorProvider.Verify(
|
||||||
|
o => o.Validate(It.IsAny<ModelValidationContext>()), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetActionArgumentsAsync_DoesNotCallValidator_IfModelBinderFails()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Func<object, int> method = foo => 1;
|
||||||
|
var actionDescriptor = new ControllerActionDescriptor
|
||||||
|
{
|
||||||
|
MethodInfo = method.Method,
|
||||||
|
Parameters = new List<ParameterDescriptor>
|
||||||
|
{
|
||||||
|
new ParameterDescriptor
|
||||||
|
{
|
||||||
|
Name = "foo",
|
||||||
|
ParameterType = typeof(object),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var actionContext = new ActionContext(
|
||||||
|
new DefaultHttpContext(),
|
||||||
|
new RouteData(),
|
||||||
|
actionDescriptor);
|
||||||
|
|
||||||
|
var binder = new Mock<IModelBinder>();
|
||||||
|
binder
|
||||||
|
.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||||
|
.Returns(Task.FromResult<ModelBindingResult>(null));
|
||||||
|
|
||||||
|
var actionBindingContext = new ActionBindingContext()
|
||||||
|
{
|
||||||
|
ModelBinder = binder.Object,
|
||||||
|
};
|
||||||
|
|
||||||
|
var mockValidatorProvider = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
||||||
|
mockValidatorProvider.Setup(o => o.Validate(It.IsAny<ModelValidationContext>()))
|
||||||
|
.Verifiable();
|
||||||
|
var invoker = new DefaultControllerActionArgumentBinder(
|
||||||
|
new DataAnnotationsModelMetadataProvider(),
|
||||||
|
mockValidatorProvider.Object,
|
||||||
|
new MockMvcOptionsAccessor());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await invoker.GetActionArgumentsAsync(actionContext, actionBindingContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
mockValidatorProvider.Verify(o => o.Validate(It.IsAny<ModelValidationContext>()), Times.Never());
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetActionArgumentsAsync_SetsMaxModelErrors()
|
public async Task GetActionArgumentsAsync_SetsMaxModelErrors()
|
||||||
{
|
{
|
||||||
|
|
@ -332,11 +429,8 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
var binder = new Mock<IModelBinder>();
|
var binder = new Mock<IModelBinder>();
|
||||||
binder
|
binder
|
||||||
.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||||
.Callback<ModelBindingContext>(c =>
|
.Returns(Task.FromResult(
|
||||||
{
|
result: new ModelBindingResult(model: "Hello", key: string.Empty, isModelSet: true)));
|
||||||
c.Model = "Hello";
|
|
||||||
})
|
|
||||||
.Returns(Task.FromResult(result: true));
|
|
||||||
|
|
||||||
var actionContext = new ActionContext(
|
var actionContext = new ActionContext(
|
||||||
new DefaultHttpContext(),
|
new DefaultHttpContext(),
|
||||||
|
|
@ -355,9 +449,11 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
|
|
||||||
var options = new MockMvcOptionsAccessor();
|
var options = new MockMvcOptionsAccessor();
|
||||||
options.Options.MaxModelValidationErrors = 5;
|
options.Options.MaxModelValidationErrors = 5;
|
||||||
|
var mockValidatorProvider = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
||||||
|
mockValidatorProvider.Setup(o => o.Validate(It.IsAny<ModelValidationContext>()));
|
||||||
var invoker = new DefaultControllerActionArgumentBinder(
|
var invoker = new DefaultControllerActionArgumentBinder(
|
||||||
new DataAnnotationsModelMetadataProvider(),
|
new DataAnnotationsModelMetadataProvider(),
|
||||||
|
mockValidatorProvider.Object,
|
||||||
options);
|
options);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
|
||||||
|
|
@ -24,25 +24,26 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var metadataProvider = new Mock<IModelMetadataProvider>();
|
var metadataProvider = new Mock<IModelMetadataProvider>();
|
||||||
metadataProvider.Setup(m => m.GetMetadataForType(null, It.IsAny<Type>()))
|
metadataProvider.Setup(m => m.GetMetadataForType(It.IsAny<Func<object>>(), It.IsAny<Type>()))
|
||||||
.Returns(new ModelMetadata(metadataProvider.Object, null, null, typeof(MyModel), null))
|
.Returns(new ModelMetadata(metadataProvider.Object, null, null, typeof(MyModel), null))
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
|
|
||||||
var binder = new Mock<IModelBinder>();
|
var binder = new Mock<IModelBinder>();
|
||||||
binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||||
.Returns(Task.FromResult(false));
|
.Returns(Task.FromResult<ModelBindingResult>(null));
|
||||||
var model = new MyModel();
|
var model = new MyModel();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await ModelBindingHelper.TryUpdateModelAsync(
|
var result = await ModelBindingHelper.TryUpdateModelAsync(
|
||||||
model,
|
model,
|
||||||
null,
|
null,
|
||||||
Mock.Of<HttpContext>(),
|
Mock.Of<HttpContext>(),
|
||||||
new ModelStateDictionary(),
|
new ModelStateDictionary(),
|
||||||
metadataProvider.Object,
|
metadataProvider.Object,
|
||||||
GetCompositeBinder(binder.Object),
|
GetCompositeBinder(binder.Object),
|
||||||
Mock.Of<IValueProvider>(),
|
Mock.Of<IValueProvider>(),
|
||||||
Mock.Of<IModelValidatorProvider>());
|
new DefaultObjectValidator(Mock.Of<IValidationExcludeFiltersProvider>(), metadataProvider.Object),
|
||||||
|
Mock.Of<IModelValidatorProvider>());
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(result);
|
Assert.False(result);
|
||||||
|
|
@ -71,17 +72,19 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
{ "", null }
|
{ "", null }
|
||||||
};
|
};
|
||||||
var valueProvider = new TestValueProvider(values);
|
var valueProvider = new TestValueProvider(values);
|
||||||
|
var modelMetadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await ModelBindingHelper.TryUpdateModelAsync(
|
var result = await ModelBindingHelper.TryUpdateModelAsync(
|
||||||
model,
|
model,
|
||||||
"",
|
"",
|
||||||
Mock.Of<HttpContext>(),
|
Mock.Of<HttpContext>(),
|
||||||
modelStateDictionary,
|
modelStateDictionary,
|
||||||
new DataAnnotationsModelMetadataProvider(),
|
modelMetadataProvider,
|
||||||
GetCompositeBinder(binders),
|
GetCompositeBinder(binders),
|
||||||
valueProvider,
|
valueProvider,
|
||||||
validator);
|
new DefaultObjectValidator(Mock.Of<IValidationExcludeFiltersProvider>(), modelMetadataProvider),
|
||||||
|
validator);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(result);
|
Assert.False(result);
|
||||||
|
|
@ -109,17 +112,19 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
{ "MyProperty", "MyPropertyValue" }
|
{ "MyProperty", "MyPropertyValue" }
|
||||||
};
|
};
|
||||||
var valueProvider = new TestValueProvider(values);
|
var valueProvider = new TestValueProvider(values);
|
||||||
|
var metadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await ModelBindingHelper.TryUpdateModelAsync(
|
var result = await ModelBindingHelper.TryUpdateModelAsync(
|
||||||
model,
|
model,
|
||||||
"",
|
"",
|
||||||
Mock.Of<HttpContext>(),
|
Mock.Of<HttpContext>(),
|
||||||
modelStateDictionary,
|
modelStateDictionary,
|
||||||
new DataAnnotationsModelMetadataProvider(),
|
metadataProvider,
|
||||||
GetCompositeBinder(binders),
|
GetCompositeBinder(binders),
|
||||||
valueProvider,
|
valueProvider,
|
||||||
validator);
|
new DefaultObjectValidator(Mock.Of<IValidationExcludeFiltersProvider>(), metadataProvider),
|
||||||
|
validator);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
|
|
@ -131,27 +136,28 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var metadataProvider = new Mock<IModelMetadataProvider>();
|
var metadataProvider = new Mock<IModelMetadataProvider>();
|
||||||
metadataProvider.Setup(m => m.GetMetadataForType(null, It.IsAny<Type>()))
|
metadataProvider.Setup(m => m.GetMetadataForType(It.IsAny<Func<object>>(), It.IsAny<Type>()))
|
||||||
.Returns(new ModelMetadata(metadataProvider.Object, null, null, typeof(MyModel), null))
|
.Returns(new ModelMetadata(metadataProvider.Object, null, null, typeof(MyModel), null))
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
|
|
||||||
var binder = new Mock<IModelBinder>();
|
var binder = new Mock<IModelBinder>();
|
||||||
binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||||
.Returns(Task.FromResult(false));
|
.Returns(Task.FromResult<ModelBindingResult>(null));
|
||||||
var model = new MyModel();
|
var model = new MyModel();
|
||||||
Func<ModelBindingContext, string, bool> includePredicate =
|
Func<ModelBindingContext, string, bool> includePredicate =
|
||||||
(context, propertyName) => true;
|
(context, propertyName) => true;
|
||||||
// Act
|
// Act
|
||||||
var result = await ModelBindingHelper.TryUpdateModelAsync(
|
var result = await ModelBindingHelper.TryUpdateModelAsync(
|
||||||
model,
|
model,
|
||||||
null,
|
null,
|
||||||
Mock.Of<HttpContext>(),
|
Mock.Of<HttpContext>(),
|
||||||
new ModelStateDictionary(),
|
new ModelStateDictionary(),
|
||||||
metadataProvider.Object,
|
metadataProvider.Object,
|
||||||
GetCompositeBinder(binder.Object),
|
GetCompositeBinder(binder.Object),
|
||||||
Mock.Of<IValueProvider>(),
|
Mock.Of<IValueProvider>(),
|
||||||
Mock.Of<IModelValidatorProvider>(),
|
Mock.Of<IObjectModelValidator>(),
|
||||||
includePredicate);
|
Mock.Of<IModelValidatorProvider>(),
|
||||||
|
includePredicate);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(result);
|
Assert.False(result);
|
||||||
|
|
@ -194,18 +200,20 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
string.Equals(propertyName, "MyProperty", StringComparison.OrdinalIgnoreCase);
|
string.Equals(propertyName, "MyProperty", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
var valueProvider = new TestValueProvider(values);
|
var valueProvider = new TestValueProvider(values);
|
||||||
|
var metadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await ModelBindingHelper.TryUpdateModelAsync(
|
var result = await ModelBindingHelper.TryUpdateModelAsync(
|
||||||
model,
|
model,
|
||||||
"",
|
"",
|
||||||
Mock.Of<HttpContext>(),
|
Mock.Of<HttpContext>(),
|
||||||
modelStateDictionary,
|
modelStateDictionary,
|
||||||
new DataAnnotationsModelMetadataProvider(),
|
metadataProvider,
|
||||||
GetCompositeBinder(binders),
|
GetCompositeBinder(binders),
|
||||||
valueProvider,
|
valueProvider,
|
||||||
validator,
|
new DefaultObjectValidator(Mock.Of<IValidationExcludeFiltersProvider>(), metadataProvider),
|
||||||
includePredicate);
|
validator,
|
||||||
|
includePredicate);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
|
|
@ -219,13 +227,13 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var metadataProvider = new Mock<IModelMetadataProvider>();
|
var metadataProvider = new Mock<IModelMetadataProvider>();
|
||||||
metadataProvider.Setup(m => m.GetMetadataForType(null, It.IsAny<Type>()))
|
metadataProvider.Setup(m => m.GetMetadataForType(It.IsAny<Func<object>>(), It.IsAny<Type>()))
|
||||||
.Returns(new ModelMetadata(metadataProvider.Object, null, null, typeof(MyModel), null))
|
.Returns(new ModelMetadata(metadataProvider.Object, null, null, typeof(MyModel), null))
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
|
|
||||||
var binder = new Mock<IModelBinder>();
|
var binder = new Mock<IModelBinder>();
|
||||||
binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||||
.Returns(Task.FromResult(false));
|
.Returns(Task.FromResult<ModelBindingResult>(null));
|
||||||
var model = new MyModel();
|
var model = new MyModel();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -237,6 +245,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
metadataProvider.Object,
|
metadataProvider.Object,
|
||||||
GetCompositeBinder(binder.Object),
|
GetCompositeBinder(binder.Object),
|
||||||
Mock.Of<IValueProvider>(),
|
Mock.Of<IValueProvider>(),
|
||||||
|
Mock.Of<IObjectModelValidator>(),
|
||||||
Mock.Of<IModelValidatorProvider>(),
|
Mock.Of<IModelValidatorProvider>(),
|
||||||
m => m.IncludedProperty );
|
m => m.IncludedProperty );
|
||||||
|
|
||||||
|
|
@ -277,19 +286,21 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
};
|
};
|
||||||
|
|
||||||
var valueProvider = new TestValueProvider(values);
|
var valueProvider = new TestValueProvider(values);
|
||||||
|
var metadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await ModelBindingHelper.TryUpdateModelAsync(
|
var result = await ModelBindingHelper.TryUpdateModelAsync(
|
||||||
model,
|
model,
|
||||||
"",
|
"",
|
||||||
Mock.Of<HttpContext>(),
|
Mock.Of<HttpContext>(),
|
||||||
modelStateDictionary,
|
modelStateDictionary,
|
||||||
new DataAnnotationsModelMetadataProvider(),
|
new DataAnnotationsModelMetadataProvider(),
|
||||||
GetCompositeBinder(binders),
|
GetCompositeBinder(binders),
|
||||||
valueProvider,
|
valueProvider,
|
||||||
validator,
|
new DefaultObjectValidator(Mock.Of<IValidationExcludeFiltersProvider>(), metadataProvider),
|
||||||
m => m.IncludedProperty,
|
validator,
|
||||||
m => m.MyProperty);
|
m => m.IncludedProperty,
|
||||||
|
m => m.MyProperty);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
|
|
@ -327,17 +338,19 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
};
|
};
|
||||||
|
|
||||||
var valueProvider = new TestValueProvider(values);
|
var valueProvider = new TestValueProvider(values);
|
||||||
|
var metadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await ModelBindingHelper.TryUpdateModelAsync(
|
var result = await ModelBindingHelper.TryUpdateModelAsync(
|
||||||
model,
|
model,
|
||||||
"",
|
"",
|
||||||
Mock.Of<HttpContext>(),
|
Mock.Of<HttpContext>(),
|
||||||
modelStateDictionary,
|
modelStateDictionary,
|
||||||
new DataAnnotationsModelMetadataProvider(),
|
metadataProvider,
|
||||||
GetCompositeBinder(binders),
|
GetCompositeBinder(binders),
|
||||||
valueProvider,
|
valueProvider,
|
||||||
validator);
|
new DefaultObjectValidator(Mock.Of<IValidationExcludeFiltersProvider>(), metadataProvider),
|
||||||
|
validator);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
// Includes everything.
|
// Includes everything.
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
|
|
||||||
var responseContent = await response.Content.ReadAsStringAsync();
|
var responseContent = await response.Content.ReadAsStringAsync();
|
||||||
var responseObject = JsonConvert.DeserializeObject<Dictionary<string, ErrorCollection>>(responseContent);
|
var responseObject = JsonConvert.DeserializeObject<Dictionary<string, ErrorCollection>>(responseContent);
|
||||||
var errorCollection = Assert.Single(responseObject);
|
var errorCollection = Assert.Single(responseObject, modelState => modelState.Value.Errors.Any());
|
||||||
var error = Assert.Single(errorCollection.Value.Errors);
|
var error = Assert.Single(errorCollection.Value.Errors);
|
||||||
Assert.Equal(expectedModelStateErrorMessage, error.ErrorMessage);
|
Assert.Equal(expectedModelStateErrorMessage, error.ErrorMessage);
|
||||||
|
|
||||||
|
|
@ -164,7 +164,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
|
|
||||||
var responseContent = await response.Content.ReadAsStringAsync();
|
var responseContent = await response.Content.ReadAsStringAsync();
|
||||||
var responseObject = JsonConvert.DeserializeObject<Dictionary<string, ErrorCollection>>(responseContent);
|
var responseObject = JsonConvert.DeserializeObject<Dictionary<string, ErrorCollection>>(responseContent);
|
||||||
var errorCollection = Assert.Single(responseObject);
|
var errorCollection = Assert.Single(responseObject, modelState => modelState.Value.Errors.Any());
|
||||||
var error = Assert.Single(errorCollection.Value.Errors);
|
var error = Assert.Single(errorCollection.Value.Errors);
|
||||||
Assert.Equal(expectedModelStateErrorMessage, error.ErrorMessage);
|
Assert.Equal(expectedModelStateErrorMessage, error.ErrorMessage);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,45 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
private readonly IServiceProvider _services = TestHelper.CreateServices("ModelBindingWebSite");
|
private readonly IServiceProvider _services = TestHelper.CreateServices("ModelBindingWebSite");
|
||||||
private readonly Action<IApplicationBuilder> _app = new ModelBindingWebSite.Startup().Configure;
|
private readonly Action<IApplicationBuilder> _app = new ModelBindingWebSite.Startup().Configure;
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task TypeBasedExclusion_ForBodyAndNonBodyBoundModels()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Make sure the body object gets created with an invalid zip.
|
||||||
|
var input = "{\"OfficeAddress.Zip\":\"45\"}";
|
||||||
|
var content = new StringContent(input, Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
// Make sure the non body based object gets created with an invalid zip.
|
||||||
|
var response = await client.PostAsync(
|
||||||
|
"http://localhost/Validation/SkipValidation?ShippingAddresses[0].Zip=45&HomeAddress.Zip=46", content);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var stringValue = await response.Content.ReadAsStringAsync();
|
||||||
|
var isModelStateValid = JsonConvert.DeserializeObject<bool>(stringValue);
|
||||||
|
Assert.True(isModelStateValid);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ModelValidation_DoesNotValidate_AnAlreadyValidatedObject()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync(
|
||||||
|
"http://localhost/Validation/AvoidRecursive?Name=selfish");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var stringValue = await response.Content.ReadAsStringAsync();
|
||||||
|
var isModelStateValid = JsonConvert.DeserializeObject<bool>(stringValue);
|
||||||
|
Assert.True(isModelStateValid);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("RestrictValueProvidersUsingFromRoute", "valueFromRoute")]
|
[InlineData("RestrictValueProvidersUsingFromRoute", "valueFromRoute")]
|
||||||
[InlineData("RestrictValueProvidersUsingFromQuery", "valueFromQuery")]
|
[InlineData("RestrictValueProvidersUsingFromQuery", "valueFromQuery")]
|
||||||
|
|
@ -1012,7 +1051,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
// Act
|
// Act
|
||||||
var response = await client.GetStringAsync("http://localhost/TryUpdateModel/" +
|
var response = await client.GetStringAsync("http://localhost/TryUpdateModel/" +
|
||||||
"CreateAndUpdateUser" +
|
"CreateAndUpdateUser" +
|
||||||
"?RegisterationMonth=March");
|
"?RegistedeburationMonth=March");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var result = JsonConvert.DeserializeObject<bool>(response);
|
var result = JsonConvert.DeserializeObject<bool>(response);
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var retVal = await binder.BindModelAsync(bindingContext);
|
var retVal = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(retVal);
|
Assert.NotNull(retVal);
|
||||||
|
|
||||||
int[] array = bindingContext.Model as int[];
|
int[] array = retVal.Model as int[];
|
||||||
Assert.Equal(new[] { 42, 84 }, array);
|
Assert.Equal(new[] { 42, 84 }, array);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,7 +43,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var bound = await binder.BindModelAsync(bindingContext);
|
var bound = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(bound);
|
Assert.Null(bound);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -62,7 +62,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var bound = await binder.BindModelAsync(bindingContext);
|
var bound = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(bound);
|
Assert.Null(bound);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IModelBinder CreateIntBinder()
|
private static IModelBinder CreateIntBinder()
|
||||||
|
|
@ -75,10 +75,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var value = await mbc.ValueProvider.GetValueAsync(mbc.ModelName);
|
var value = await mbc.ValueProvider.GetValueAsync(mbc.ModelName);
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
mbc.Model = value.ConvertTo(mbc.ModelType);
|
var model = value.ConvertTo(mbc.ModelType);
|
||||||
return true;
|
return new ModelBindingResult(model, key: null, isModelSet: true);
|
||||||
}
|
}
|
||||||
return false;
|
return null;
|
||||||
});
|
});
|
||||||
return mockIntBinder.Object;
|
return mockIntBinder.Object;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var binderResult = await binder.BindModelAsync(bindingContext);
|
var binderResult = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(binderResult);
|
Assert.Null(binderResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -43,7 +43,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var binderResult = await binder.BindModelAsync(bindingContext);
|
var binderResult = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(binderResult);
|
Assert.NotNull(binderResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -67,8 +67,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var binderResult = await binder.BindModelAsync(bindingContext);
|
var binderResult = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var p = (Person)bindingContext.Model;
|
var p = (Person)binderResult.Model;
|
||||||
Assert.True(binderResult);
|
|
||||||
Assert.Equal(model.Age, p.Age);
|
Assert.Equal(model.Age, p.Age);
|
||||||
Assert.Equal(model.Name, p.Name);
|
Assert.Equal(model.Name, p.Name);
|
||||||
}
|
}
|
||||||
|
|
@ -95,8 +94,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var binderResult = await binder.BindModelAsync(bindingContext);
|
var binderResult = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var p = (Person)bindingContext.Model;
|
var p = (Person)binderResult.Model;
|
||||||
Assert.True(binderResult);
|
|
||||||
Assert.Equal(model.Age, p.Age);
|
Assert.Equal(model.Age, p.Age);
|
||||||
Assert.Equal(model.Name, p.Name);
|
Assert.Equal(model.Name, p.Name);
|
||||||
}
|
}
|
||||||
|
|
@ -152,9 +150,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
|
|
||||||
private class FalseModelBinder : IModelBinder
|
private class FalseModelBinder : IModelBinder
|
||||||
{
|
{
|
||||||
public Task<bool> BindModelAsync(ModelBindingContext bindingContext)
|
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
return Task.FromResult(false);
|
return Task.FromResult<ModelBindingResult>(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -167,10 +165,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
_model = new Person();
|
_model = new Person();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<bool> BindModelAsync(ModelBindingContext bindingContext)
|
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
bindingContext.Model = _model;
|
return Task.FromResult(new ModelBindingResult(_model, bindingContext.ModelName, true));
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var result = await binder.BindModelAsync(context);
|
var result = await binder.BindModelAsync(context);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(result);
|
Assert.Null(result);
|
||||||
Assert.False(binder.WasBindModelCoreCalled);
|
Assert.False(binder.WasBindModelCoreCalled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,7 +69,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var result = await binder.BindModelAsync(context);
|
var result = await binder.BindModelAsync(context);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(result);
|
Assert.Null(result);
|
||||||
Assert.False(binder.WasBindModelCoreCalled);
|
Assert.False(binder.WasBindModelCoreCalled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,7 +93,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var result = await binder.BindModelAsync(context);
|
var result = await binder.BindModelAsync(context);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result);
|
Assert.NotNull(result);
|
||||||
|
Assert.True(result.IsModelSet);
|
||||||
Assert.True(binder.WasBindModelCoreCalled);
|
Assert.True(binder.WasBindModelCoreCalled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,10 +107,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Task BindModelCoreAsync([NotNull] ModelBindingContext bindingContext)
|
protected override Task<ModelBindingResult> BindModelCoreAsync([NotNull] ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
WasBindModelCoreCalled = true;
|
WasBindModelCoreCalled = true;
|
||||||
return Task.FromResult(true);
|
return Task.FromResult(new ModelBindingResult(null, bindingContext.ModelName, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var binderResult = await binder.BindModelAsync(bindingContext);
|
var binderResult = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(binderResult);
|
Assert.Null(binderResult);
|
||||||
Assert.Null(bindingContext.Model);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -50,8 +49,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var binderResult = await binder.BindModelAsync(bindingContext);
|
var binderResult = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(binderResult);
|
Assert.NotNull(binderResult);
|
||||||
var bytes = Assert.IsType<byte[]>(bindingContext.Model);
|
var bytes = Assert.IsType<byte[]>(binderResult.Model);
|
||||||
Assert.Equal(new byte[] { 23, 43, 53 }, bytes);
|
Assert.Equal(new byte[] { 23, 43, 53 }, bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,7 +74,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var binderResult = await binder.BindModelAsync(bindingContext);
|
var binderResult = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(binderResult);
|
Assert.NotNull(binderResult);
|
||||||
Assert.False(bindingContext.ModelState.IsValid);
|
Assert.False(bindingContext.ModelState.IsValid);
|
||||||
var error = Assert.Single(bindingContext.ModelState["foo"].Errors);
|
var error = Assert.Single(bindingContext.ModelState["foo"].Errors);
|
||||||
Assert.Equal(expected, error.ErrorMessage);
|
Assert.Equal(expected, error.ErrorMessage);
|
||||||
|
|
@ -97,7 +96,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var binderResult = await binder.BindModelAsync(bindingContext);
|
var binderResult = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(binderResult);
|
Assert.Null(binderResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -111,7 +110,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var binderResult = await binder.BindModelAsync(bindingContext);
|
var binderResult = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(binderResult);
|
Assert.Null(binderResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ModelBindingContext GetBindingContext(IValueProvider valueProvider, Type modelType)
|
private static ModelBindingContext GetBindingContext(IValueProvider valueProvider, Type modelType)
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var bound = await binder.BindModelAsync(bindingContext);
|
var bound = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(bound);
|
Assert.NotNull(bound);
|
||||||
Assert.Equal(bindingContext.OperationBindingContext.HttpContext.RequestAborted, bindingContext.Model);
|
Assert.Equal(bindingContext.OperationBindingContext.HttpContext.RequestAborted, bound.Model);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -40,8 +40,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var bound = await binder.BindModelAsync(bindingContext);
|
var bound = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(bound);
|
Assert.Null(bound);
|
||||||
Assert.Null(bindingContext.Model);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ModelBindingContext GetBindingContext(Type modelType)
|
private static ModelBindingContext GetBindingContext(Type modelType)
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(new[] { 42, 0, 200 }, boundCollection.ToArray());
|
Assert.Equal(new[] { 42, 0, 200 }, boundCollection.ToArray());
|
||||||
Assert.Equal(new[] { "someName[foo]", "someName[baz]" }, bindingContext.ValidationNode.ChildNodes.Select(o => o.ModelStateKey).ToArray());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -55,7 +54,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(new[] { 42, 100 }, boundCollection.ToArray());
|
Assert.Equal(new[] { 42, 100 }, boundCollection.ToArray());
|
||||||
Assert.Equal(new[] { "someName[0]", "someName[1]" }, bindingContext.ValidationNode.ChildNodes.Select(o => o.ModelStateKey).ToArray());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -73,10 +71,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var binder = new CollectionModelBinder<int>();
|
var binder = new CollectionModelBinder<int>();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
bool retVal = await binder.BindModelAsync(bindingContext);
|
var retVal = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(new[] { 42, 100, 200 }, ((List<int>)bindingContext.Model).ToArray());
|
Assert.Equal(new[] { 42, 100, 200 }, ((List<int>)retVal.Model).ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -91,11 +89,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var binder = new CollectionModelBinder<int>();
|
var binder = new CollectionModelBinder<int>();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
bool retVal = await binder.BindModelAsync(bindingContext);
|
var retVal = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(retVal);
|
Assert.NotNull(retVal);
|
||||||
Assert.Equal(new[] { 42, 100, 200 }, ((List<int>)bindingContext.Model).ToArray());
|
Assert.Equal(new[] { 42, 100, 200 }, ((List<int>)retVal.Model).ToArray());
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -134,15 +132,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var culture = new CultureInfo("fr-FR");
|
var culture = new CultureInfo("fr-FR");
|
||||||
var bindingContext = GetModelBindingContext(new SimpleHttpValueProvider());
|
var bindingContext = GetModelBindingContext(new SimpleHttpValueProvider());
|
||||||
|
|
||||||
ModelValidationNode childValidationNode = null;
|
|
||||||
Mock.Get<IModelBinder>(bindingContext.OperationBindingContext.ModelBinder)
|
Mock.Get<IModelBinder>(bindingContext.OperationBindingContext.ModelBinder)
|
||||||
.Setup(o => o.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
.Setup(o => o.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||||
.Returns((ModelBindingContext mbc) =>
|
.Returns((ModelBindingContext mbc) =>
|
||||||
{
|
{
|
||||||
Assert.Equal("someName", mbc.ModelName);
|
Assert.Equal("someName", mbc.ModelName);
|
||||||
childValidationNode = mbc.ValidationNode;
|
return Task.FromResult(new ModelBindingResult(42, mbc.ModelName, true));
|
||||||
mbc.Model = 42;
|
|
||||||
return Task.FromResult(true);
|
|
||||||
});
|
});
|
||||||
var modelBinder = new CollectionModelBinder<int>();
|
var modelBinder = new CollectionModelBinder<int>();
|
||||||
|
|
||||||
|
|
@ -151,7 +146,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(new[] { 42 }, boundCollection.ToArray());
|
Assert.Equal(new[] { 42 }, boundCollection.ToArray());
|
||||||
Assert.Equal(new[] { childValidationNode }, bindingContext.ValidationNode.ChildNodes.ToArray());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ModelBindingContext GetModelBindingContext(IValueProvider valueProvider)
|
private static ModelBindingContext GetModelBindingContext(IValueProvider valueProvider)
|
||||||
|
|
@ -181,10 +175,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var value = await mbc.ValueProvider.GetValueAsync(mbc.ModelName);
|
var value = await mbc.ValueProvider.GetValueAsync(mbc.ModelName);
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
mbc.Model = value.ConvertTo(mbc.ModelType);
|
var model = value.ConvertTo(mbc.ModelType);
|
||||||
return true;
|
return new ModelBindingResult(model, mbc.ModelName, true);
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
return mockIntBinder.Object;
|
return mockIntBinder.Object;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|
||||||
{
|
|
||||||
public class ComplexModelDtoResultTest
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void Constructor_SetsProperties()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var validationNode = GetValidationNode();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var result = new ComplexModelDtoResult(
|
|
||||||
"some string",
|
|
||||||
isModelBound: true,
|
|
||||||
validationNode: validationNode);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal("some string", result.Model);
|
|
||||||
Assert.True(result.IsModelBound);
|
|
||||||
Assert.Equal(validationNode, result.ValidationNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ModelValidationNode GetValidationNode()
|
|
||||||
{
|
|
||||||
var provider = new EmptyModelMetadataProvider();
|
|
||||||
var metadata = provider.GetMetadataForType(null, typeof(object));
|
|
||||||
return new ModelValidationNode(metadata, "someKey");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -15,11 +15,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
public class CompositeModelBinderTest
|
public class CompositeModelBinderTest
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task BindModel_SuccessfulBind_RunsValidationAndReturnsModel()
|
public async Task BindModel_SuccessfulBind_ReturnsModel()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var validationCalled = false;
|
|
||||||
|
|
||||||
var bindingContext = new ModelBindingContext
|
var bindingContext = new ModelBindingContext
|
||||||
{
|
{
|
||||||
FallbackToEmptyPrefix = true,
|
FallbackToEmptyPrefix = true,
|
||||||
|
|
@ -46,28 +44,25 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
Assert.Equal("someName", context.ModelName);
|
Assert.Equal("someName", context.ModelName);
|
||||||
Assert.Same(bindingContext.ValueProvider, context.ValueProvider);
|
Assert.Same(bindingContext.ValueProvider, context.ValueProvider);
|
||||||
|
|
||||||
context.Model = 42;
|
|
||||||
bindingContext.ValidationNode.Validating += delegate { validationCalled = true; };
|
return Task.FromResult(
|
||||||
return Task.FromResult(true);
|
new ModelBindingResult(model: 42, key: "someName", isModelSet: true));
|
||||||
});
|
});
|
||||||
var shimBinder = CreateCompositeBinder(mockIntBinder.Object);
|
var shimBinder = CreateCompositeBinder(mockIntBinder.Object);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var isBound = await shimBinder.BindModelAsync(bindingContext);
|
var result = await shimBinder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(isBound);
|
Assert.NotNull(result);
|
||||||
Assert.Equal(42, bindingContext.Model);
|
Assert.True(result.IsModelSet);
|
||||||
|
Assert.Equal(42, result.Model);
|
||||||
Assert.True(validationCalled);
|
|
||||||
Assert.True(bindingContext.ModelState.IsValid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task BindModel_SuccessfulBind_ComplexTypeFallback_RunsValidationAndReturnsModel()
|
public async Task BindModel_SuccessfulBind_ComplexTypeFallback_ReturnsModel()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var validationCalled = false;
|
|
||||||
var expectedModel = new List<int> { 1, 2, 3, 4, 5 };
|
var expectedModel = new List<int> { 1, 2, 3, 4, 5 };
|
||||||
|
|
||||||
var bindingContext = new ModelBindingContext
|
var bindingContext = new ModelBindingContext
|
||||||
|
|
@ -94,35 +89,32 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(mbc.ModelName))
|
if (!string.IsNullOrEmpty(mbc.ModelName))
|
||||||
{
|
{
|
||||||
return Task.FromResult(false);
|
return Task.FromResult<ModelBindingResult>(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.Same(bindingContext.ModelMetadata, mbc.ModelMetadata);
|
Assert.Same(bindingContext.ModelMetadata, mbc.ModelMetadata);
|
||||||
Assert.Equal("", mbc.ModelName);
|
Assert.Equal("", mbc.ModelName);
|
||||||
Assert.Same(bindingContext.ValueProvider, mbc.ValueProvider);
|
Assert.Same(bindingContext.ValueProvider, mbc.ValueProvider);
|
||||||
|
|
||||||
mbc.Model = expectedModel;
|
return Task.FromResult(new ModelBindingResult(expectedModel, string.Empty, true));
|
||||||
mbc.ValidationNode.Validating += delegate { validationCalled = true; };
|
|
||||||
return Task.FromResult(true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var shimBinder = CreateCompositeBinder(mockIntBinder.Object);
|
var shimBinder = CreateCompositeBinder(mockIntBinder.Object);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var isBound = await shimBinder.BindModelAsync(bindingContext);
|
var result = await shimBinder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(isBound);
|
Assert.NotNull(result);
|
||||||
Assert.Equal(expectedModel, bindingContext.Model);
|
Assert.True(result.IsModelSet);
|
||||||
Assert.True(validationCalled);
|
Assert.Equal(string.Empty, result.Key);
|
||||||
Assert.True(bindingContext.ModelState.IsValid);
|
Assert.Equal(expectedModel, result.Model);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ModelBinder_ReturnsTrue_WithoutSettingValue_SkipsValidation()
|
public async Task ModelBinder_ReturnsTrue_WithoutSettingValue()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var validationCalled = false;
|
|
||||||
|
|
||||||
var bindingContext = new ModelBindingContext
|
var bindingContext = new ModelBindingContext
|
||||||
{
|
{
|
||||||
|
|
@ -143,31 +135,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var modelBinder = new Mock<IModelBinder>();
|
var modelBinder = new Mock<IModelBinder>();
|
||||||
modelBinder
|
modelBinder
|
||||||
.Setup(mb => mb.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
.Setup(mb => mb.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||||
.Callback<ModelBindingContext>(context =>
|
.Returns(Task.FromResult(new ModelBindingResult(null, "someName", false)));
|
||||||
{
|
|
||||||
context.ValidationNode.Validating += delegate { validationCalled = true; };
|
|
||||||
})
|
|
||||||
.Returns(Task.FromResult(true));
|
|
||||||
|
|
||||||
var composite = CreateCompositeBinder(modelBinder.Object);
|
var composite = CreateCompositeBinder(modelBinder.Object);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var isBound = await composite.BindModelAsync(bindingContext);
|
var result = await composite.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(isBound);
|
Assert.NotNull(result);
|
||||||
|
Assert.False(result.IsModelSet);
|
||||||
Assert.Null(bindingContext.Model);
|
Assert.Equal("someName", result.Key);
|
||||||
Assert.False(validationCalled);
|
Assert.Null(result.Model);
|
||||||
Assert.False(bindingContext.IsModelSet);
|
|
||||||
Assert.True(bindingContext.ModelState.IsValid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ModelBinder_ReturnsTrue_SetsNullValue_RunsValidation()
|
public async Task ModelBinder_ReturnsTrue_SetsNullValue_SetsModelStateKey()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var validationCalled = false;
|
|
||||||
|
|
||||||
var bindingContext = new ModelBindingContext
|
var bindingContext = new ModelBindingContext
|
||||||
{
|
{
|
||||||
|
|
@ -188,25 +173,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var modelBinder = new Mock<IModelBinder>();
|
var modelBinder = new Mock<IModelBinder>();
|
||||||
modelBinder
|
modelBinder
|
||||||
.Setup(mb => mb.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
.Setup(mb => mb.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||||
.Callback<ModelBindingContext>(context =>
|
.Returns(Task.FromResult(new ModelBindingResult(null, "someName", true)));
|
||||||
{
|
|
||||||
context.Model = null;
|
|
||||||
context.ValidationNode.Validating += delegate { validationCalled = true; };
|
|
||||||
})
|
|
||||||
.Returns(Task.FromResult(true));
|
|
||||||
|
|
||||||
var composite = CreateCompositeBinder(modelBinder.Object);
|
var composite = CreateCompositeBinder(modelBinder.Object);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var isBound = await composite.BindModelAsync(bindingContext);
|
var result = await composite.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(isBound);
|
Assert.NotNull(result);
|
||||||
|
Assert.True(result.IsModelSet);
|
||||||
Assert.Null(bindingContext.Model);
|
Assert.Equal("someName", result.Key);
|
||||||
Assert.True(validationCalled);
|
Assert.Null(result.Model);
|
||||||
Assert.True(bindingContext.IsModelSet);
|
|
||||||
Assert.False(bindingContext.ModelState.IsValid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -215,7 +193,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
// Arrange
|
// Arrange
|
||||||
var mockListBinder = new Mock<IModelBinder>();
|
var mockListBinder = new Mock<IModelBinder>();
|
||||||
mockListBinder.Setup(o => o.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
mockListBinder.Setup(o => o.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||||
.Returns(Task.FromResult(false))
|
.Returns(Task.FromResult<ModelBindingResult>(null))
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
|
|
||||||
var shimBinder = mockListBinder.Object;
|
var shimBinder = mockListBinder.Object;
|
||||||
|
|
@ -227,11 +205,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var isBound = await shimBinder.BindModelAsync(bindingContext);
|
var result = await shimBinder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(isBound);
|
Assert.Null(result);
|
||||||
Assert.Null(bindingContext.Model);
|
|
||||||
Assert.True(bindingContext.ModelState.IsValid);
|
Assert.True(bindingContext.ModelState.IsValid);
|
||||||
mockListBinder.Verify();
|
mockListBinder.Verify();
|
||||||
}
|
}
|
||||||
|
|
@ -252,11 +229,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var isBound = await shimBinder.BindModelAsync(bindingContext);
|
var result = await shimBinder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(isBound);
|
Assert.Null(result);
|
||||||
Assert.Null(bindingContext.Model);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -273,11 +249,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var bindingContext = CreateBindingContext(binder, valueProvider, typeof(SimplePropertiesModel));
|
var bindingContext = CreateBindingContext(binder, valueProvider, typeof(SimplePropertiesModel));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var isBound = await binder.BindModelAsync(bindingContext);
|
var result = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(isBound);
|
Assert.NotNull(result);
|
||||||
var model = Assert.IsType<SimplePropertiesModel>(bindingContext.Model);
|
var model = Assert.IsType<SimplePropertiesModel>(result.Model);
|
||||||
Assert.Equal("firstName-value", model.FirstName);
|
Assert.Equal("firstName-value", model.FirstName);
|
||||||
Assert.Equal("lastName-value", model.LastName);
|
Assert.Equal("lastName-value", model.LastName);
|
||||||
}
|
}
|
||||||
|
|
@ -302,11 +278,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var bindingContext = CreateBindingContext(binder, valueProvider, typeof(Person));
|
var bindingContext = CreateBindingContext(binder, valueProvider, typeof(Person));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var isBound = await binder.BindModelAsync(bindingContext);
|
var result = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(isBound);
|
Assert.NotNull(result);
|
||||||
var model = Assert.IsType<Person>(bindingContext.Model);
|
var model = Assert.IsType<Person>(result.Model);
|
||||||
Assert.Equal("firstName-value", model.FirstName);
|
Assert.Equal("firstName-value", model.FirstName);
|
||||||
Assert.Equal("lastName-value", model.LastName);
|
Assert.Equal("lastName-value", model.LastName);
|
||||||
Assert.Equal(2, model.Friends.Count);
|
Assert.Equal(2, model.Friends.Count);
|
||||||
|
|
@ -319,81 +295,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
Assert.Equal(new byte[] { 227, 233, 133, 121, 58, 119, 180, 241 }, model.Resume);
|
Assert.Equal(new byte[] { 227, 233, 133, 121, 58, 119, 180, 241 }, model.Resume);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task BindModel_WithDefaultValidators_ValidatesSubProperties()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var validatorProvider = new DataAnnotationsModelValidatorProvider();
|
|
||||||
var binder = CreateBinderWithDefaults();
|
|
||||||
var valueProvider = new SimpleHttpValueProvider
|
|
||||||
{
|
|
||||||
{ "user.password", "password-val" },
|
|
||||||
{ "user.confirmpassword", "not-password-val" },
|
|
||||||
};
|
|
||||||
var bindingContext = CreateBindingContext(binder, valueProvider, typeof(User), validatorProvider);
|
|
||||||
bindingContext.ModelName = "user";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var isBound = await binder.BindModelAsync(bindingContext);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
var error = Assert.Single(bindingContext.ModelState["user.confirmpassword"].Errors);
|
|
||||||
Assert.Equal("'ConfirmPassword' and 'Password' do not match.", error.ErrorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task BindModel_WithDefaultValidators_ValidatesInstance()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var validatorProvider = new DataAnnotationsModelValidatorProvider();
|
|
||||||
var binder = CreateBinderWithDefaults();
|
|
||||||
var valueProvider = new SimpleHttpValueProvider
|
|
||||||
{
|
|
||||||
{ "user.password", "password" },
|
|
||||||
{ "user.confirmpassword", "password" },
|
|
||||||
};
|
|
||||||
var bindingContext = CreateBindingContext(binder, valueProvider, typeof(User), validatorProvider);
|
|
||||||
bindingContext.ModelName = "user";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var isBound = await binder.BindModelAsync(bindingContext);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
var error = Assert.Single(bindingContext.ModelState["user"].Errors);
|
|
||||||
Assert.Equal("Password does not meet complexity requirements.", error.ErrorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task BindModel_UsesTryAddModelError()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var validatorProvider = new DataAnnotationsModelValidatorProvider();
|
|
||||||
var binder = CreateBinderWithDefaults();
|
|
||||||
var valueProvider = new SimpleHttpValueProvider
|
|
||||||
{
|
|
||||||
{ "user.password", "password" },
|
|
||||||
{ "user.confirmpassword", "password2" },
|
|
||||||
};
|
|
||||||
var bindingContext = CreateBindingContext(binder, valueProvider, typeof(User), validatorProvider);
|
|
||||||
bindingContext.ModelState.MaxAllowedErrors = 2;
|
|
||||||
bindingContext.ModelState.AddModelError("key1", "error1");
|
|
||||||
bindingContext.ModelName = "user";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await binder.BindModelAsync(bindingContext);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
var modelState = bindingContext.ModelState["user.confirmpassword"];
|
|
||||||
Assert.Empty(modelState.Errors);
|
|
||||||
|
|
||||||
modelState = bindingContext.ModelState["user"];
|
|
||||||
Assert.Empty(modelState.Errors);
|
|
||||||
|
|
||||||
var error = Assert.Single(bindingContext.ModelState[""].Errors);
|
|
||||||
Assert.IsType<TooManyModelErrorsException>(error.Exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static ModelBindingContext CreateBindingContext(IModelBinder binder,
|
private static ModelBindingContext CreateBindingContext(IModelBinder binder,
|
||||||
IValueProvider valueProvider,
|
IValueProvider valueProvider,
|
||||||
Type type,
|
Type type,
|
||||||
|
|
|
||||||
|
|
@ -34,12 +34,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var binder = new DictionaryModelBinder<int, string>();
|
var binder = new DictionaryModelBinder<int, string>();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
bool retVal = await binder.BindModelAsync(bindingContext);
|
var retVal = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(retVal);
|
Assert.NotNull(retVal);
|
||||||
|
|
||||||
var dictionary = Assert.IsAssignableFrom<IDictionary<int, string>>(bindingContext.Model);
|
var dictionary = Assert.IsAssignableFrom<IDictionary<int, string>>(retVal.Model);
|
||||||
Assert.NotNull(dictionary);
|
Assert.NotNull(dictionary);
|
||||||
Assert.Equal(2, dictionary.Count);
|
Assert.Equal(2, dictionary.Count);
|
||||||
Assert.Equal("forty-two", dictionary[42]);
|
Assert.Equal("forty-two", dictionary[42]);
|
||||||
|
|
@ -56,10 +56,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var value = await mbc.ValueProvider.GetValueAsync(mbc.ModelName);
|
var value = await mbc.ValueProvider.GetValueAsync(mbc.ModelName);
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
mbc.Model = value.ConvertTo(mbc.ModelType);
|
var model = value.ConvertTo(mbc.ModelType);
|
||||||
return true;
|
return new ModelBindingResult(model, key: null, isModelSet: true);
|
||||||
}
|
}
|
||||||
return false;
|
return null;
|
||||||
});
|
});
|
||||||
return mockKvpBinder.Object;
|
return mockKvpBinder.Object;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var result = await binder.BindModelAsync(bindingContext);
|
var result = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result);
|
Assert.NotNull(result);
|
||||||
var form = Assert.IsAssignableFrom<IFormCollection>(bindingContext.Model);
|
var form = Assert.IsAssignableFrom<IFormCollection>(result.Model);
|
||||||
Assert.Equal(2, form.Count);
|
Assert.Equal(2, form.Count);
|
||||||
Assert.Equal("value1", form["field1"]);
|
Assert.Equal("value1", form["field1"]);
|
||||||
Assert.Equal("value2", form["field2"]);
|
Assert.Equal("value2", form["field2"]);
|
||||||
|
|
@ -58,7 +58,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var result = await binder.BindModelAsync(bindingContext);
|
var result = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(result);
|
Assert.Null(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -73,9 +73,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var result = await binder.BindModelAsync(bindingContext);
|
var result = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result);
|
Assert.NotNull(result);
|
||||||
Assert.IsType(typeof(FormCollection), bindingContext.Model);
|
Assert.IsType(typeof(FormCollection), result.Model);
|
||||||
Assert.Empty((FormCollection)bindingContext.Model);
|
Assert.Empty((FormCollection)result.Model);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -95,8 +95,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var result = await binder.BindModelAsync(bindingContext);
|
var result = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result);
|
Assert.NotNull(result);
|
||||||
var form = Assert.IsAssignableFrom<IFormCollection>(bindingContext.Model);
|
var form = Assert.IsAssignableFrom<IFormCollection>(result.Model);
|
||||||
Assert.Equal(2, form.Count);
|
Assert.Equal(2, form.Count);
|
||||||
Assert.Equal("value1", form["field1"]);
|
Assert.Equal("value1", form["field1"]);
|
||||||
Assert.Equal("value2", form["field2"]);
|
Assert.Equal("value2", form["field2"]);
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var result = await binder.BindModelAsync(bindingContext);
|
var result = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result);
|
Assert.NotNull(result);
|
||||||
var files = Assert.IsAssignableFrom<IList<IFormFile>>(bindingContext.Model);
|
var files = Assert.IsAssignableFrom<IList<IFormFile>>(result.Model);
|
||||||
Assert.Equal(2, files.Count);
|
Assert.Equal(2, files.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,8 +52,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var result = await binder.BindModelAsync(bindingContext);
|
var result = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result);
|
Assert.NotNull(result);
|
||||||
var files = Assert.IsAssignableFrom<IList<IFormFile>>(bindingContext.Model);
|
var files = Assert.IsAssignableFrom<IList<IFormFile>>(result.Model);
|
||||||
Assert.Equal(2, files.Count);
|
Assert.Equal(2, files.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,8 +72,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var result = await binder.BindModelAsync(bindingContext);
|
var result = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result);
|
Assert.NotNull(result);
|
||||||
var file = Assert.IsAssignableFrom<IFormFile>(bindingContext.Model);
|
var file = Assert.IsAssignableFrom<IFormFile>(result.Model);
|
||||||
Assert.Equal("form-data; name=file; filename=file1.txt",
|
Assert.Equal("form-data; name=file; filename=file1.txt",
|
||||||
file.ContentDisposition);
|
file.ContentDisposition);
|
||||||
}
|
}
|
||||||
|
|
@ -91,8 +91,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var result = await binder.BindModelAsync(bindingContext);
|
var result = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result);
|
Assert.NotNull(result);
|
||||||
Assert.Null(bindingContext.Model);
|
Assert.Null(result.Model);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -109,8 +109,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var result = await binder.BindModelAsync(bindingContext);
|
var result = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result);
|
Assert.NotNull(result);
|
||||||
Assert.Null(bindingContext.Model);
|
Assert.Null(result.Model);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -127,8 +127,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var result = await binder.BindModelAsync(bindingContext);
|
var result = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result);
|
Assert.NotNull(result);
|
||||||
Assert.Null(bindingContext.Model);
|
Assert.Null(result.Model);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -145,8 +145,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var result = await binder.BindModelAsync(bindingContext);
|
var result = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result);
|
Assert.NotNull(result);
|
||||||
Assert.Null(bindingContext.Model);
|
Assert.Null(result.Model);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ModelBindingContext GetBindingContext(Type modelType, HttpContext httpContext)
|
private static ModelBindingContext GetBindingContext(Type modelType, HttpContext httpContext)
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var result = await binder.BindModelAsync(modelBindingContext);
|
var result = await binder.BindModelAsync(modelBindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result);
|
Assert.NotNull(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -47,8 +47,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var result = await binder.BindModelAsync(modelBindingContext);
|
var result = await binder.BindModelAsync(modelBindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result);
|
Assert.NotNull(result);
|
||||||
Assert.Equal(headerValue.Split(','), modelBindingContext.Model);
|
Assert.Equal(headerValue.Split(','), result.Model);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -68,8 +68,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var result = await binder.BindModelAsync(modelBindingContext);
|
var result = await binder.BindModelAsync(modelBindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result);
|
Assert.NotNull(result);
|
||||||
Assert.Equal(headerValue, modelBindingContext.Model);
|
Assert.Equal(headerValue, result.Model);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ModelBindingContext GetBindingContext(Type modelType)
|
private static ModelBindingContext GetBindingContext(Type modelType)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
#if ASPNET50
|
#if ASPNET50
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Moq;
|
using Moq;
|
||||||
|
|
@ -13,37 +15,70 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
public class KeyValuePairModelBinderTest
|
public class KeyValuePairModelBinderTest
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task BindModel_MissingKey_ReturnsFalse()
|
public async Task BindModel_MissingKey_ReturnsTrue_AndAddsModelValidationError()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var valueProvider = new SimpleHttpValueProvider();
|
var valueProvider = new SimpleHttpValueProvider();
|
||||||
var bindingContext = GetBindingContext(valueProvider, Mock.Of<IModelBinder>());
|
|
||||||
|
// Create string binder to create the value but not the key.
|
||||||
|
var bindingContext = GetBindingContext(valueProvider, CreateStringBinder());
|
||||||
var binder = new KeyValuePairModelBinder<int, string>();
|
var binder = new KeyValuePairModelBinder<int, string>();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
bool retVal = await binder.BindModelAsync(bindingContext);
|
var retVal = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(retVal);
|
Assert.NotNull(retVal);
|
||||||
Assert.Null(bindingContext.Model);
|
Assert.Null(retVal.Model);
|
||||||
Assert.Empty(bindingContext.ValidationNode.ChildNodes);
|
Assert.False(bindingContext.ModelState.IsValid);
|
||||||
|
Assert.Equal("someName", bindingContext.ModelName);
|
||||||
|
var error = Assert.Single(bindingContext.ModelState["someName.Key"].Errors);
|
||||||
|
Assert.Equal("A value is required.", error.ErrorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task BindModel_MissingValue_ReturnsTrue()
|
public async Task BindModel_MissingValue_ReturnsTrue_AndAddsModelValidationError()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var valueProvider = new SimpleHttpValueProvider();
|
var valueProvider = new SimpleHttpValueProvider();
|
||||||
var bindingContext = GetBindingContext(valueProvider);
|
|
||||||
|
// Create int binder to create the value but not the key.
|
||||||
|
var bindingContext = GetBindingContext(valueProvider, CreateIntBinder());
|
||||||
var binder = new KeyValuePairModelBinder<int, string>();
|
var binder = new KeyValuePairModelBinder<int, string>();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
bool retVal = await binder.BindModelAsync(bindingContext);
|
var retVal = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(retVal);
|
Assert.NotNull(retVal);
|
||||||
Assert.Null(bindingContext.Model);
|
Assert.Null(retVal.Model);
|
||||||
Assert.Equal(new[] { "someName.key" }, bindingContext.ValidationNode.ChildNodes.Select(n => n.ModelStateKey).ToArray());
|
Assert.False(bindingContext.ModelState.IsValid);
|
||||||
|
Assert.Equal("someName", bindingContext.ModelName);
|
||||||
|
Assert.Equal(bindingContext.ModelState["someName.Value"].Errors.First().ErrorMessage, "A value is required.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BindModel_MissingKeyAndMissingValue_DoNotAddModelStateError()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var valueProvider = new SimpleHttpValueProvider();
|
||||||
|
|
||||||
|
// Create int binder to create the value but not the key.
|
||||||
|
var bindingContext = GetBindingContext(valueProvider);
|
||||||
|
var mockBinder = new Mock<IModelBinder>();
|
||||||
|
mockBinder.Setup(o => o.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||||
|
.Returns(Task.FromResult<ModelBindingResult>(null));
|
||||||
|
|
||||||
|
bindingContext.OperationBindingContext.ModelBinder = mockBinder.Object;
|
||||||
|
var binder = new KeyValuePairModelBinder<int, string>();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var retVal = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(retVal);
|
||||||
|
Assert.True(bindingContext.ModelState.IsValid);
|
||||||
|
Assert.Equal(0, bindingContext.ModelState.ErrorCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -60,9 +95,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var retVal = await binder.BindModelAsync(bindingContext);
|
var retVal = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(retVal);
|
Assert.NotNull(retVal);
|
||||||
Assert.Equal(new KeyValuePair<int, string>(42, "some-value"), bindingContext.Model);
|
Assert.Equal(new KeyValuePair<int, string>(42, "some-value"), retVal.Model);
|
||||||
Assert.Equal(new[] { "someName.key", "someName.value" }, bindingContext.ValidationNode.ChildNodes.Select(n => n.ModelStateKey).ToArray());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -76,9 +110,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var retVal = await binder.TryBindStrongModel<int>(bindingContext, "key");
|
var retVal = await binder.TryBindStrongModel<int>(bindingContext, "key");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(retVal.Success);
|
Assert.True(retVal.IsModelSet);
|
||||||
Assert.Equal(42, retVal.Model);
|
Assert.Equal(42, retVal.Model);
|
||||||
Assert.Single(bindingContext.ValidationNode.ChildNodes);
|
|
||||||
Assert.Empty(bindingContext.ModelState);
|
Assert.Empty(bindingContext.ModelState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,7 +125,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
.Returns((ModelBindingContext mbc) =>
|
.Returns((ModelBindingContext mbc) =>
|
||||||
{
|
{
|
||||||
Assert.Equal("someName.key", mbc.ModelName);
|
Assert.Equal("someName.key", mbc.ModelName);
|
||||||
return Task.FromResult(true);
|
return Task.FromResult(new ModelBindingResult(null, string.Empty, true));
|
||||||
});
|
});
|
||||||
var bindingContext = GetBindingContext(new SimpleHttpValueProvider(), innerBinder.Object);
|
var bindingContext = GetBindingContext(new SimpleHttpValueProvider(), innerBinder.Object);
|
||||||
|
|
||||||
|
|
@ -103,25 +136,27 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var retVal = await binder.TryBindStrongModel<int>(bindingContext, "key");
|
var retVal = await binder.TryBindStrongModel<int>(bindingContext, "key");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(retVal.Success);
|
Assert.True(retVal.IsModelSet);
|
||||||
Assert.Equal(default(int), retVal.Model);
|
Assert.Null(retVal.Model);
|
||||||
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,
|
||||||
|
Type keyValuePairType = null)
|
||||||
{
|
{
|
||||||
var metataProvider = new EmptyModelMetadataProvider();
|
var metataProvider = new EmptyModelMetadataProvider();
|
||||||
var bindingContext = new ModelBindingContext
|
var bindingContext = new ModelBindingContext
|
||||||
{
|
{
|
||||||
ModelMetadata = metataProvider.GetMetadataForType(null, typeof(KeyValuePair<int, string>)),
|
ModelMetadata = metataProvider.GetMetadataForType(null, keyValuePairType ?? typeof(KeyValuePair<int, string>)),
|
||||||
ModelName = "someName",
|
ModelName = "someName",
|
||||||
ValueProvider = valueProvider,
|
ValueProvider = valueProvider,
|
||||||
OperationBindingContext = new OperationBindingContext
|
OperationBindingContext = new OperationBindingContext
|
||||||
{
|
{
|
||||||
ModelBinder = innerBinder ?? CreateIntBinder(),
|
ModelBinder = innerBinder ?? CreateIntBinder(),
|
||||||
MetadataProvider = metataProvider,
|
MetadataProvider = metataProvider,
|
||||||
ValidatorProvider = Mock.Of<IModelValidatorProvider>()
|
ValidatorProvider = new DataAnnotationsModelValidatorProvider()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return bindingContext;
|
return bindingContext;
|
||||||
|
|
@ -136,10 +171,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
{
|
{
|
||||||
if (mbc.ModelType == typeof(int))
|
if (mbc.ModelType == typeof(int))
|
||||||
{
|
{
|
||||||
mbc.Model = 42;
|
return Task.FromResult(new ModelBindingResult(42, mbc.ModelName, true));
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
}
|
||||||
return Task.FromResult(false);
|
return Task.FromResult<ModelBindingResult>(null);
|
||||||
});
|
});
|
||||||
return mockIntBinder.Object;
|
return mockIntBinder.Object;
|
||||||
}
|
}
|
||||||
|
|
@ -153,10 +187,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
{
|
{
|
||||||
if (mbc.ModelType == typeof(string))
|
if (mbc.ModelType == typeof(string))
|
||||||
{
|
{
|
||||||
mbc.Model = "some-value";
|
return Task.FromResult(new ModelBindingResult("some-value", mbc.ModelName, true));
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
}
|
||||||
return Task.FromResult(false);
|
return Task.FromResult<ModelBindingResult>(null);
|
||||||
});
|
});
|
||||||
return mockStringBinder.Object;
|
return mockStringBinder.Object;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,19 +34,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ModelProperty_ThrowsIfModelMetadataDoesNotExist()
|
public void ModelTypeAreFedFromModelMetadata()
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var bindingContext = new ModelBindingContext();
|
|
||||||
|
|
||||||
// Act & assert
|
|
||||||
ExceptionAssert.Throws<InvalidOperationException>(
|
|
||||||
() => bindingContext.Model = null,
|
|
||||||
"The ModelMetadata property must be set before accessing this property.");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ModelAndModelTypeAreFedFromModelMetadata()
|
|
||||||
{
|
{
|
||||||
// Act
|
// Act
|
||||||
var bindingContext = new ModelBindingContext
|
var bindingContext = new ModelBindingContext
|
||||||
|
|
@ -55,27 +43,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
};
|
};
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(42, bindingContext.Model);
|
|
||||||
Assert.Equal(typeof(int), bindingContext.ModelType);
|
Assert.Equal(typeof(int), bindingContext.ModelType);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ValidationNodeProperty_DefaultValues()
|
|
||||||
{
|
|
||||||
// Act
|
|
||||||
var bindingContext = new ModelBindingContext
|
|
||||||
{
|
|
||||||
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => 42, typeof(int)),
|
|
||||||
ModelName = "theInt"
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var validationNode = bindingContext.ValidationNode;
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.NotNull(validationNode);
|
|
||||||
Assert.Equal(bindingContext.ModelMetadata, validationNode.ModelMetadata);
|
|
||||||
Assert.Equal(bindingContext.ModelName, validationNode.ModelStateKey);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
|
{
|
||||||
|
public class ModelBindingResultTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_SetsProperties()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = new ModelBindingResult(
|
||||||
|
"some string",
|
||||||
|
isModelSet: true,
|
||||||
|
key: "someName");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("some string", result.Model);
|
||||||
|
Assert.True(result.IsModelSet);
|
||||||
|
Assert.Equal("someName", result.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -359,7 +359,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
.Returns((ModelBindingContext mbc) =>
|
.Returns((ModelBindingContext mbc) =>
|
||||||
{
|
{
|
||||||
// just return the DTO unchanged
|
// just return the DTO unchanged
|
||||||
return Task.FromResult(true);
|
return Task.FromResult(new ModelBindingResult(mbc.ModelMetadata.Model, mbc.ModelName, true));
|
||||||
});
|
});
|
||||||
|
|
||||||
var testableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
|
var testableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
|
||||||
|
|
@ -371,9 +371,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var retValue = await testableBinder.Object.BindModelAsync(bindingContext);
|
var retValue = await testableBinder.Object.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(retValue);
|
Assert.NotNull(retValue);
|
||||||
Assert.IsType<Person>(bindingContext.Model);
|
Assert.True(retValue.IsModelSet);
|
||||||
Assert.True(bindingContext.ValidationNode.ValidateAllProperties);
|
Assert.NotNull(retValue.Model);
|
||||||
|
Assert.IsType<Person>(retValue.Model);
|
||||||
testableBinder.Verify();
|
testableBinder.Verify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -404,7 +405,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
.Returns((ModelBindingContext mbc) =>
|
.Returns((ModelBindingContext mbc) =>
|
||||||
{
|
{
|
||||||
// just return the DTO unchanged
|
// just return the DTO unchanged
|
||||||
return Task.FromResult(true);
|
return Task.FromResult(new ModelBindingResult(mbc.ModelMetadata.Model, mbc.ModelName, true));
|
||||||
});
|
});
|
||||||
|
|
||||||
var testableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
|
var testableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
|
||||||
|
|
@ -416,9 +417,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var retValue = await testableBinder.Object.BindModelAsync(bindingContext);
|
var retValue = await testableBinder.Object.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(retValue);
|
Assert.NotNull(retValue);
|
||||||
Assert.IsType<Person>(bindingContext.Model);
|
Assert.True(retValue.IsModelSet);
|
||||||
Assert.True(bindingContext.ValidationNode.ValidateAllProperties);
|
Assert.NotNull(retValue.Model);
|
||||||
|
Assert.IsType<Person>(retValue.Model);
|
||||||
testableBinder.Verify();
|
testableBinder.Verify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -517,9 +519,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var testableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
|
var testableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var originalModel = bindingContext.Model;
|
var originalModel = bindingContext.ModelMetadata.Model;
|
||||||
testableBinder.Object.EnsureModelPublic(bindingContext);
|
testableBinder.Object.EnsureModelPublic(bindingContext);
|
||||||
var newModel = bindingContext.Model;
|
var newModel = bindingContext.ModelMetadata.Model;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Same(originalModel, newModel);
|
Assert.Same(originalModel, newModel);
|
||||||
|
|
@ -540,9 +542,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
.Returns(new Person()).Verifiable();
|
.Returns(new Person()).Verifiable();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var originalModel = bindingContext.Model;
|
var originalModel = bindingContext.ModelMetadata.Model;
|
||||||
testableBinder.Object.EnsureModelPublic(bindingContext);
|
testableBinder.Object.EnsureModelPublic(bindingContext);
|
||||||
var newModel = bindingContext.Model;
|
var newModel = bindingContext.ModelMetadata.Model;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Null(originalModel);
|
Assert.Null(originalModel);
|
||||||
|
|
@ -685,53 +687,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
Assert.Equal(new[] { "Never" }, validationInfo.SkipProperties);
|
Assert.Equal(new[] { "Never" }, validationInfo.SkipProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void NullCheckFailedHandler_ModelStateAlreadyInvalid_DoesNothing()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var modelState = new ModelStateDictionary();
|
|
||||||
modelState.AddModelError("foo.bar", "Some existing error.");
|
|
||||||
|
|
||||||
var modelMetadata = GetMetadataForType(typeof(Person));
|
|
||||||
var validationNode = new ModelValidationNode(modelMetadata, "foo");
|
|
||||||
var validationContext = new ModelValidationContext(new DataAnnotationsModelMetadataProvider(),
|
|
||||||
Mock.Of<IModelValidatorProvider>(),
|
|
||||||
modelState,
|
|
||||||
modelMetadata,
|
|
||||||
null);
|
|
||||||
var e = new ModelValidatedEventArgs(validationContext, parentNode: null);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var handler = MutableObjectModelBinder.CreateNullCheckFailedHandler(modelMetadata, incomingValue: null);
|
|
||||||
handler(validationNode, e);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.False(modelState.ContainsKey("foo"));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void NullCheckFailedHandler_ModelStateValid_AddsErrorString()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var modelState = new ModelStateDictionary();
|
|
||||||
var modelMetadata = GetMetadataForType(typeof(Person));
|
|
||||||
var validationNode = new ModelValidationNode(modelMetadata, "foo");
|
|
||||||
var validationContext = new ModelValidationContext(new DataAnnotationsModelMetadataProvider(),
|
|
||||||
Mock.Of<IModelValidatorProvider>(),
|
|
||||||
modelState,
|
|
||||||
modelMetadata,
|
|
||||||
null);
|
|
||||||
var e = new ModelValidatedEventArgs(validationContext, parentNode: null);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var handler = MutableObjectModelBinder.CreateNullCheckFailedHandler(modelMetadata, incomingValue: null);
|
|
||||||
handler(validationNode, e);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.True(modelState.ContainsKey("foo"));
|
|
||||||
Assert.Equal("A value is required.", modelState["foo"].Errors[0].ErrorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[ReplaceCulture]
|
[ReplaceCulture]
|
||||||
public void ProcessDto_BindRequiredFieldMissing_RaisesModelError()
|
public void ProcessDto_BindRequiredFieldMissing_RaisesModelError()
|
||||||
|
|
@ -756,10 +711,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
|
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
|
||||||
|
|
||||||
var nameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "Name");
|
var nameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "Name");
|
||||||
dto.Results[nameProperty] = new ComplexModelDtoResult(
|
dto.Results[nameProperty] = new ModelBindingResult(
|
||||||
"John Doe",
|
"John Doe",
|
||||||
isModelBound: true,
|
isModelSet: true,
|
||||||
validationNode: new ModelValidationNode(nameProperty, ""));
|
key: "");
|
||||||
|
|
||||||
var testableBinder = new TestableMutableObjectModelBinder();
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
|
|
@ -802,7 +757,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
ValidatorProvider = Mock.Of<IModelValidatorProvider>()
|
ValidatorProvider = Mock.Of<IModelValidatorProvider>()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var validationContext = new ModelValidationContext(new EmptyModelMetadataProvider(),
|
var validationContext = new ModelValidationContext("theModel",
|
||||||
bindingContext.OperationBindingContext
|
bindingContext.OperationBindingContext
|
||||||
.ValidatorProvider,
|
.ValidatorProvider,
|
||||||
bindingContext.ModelState,
|
bindingContext.ModelState,
|
||||||
|
|
@ -813,35 +768,29 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var testableBinder = new TestableMutableObjectModelBinder();
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
var propertyMetadata = dto.PropertyMetadata.Single(o => o.PropertyName == "Name");
|
var propertyMetadata = dto.PropertyMetadata.Single(o => o.PropertyName == "Name");
|
||||||
dto.Results[propertyMetadata] = new ComplexModelDtoResult(
|
dto.Results[propertyMetadata] = new ModelBindingResult(
|
||||||
"John Doe",
|
"John Doe",
|
||||||
isModelBound: true,
|
isModelSet: true,
|
||||||
validationNode: new ModelValidationNode(propertyMetadata, "theModel.Name"));
|
key: "theModel.Name");
|
||||||
|
|
||||||
// Attempt to set non-Nullable property to null. BindRequiredAttribute should not be relevant in this
|
// Attempt to set non-Nullable property to null. BindRequiredAttribute should not be relevant in this
|
||||||
// case because the binding exists.
|
// case because the binding exists.
|
||||||
propertyMetadata = dto.PropertyMetadata.Single(o => o.PropertyName == "Age");
|
propertyMetadata = dto.PropertyMetadata.Single(o => o.PropertyName == "Age");
|
||||||
dto.Results[propertyMetadata] = new ComplexModelDtoResult(
|
dto.Results[propertyMetadata] = new ModelBindingResult(
|
||||||
null,
|
null,
|
||||||
isModelBound: true,
|
isModelSet: true,
|
||||||
validationNode: new ModelValidationNode(propertyMetadata, "theModel.Age"));
|
key: "theModel.Age");
|
||||||
|
|
||||||
// Act; must also Validate because null-check error handler is late-bound
|
// Act
|
||||||
testableBinder.ProcessDto(bindingContext, dto);
|
testableBinder.ProcessDto(bindingContext, dto);
|
||||||
bindingContext.ValidationNode.Validate(validationContext);
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var modelStateDictionary = bindingContext.ModelState;
|
var modelStateDictionary = bindingContext.ModelState;
|
||||||
Assert.False(modelStateDictionary.IsValid);
|
Assert.False(modelStateDictionary.IsValid);
|
||||||
Assert.Equal(2, modelStateDictionary.Count);
|
Assert.Equal(1, modelStateDictionary.Count);
|
||||||
|
|
||||||
// Check Name field
|
|
||||||
ModelState modelState;
|
|
||||||
Assert.True(modelStateDictionary.TryGetValue("theModel.Name", out modelState));
|
|
||||||
Assert.Equal(0, modelState.Errors.Count);
|
|
||||||
Assert.Equal(ModelValidationState.Valid, modelState.ValidationState);
|
|
||||||
|
|
||||||
// Check Age error.
|
// Check Age error.
|
||||||
|
ModelState modelState;
|
||||||
Assert.True(modelStateDictionary.TryGetValue("theModel.Age", out modelState));
|
Assert.True(modelStateDictionary.TryGetValue("theModel.Age", out modelState));
|
||||||
Assert.Equal(ModelValidationState.Invalid, modelState.ValidationState);
|
Assert.Equal(ModelValidationState.Invalid, modelState.ValidationState);
|
||||||
|
|
||||||
|
|
@ -906,16 +855,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
// Make Age valid and City invalid.
|
// Make Age valid and City invalid.
|
||||||
var propertyMetadata = dto.PropertyMetadata.Single(p => p.PropertyName == "Age");
|
var propertyMetadata = dto.PropertyMetadata.Single(p => p.PropertyName == "Age");
|
||||||
dto.Results[propertyMetadata] = new ComplexModelDtoResult(
|
dto.Results[propertyMetadata] = new ModelBindingResult(
|
||||||
23,
|
23,
|
||||||
isModelBound: true,
|
isModelSet: true,
|
||||||
validationNode: new ModelValidationNode(propertyMetadata, "theModel.Age"));
|
key: "theModel.Age");
|
||||||
|
|
||||||
propertyMetadata = dto.PropertyMetadata.Single(p => p.PropertyName == "City");
|
propertyMetadata = dto.PropertyMetadata.Single(p => p.PropertyName == "City");
|
||||||
dto.Results[propertyMetadata] = new ComplexModelDtoResult(
|
dto.Results[propertyMetadata] = new ModelBindingResult(
|
||||||
null,
|
null,
|
||||||
isModelBound: true,
|
isModelSet: true,
|
||||||
validationNode: new ModelValidationNode(propertyMetadata, "theModel.City"));
|
key: "theModel.City");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
testableBinder.ProcessDto(bindingContext, dto);
|
testableBinder.ProcessDto(bindingContext, dto);
|
||||||
|
|
@ -980,10 +929,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
// Make ValueTypeRequired invalid.
|
// Make ValueTypeRequired invalid.
|
||||||
var propertyMetadata = dto.PropertyMetadata.Single(p => p.PropertyName == "ValueTypeRequired");
|
var propertyMetadata = dto.PropertyMetadata.Single(p => p.PropertyName == "ValueTypeRequired");
|
||||||
dto.Results[propertyMetadata] = new ComplexModelDtoResult(
|
dto.Results[propertyMetadata] = new ModelBindingResult(
|
||||||
null,
|
null,
|
||||||
isModelBound: true,
|
isModelSet: true,
|
||||||
validationNode: new ModelValidationNode(propertyMetadata, "theModel.ValueTypeRequired"));
|
key: "theModel.ValueTypeRequired");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
testableBinder.ProcessDto(bindingContext, dto);
|
testableBinder.ProcessDto(bindingContext, dto);
|
||||||
|
|
@ -1018,16 +967,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
|
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
|
||||||
|
|
||||||
var firstNameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "FirstName");
|
var firstNameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "FirstName");
|
||||||
dto.Results[firstNameProperty] = new ComplexModelDtoResult(
|
dto.Results[firstNameProperty] = new ModelBindingResult(
|
||||||
"John",
|
"John",
|
||||||
isModelBound: true,
|
isModelSet: true,
|
||||||
validationNode: new ModelValidationNode(firstNameProperty, ""));
|
key: "");
|
||||||
|
|
||||||
var lastNameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "LastName");
|
var lastNameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "LastName");
|
||||||
dto.Results[lastNameProperty] = new ComplexModelDtoResult(
|
dto.Results[lastNameProperty] = new ModelBindingResult(
|
||||||
"Doe",
|
"Doe",
|
||||||
isModelBound: true,
|
isModelSet: true,
|
||||||
validationNode: new ModelValidationNode(lastNameProperty, ""));
|
key: "");
|
||||||
|
|
||||||
var dobProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "DateOfBirth");
|
var dobProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "DateOfBirth");
|
||||||
dto.Results[dobProperty] = null;
|
dto.Results[dobProperty] = null;
|
||||||
|
|
@ -1051,12 +1000,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var bindingContext = CreateContext(GetMetadataForObject(new Person()));
|
var bindingContext = CreateContext(GetMetadataForObject(new Person()));
|
||||||
|
|
||||||
var propertyMetadata = bindingContext.ModelMetadata.Properties.First(o => o.PropertyName == "PropertyWithDefaultValue");
|
var propertyMetadata = bindingContext.ModelMetadata.Properties.First(o => o.PropertyName == "PropertyWithDefaultValue");
|
||||||
var validationNode = new ModelValidationNode(propertyMetadata, "foo");
|
|
||||||
|
|
||||||
var dtoResult = new ComplexModelDtoResult(
|
var dtoResult = new ModelBindingResult(
|
||||||
model: null,
|
model: null,
|
||||||
isModelBound: false,
|
isModelSet: false,
|
||||||
validationNode: validationNode);
|
key: "foo");
|
||||||
|
|
||||||
var requiredValidator = bindingContext.OperationBindingContext
|
var requiredValidator = bindingContext.OperationBindingContext
|
||||||
.ValidatorProvider
|
.ValidatorProvider
|
||||||
|
|
@ -1069,7 +1017,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var person = Assert.IsType<Person>(bindingContext.Model);
|
var person = Assert.IsType<Person>(bindingContext.ModelMetadata.Model);
|
||||||
Assert.Equal(123.456m, person.PropertyWithDefaultValue);
|
Assert.Equal(123.456m, person.PropertyWithDefaultValue);
|
||||||
Assert.True(bindingContext.ModelState.IsValid);
|
Assert.True(bindingContext.ModelState.IsValid);
|
||||||
}
|
}
|
||||||
|
|
@ -1082,13 +1030,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(
|
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(
|
||||||
o => o.PropertyName == "PropertyWithInitializedValue");
|
o => o.PropertyName == "PropertyWithInitializedValue");
|
||||||
var validationNode = new ModelValidationNode(propertyMetadata, "foo");
|
|
||||||
|
|
||||||
// This value won't be used because IsModelBound = false.
|
// This value won't be used because IsModelBound = false.
|
||||||
var dtoResult = new ComplexModelDtoResult(
|
var dtoResult = new ModelBindingResult(
|
||||||
model: "bad-value",
|
model: "bad-value",
|
||||||
isModelBound: false,
|
isModelSet: false,
|
||||||
validationNode: validationNode);
|
key: "foo");
|
||||||
|
|
||||||
var testableBinder = new TestableMutableObjectModelBinder();
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
|
|
@ -1096,7 +1043,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator: null);
|
testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator: null);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var person = Assert.IsType<Person>(bindingContext.Model);
|
var person = Assert.IsType<Person>(bindingContext.ModelMetadata.Model);
|
||||||
Assert.Equal("preinitialized", person.PropertyWithInitializedValue);
|
Assert.Equal("preinitialized", person.PropertyWithInitializedValue);
|
||||||
Assert.True(bindingContext.ModelState.IsValid);
|
Assert.True(bindingContext.ModelState.IsValid);
|
||||||
}
|
}
|
||||||
|
|
@ -1109,13 +1056,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(
|
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(
|
||||||
o => o.PropertyName == "PropertyWithInitializedValueAndDefault");
|
o => o.PropertyName == "PropertyWithInitializedValueAndDefault");
|
||||||
var validationNode = new ModelValidationNode(propertyMetadata, "foo");
|
|
||||||
|
|
||||||
// This value won't be used because IsModelBound = false.
|
// This value won't be used because IsModelBound = false.
|
||||||
var dtoResult = new ComplexModelDtoResult(
|
var dtoResult = new ModelBindingResult(
|
||||||
model: "bad-value",
|
model: "bad-value",
|
||||||
isModelBound: false,
|
isModelSet: false,
|
||||||
validationNode: validationNode);
|
key: "foo");
|
||||||
|
|
||||||
var testableBinder = new TestableMutableObjectModelBinder();
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
|
|
@ -1123,7 +1069,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator: null);
|
testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator: null);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var person = Assert.IsType<Person>(bindingContext.Model);
|
var person = Assert.IsType<Person>(bindingContext.ModelMetadata.Model);
|
||||||
Assert.Equal("default", person.PropertyWithInitializedValueAndDefault);
|
Assert.Equal("default", person.PropertyWithInitializedValueAndDefault);
|
||||||
Assert.True(bindingContext.ModelState.IsValid);
|
Assert.True(bindingContext.ModelState.IsValid);
|
||||||
}
|
}
|
||||||
|
|
@ -1134,12 +1080,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
// Arrange
|
// Arrange
|
||||||
var bindingContext = CreateContext(GetMetadataForObject(new Person()));
|
var bindingContext = CreateContext(GetMetadataForObject(new Person()));
|
||||||
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "NonUpdateableProperty");
|
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "NonUpdateableProperty");
|
||||||
var validationNode = new ModelValidationNode(propertyMetadata, "foo");
|
|
||||||
|
|
||||||
var dtoResult = new ComplexModelDtoResult(
|
var dtoResult = new ModelBindingResult(
|
||||||
model: null,
|
model: null,
|
||||||
isModelBound: false,
|
isModelSet: false,
|
||||||
validationNode: validationNode);
|
key: "foo");
|
||||||
|
|
||||||
var testableBinder = new TestableMutableObjectModelBinder();
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
|
|
@ -1158,12 +1103,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var bindingContext = CreateContext(GetMetadataForObject(model));
|
var bindingContext = CreateContext(GetMetadataForObject(model));
|
||||||
|
|
||||||
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfBirth");
|
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfBirth");
|
||||||
var validationNode = new ModelValidationNode(propertyMetadata, "foo");
|
|
||||||
|
|
||||||
var dtoResult = new ComplexModelDtoResult(
|
var dtoResult = new ModelBindingResult(
|
||||||
new DateTime(2001, 1, 1),
|
new DateTime(2001, 1, 1),
|
||||||
isModelBound: true,
|
key: "foo",
|
||||||
validationNode: validationNode);
|
isModelSet: true);
|
||||||
|
|
||||||
|
|
||||||
var requiredValidator = bindingContext.OperationBindingContext
|
var requiredValidator = bindingContext.OperationBindingContext
|
||||||
.ValidatorProvider
|
.ValidatorProvider
|
||||||
|
|
@ -1177,7 +1122,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
validationNode.Validate(validationContext);
|
|
||||||
Assert.True(bindingContext.ModelState.IsValid);
|
Assert.True(bindingContext.ModelState.IsValid);
|
||||||
Assert.Equal(new DateTime(2001, 1, 1), model.DateOfBirth);
|
Assert.Equal(new DateTime(2001, 1, 1), model.DateOfBirth);
|
||||||
}
|
}
|
||||||
|
|
@ -1194,11 +1138,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var bindingContext = CreateContext(GetMetadataForObject(model));
|
var bindingContext = CreateContext(GetMetadataForObject(model));
|
||||||
|
|
||||||
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfDeath");
|
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfDeath");
|
||||||
var validationNode = new ModelValidationNode(propertyMetadata, "foo");
|
var dtoResult = new ModelBindingResult(
|
||||||
var dtoResult = new ComplexModelDtoResult(
|
|
||||||
new DateTime(1800, 1, 1),
|
new DateTime(1800, 1, 1),
|
||||||
isModelBound: true,
|
isModelSet: true,
|
||||||
validationNode: validationNode);
|
key: "foo");
|
||||||
|
|
||||||
var testableBinder = new TestableMutableObjectModelBinder();
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
|
|
@ -1218,11 +1161,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var bindingContext = CreateContext(GetMetadataForObject(new Person()));
|
var bindingContext = CreateContext(GetMetadataForObject(new Person()));
|
||||||
|
|
||||||
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfBirth");
|
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfBirth");
|
||||||
var validationNode = new ModelValidationNode(propertyMetadata, "foo");
|
var dtoResult = new ModelBindingResult(
|
||||||
var dtoResult = new ComplexModelDtoResult(
|
|
||||||
model: null,
|
model: null,
|
||||||
isModelBound: true,
|
isModelSet: true,
|
||||||
validationNode: validationNode);
|
key: "foo");
|
||||||
|
|
||||||
var requiredValidator = GetRequiredValidator(bindingContext, propertyMetadata);
|
var requiredValidator = GetRequiredValidator(bindingContext, propertyMetadata);
|
||||||
var validationContext = new ModelValidationContext(bindingContext, propertyMetadata);
|
var validationContext = new ModelValidationContext(bindingContext, propertyMetadata);
|
||||||
|
|
@ -1233,8 +1175,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(bindingContext.ModelState.IsValid);
|
|
||||||
validationNode.Validate(validationContext, bindingContext.ValidationNode);
|
|
||||||
Assert.False(bindingContext.ModelState.IsValid);
|
Assert.False(bindingContext.ModelState.IsValid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1246,11 +1186,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
bindingContext.ModelName = " foo";
|
bindingContext.ModelName = " foo";
|
||||||
|
|
||||||
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "ValueTypeRequired");
|
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "ValueTypeRequired");
|
||||||
var validationNode = new ModelValidationNode(propertyMetadata, "foo.ValueTypeRequired");
|
var dtoResult = new ModelBindingResult(
|
||||||
var dtoResult = new ComplexModelDtoResult(
|
|
||||||
model: null,
|
model: null,
|
||||||
isModelBound: true,
|
isModelSet: true,
|
||||||
validationNode: validationNode);
|
key: "foo.ValueTypeRequired");
|
||||||
|
|
||||||
var requiredValidator = GetRequiredValidator(bindingContext, propertyMetadata);
|
var requiredValidator = GetRequiredValidator(bindingContext, propertyMetadata);
|
||||||
|
|
||||||
|
|
@ -1273,11 +1212,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
bindingContext.ModelName = "foo";
|
bindingContext.ModelName = "foo";
|
||||||
|
|
||||||
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "NameNoAttribute");
|
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "NameNoAttribute");
|
||||||
var validationNode = new ModelValidationNode(propertyMetadata, "foo.NameNoAttribute");
|
var dtoResult = new ModelBindingResult(
|
||||||
var dtoResult = new ComplexModelDtoResult(
|
|
||||||
model: null,
|
model: null,
|
||||||
isModelBound: true,
|
isModelSet: true,
|
||||||
validationNode: validationNode);
|
key: "foo.NameNoAttribute");
|
||||||
|
|
||||||
var requiredValidator = GetRequiredValidator(bindingContext, propertyMetadata);
|
var requiredValidator = GetRequiredValidator(bindingContext, propertyMetadata);
|
||||||
|
|
||||||
|
|
@ -1302,10 +1240,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
bindingContext.ModelName = "foo";
|
bindingContext.ModelName = "foo";
|
||||||
|
|
||||||
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "Name");
|
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "Name");
|
||||||
var validationNode = new ModelValidationNode(propertyMetadata, "foo.Name");
|
var dtoResult = new ModelBindingResult(model: null,
|
||||||
var dtoResult = new ComplexModelDtoResult(model: null,
|
isModelSet: true,
|
||||||
isModelBound: true,
|
key: "foo.Name");
|
||||||
validationNode: validationNode);
|
|
||||||
|
|
||||||
var requiredValidator = GetRequiredValidator(bindingContext, propertyMetadata);
|
var requiredValidator = GetRequiredValidator(bindingContext, propertyMetadata);
|
||||||
|
|
||||||
|
|
@ -1613,7 +1550,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
public new void SetProperty(ModelBindingContext bindingContext,
|
public new void SetProperty(ModelBindingContext bindingContext,
|
||||||
ModelMetadata propertyMetadata,
|
ModelMetadata propertyMetadata,
|
||||||
ComplexModelDtoResult dtoResult,
|
ModelBindingResult dtoResult,
|
||||||
IModelValidator requiredValidator)
|
IModelValidator requiredValidator)
|
||||||
{
|
{
|
||||||
base.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
base.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var retVal = await binder.BindModelAsync(bindingContext);
|
var retVal = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(retVal);
|
Assert.Null(retVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -64,7 +64,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var retVal = await binder.BindModelAsync(bindingContext);
|
var retVal = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(retVal);
|
Assert.NotNull(retVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -84,8 +84,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var retVal = await binder.BindModelAsync(bindingContext);
|
var retVal = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(retVal);
|
Assert.NotNull(retVal);
|
||||||
Assert.Null(bindingContext.Model);
|
Assert.Null(retVal.Model);
|
||||||
Assert.False(bindingContext.ModelState.IsValid);
|
Assert.False(bindingContext.ModelState.IsValid);
|
||||||
var error = Assert.Single(bindingContext.ModelState["theModelName"].Errors);
|
var error = Assert.Single(bindingContext.ModelState["theModelName"].Errors);
|
||||||
Assert.Equal(message, error.ErrorMessage);
|
Assert.Equal(message, error.ErrorMessage);
|
||||||
|
|
@ -102,7 +102,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var retVal = await binder.BindModelAsync(bindingContext);
|
var retVal = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(retVal, "BindModel should have returned null.");
|
Assert.Null(retVal);
|
||||||
Assert.Empty(bindingContext.ModelState);
|
Assert.Empty(bindingContext.ModelState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,8 +122,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var retVal = await binder.BindModelAsync(bindingContext);
|
var retVal = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(retVal);
|
Assert.NotNull(retVal);
|
||||||
Assert.Null(bindingContext.Model);
|
Assert.Null(retVal.Model);
|
||||||
Assert.True(bindingContext.ModelState.ContainsKey("theModelName"));
|
Assert.True(bindingContext.ModelState.ContainsKey("theModelName"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,8 +143,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var retVal = await binder.BindModelAsync(bindingContext);
|
var retVal = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(retVal);
|
Assert.NotNull(retVal);
|
||||||
Assert.Equal(42, bindingContext.Model);
|
Assert.Equal(42, retVal.Model);;
|
||||||
Assert.True(bindingContext.ModelState.ContainsKey("theModelName"));
|
Assert.True(bindingContext.ModelState.ContainsKey("theModelName"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
|
|
@ -15,11 +16,11 @@ using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
public class DefaultBodyModelValidatorTests
|
public class DefaultObjectValidatorTests
|
||||||
{
|
{
|
||||||
private static Person LonelyPerson;
|
private static Person LonelyPerson;
|
||||||
|
|
||||||
static DefaultBodyModelValidatorTests()
|
static DefaultObjectValidatorTests()
|
||||||
{
|
{
|
||||||
LonelyPerson = new Person() { Name = "Reallllllllly Long Name" };
|
LonelyPerson = new Person() { Name = "Reallllllllly Long Name" };
|
||||||
LonelyPerson.Friend = LonelyPerson;
|
LonelyPerson.Friend = LonelyPerson;
|
||||||
|
|
@ -212,14 +213,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
public void ExpectedValidationErrorsRaised(object model, Type type, Dictionary<string, string> expectedErrors)
|
public void ExpectedValidationErrorsRaised(object model, Type type, Dictionary<string, string> expectedErrors)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var validationContext = GetModelValidationContext(model, type);
|
var testValidationContext = GetModelValidationContext(model, type);
|
||||||
|
|
||||||
// Act (does not throw)
|
// Act (does not throw)
|
||||||
new DefaultBodyModelValidator().Validate(validationContext, keyPrefix: string.Empty);
|
new DefaultObjectValidator(
|
||||||
|
testValidationContext.ExcludeFiltersProvider,
|
||||||
|
testValidationContext.ModelMetadataProvider)
|
||||||
|
.Validate(testValidationContext.ModelValidationContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var actualErrors = new Dictionary<string, string>();
|
var actualErrors = new Dictionary<string, string>();
|
||||||
foreach (var keyStatePair in validationContext.ModelState)
|
foreach (var keyStatePair in testValidationContext.ModelValidationContext.ModelState)
|
||||||
{
|
{
|
||||||
foreach (var error in keyStatePair.Value.Errors)
|
foreach (var error in keyStatePair.Value.Errors)
|
||||||
{
|
{
|
||||||
|
|
@ -237,17 +241,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[ReplaceCulture]
|
[ReplaceCulture]
|
||||||
public void BodyValidator_Throws_IfPropertyAccessorThrows()
|
public void Validator_Throws_IfPropertyAccessorThrows()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var validationContext = GetModelValidationContext(new Uri("/api/values", UriKind.Relative), typeof(Uri));
|
var testValidationContext = GetModelValidationContext(new Uri("/api/values", UriKind.Relative), typeof(Uri));
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
Assert.Throws(
|
Assert.Throws(
|
||||||
typeof(InvalidOperationException),
|
typeof(InvalidOperationException),
|
||||||
() =>
|
() =>
|
||||||
{
|
{
|
||||||
new DefaultBodyModelValidator().Validate(validationContext, keyPrefix: string.Empty);
|
new DefaultObjectValidator(
|
||||||
|
testValidationContext.ExcludeFiltersProvider,
|
||||||
|
testValidationContext.ModelMetadataProvider)
|
||||||
|
.Validate(testValidationContext.ModelValidationContext);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -270,30 +277,37 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(ObjectsWithPropertiesWhichThrowOnGet))]
|
[MemberData(nameof(ObjectsWithPropertiesWhichThrowOnGet))]
|
||||||
[ReplaceCulture]
|
[ReplaceCulture]
|
||||||
public void BodyValidator_DoesNotThrow_IfExcludedPropertyAccessorsThrow(
|
public void Validator_DoesNotThrow_IfExcludedPropertyAccessorsThrow(
|
||||||
object input, Type type, List<Type> excludedTypes)
|
object input, Type type, List<Type> excludedTypes)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var validationContext = GetModelValidationContext(input, type, excludedTypes);
|
var testValidationContext = GetModelValidationContext(input, type, string.Empty, excludedTypes);
|
||||||
|
|
||||||
// Act & Assert (does not throw)
|
// Act & Assert (does not throw)
|
||||||
new DefaultBodyModelValidator().Validate(validationContext, keyPrefix: string.Empty);
|
new DefaultObjectValidator(
|
||||||
Assert.True(validationContext.ModelState.IsValid);
|
testValidationContext.ExcludeFiltersProvider,
|
||||||
|
testValidationContext.ModelMetadataProvider)
|
||||||
|
.Validate(testValidationContext.ModelValidationContext);
|
||||||
|
Assert.True(testValidationContext.ModelValidationContext.ModelState.IsValid);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[ReplaceCulture]
|
[ReplaceCulture]
|
||||||
public void BodyValidator_Throws_IfPropertyGetterThrows()
|
public void Validator_Throws_IfPropertyGetterThrows()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var validationContext = GetModelValidationContext(
|
var testValidationContext = GetModelValidationContext(
|
||||||
new Uri("/api/values", UriKind.Relative), typeof(Uri), new List<Type>());
|
new Uri("/api/values", UriKind.Relative), typeof(Uri));
|
||||||
|
var validationContext = testValidationContext.ModelValidationContext;
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
Assert.Throws<InvalidOperationException>(
|
Assert.Throws<InvalidOperationException>(
|
||||||
() =>
|
() =>
|
||||||
{
|
{
|
||||||
new DefaultBodyModelValidator().Validate(validationContext, keyPrefix: string.Empty);
|
new DefaultObjectValidator(
|
||||||
|
testValidationContext.ExcludeFiltersProvider,
|
||||||
|
testValidationContext.ModelMetadataProvider)
|
||||||
|
.Validate(validationContext);
|
||||||
});
|
});
|
||||||
Assert.True(validationContext.ModelState.IsValid);
|
Assert.True(validationContext.ModelState.IsValid);
|
||||||
}
|
}
|
||||||
|
|
@ -304,10 +318,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var model = new Address() { Street = "Microsoft Way" };
|
var model = new Address() { Street = "Microsoft Way" };
|
||||||
var validationContext = GetModelValidationContext(model, model.GetType());
|
var testValidationContext = GetModelValidationContext(model, model.GetType());
|
||||||
|
var validationContext = testValidationContext.ModelValidationContext;
|
||||||
|
|
||||||
// Act (does not throw)
|
// Act (does not throw)
|
||||||
new DefaultBodyModelValidator().Validate(validationContext, keyPrefix: string.Empty);
|
new DefaultObjectValidator(
|
||||||
|
testValidationContext.ExcludeFiltersProvider,
|
||||||
|
testValidationContext.ModelMetadataProvider)
|
||||||
|
.Validate(validationContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Contains("Street", validationContext.ModelState.Keys);
|
Assert.Contains("Street", validationContext.ModelState.Keys);
|
||||||
|
|
@ -326,14 +344,146 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
new TypeThatOverridesEquals { Funny = "hehe" },
|
new TypeThatOverridesEquals { Funny = "hehe" },
|
||||||
new TypeThatOverridesEquals { Funny = "hehe" }
|
new TypeThatOverridesEquals { Funny = "hehe" }
|
||||||
};
|
};
|
||||||
var validationContext = GetModelValidationContext(instance, typeof(TypeThatOverridesEquals[]));
|
var testValidationContext = GetModelValidationContext(instance, typeof(TypeThatOverridesEquals[]));
|
||||||
|
|
||||||
// Act & Assert (does not throw)
|
// Act & Assert (does not throw)
|
||||||
new DefaultBodyModelValidator().Validate(validationContext, keyPrefix: string.Empty);
|
new DefaultObjectValidator(
|
||||||
|
testValidationContext.ExcludeFiltersProvider,
|
||||||
|
testValidationContext.ModelMetadataProvider)
|
||||||
|
.Validate(testValidationContext.ModelValidationContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ModelValidationContext GetModelValidationContext(
|
[Fact]
|
||||||
object model, Type type, List<Type> excludedTypes = null)
|
public void Validation_ShortCircuit_WhenMaxErrorCountIsSet()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var user = new User()
|
||||||
|
{
|
||||||
|
Password = "password-val",
|
||||||
|
ConfirmPassword = "not-password-val"
|
||||||
|
};
|
||||||
|
|
||||||
|
var testValidationContext = GetModelValidationContext(
|
||||||
|
user,
|
||||||
|
typeof(User),
|
||||||
|
"user",
|
||||||
|
new List<Type> { typeof(string) });
|
||||||
|
|
||||||
|
var validationContext = testValidationContext.ModelValidationContext;
|
||||||
|
validationContext.ModelState.MaxAllowedErrors = 2;
|
||||||
|
validationContext.ModelState.AddModelError("key1", "error1");
|
||||||
|
var validator = new DefaultObjectValidator(
|
||||||
|
testValidationContext.ExcludeFiltersProvider,
|
||||||
|
testValidationContext.ModelMetadataProvider);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
validator.Validate(validationContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(new[] { "key1", "user.Password", "", "user.ConfirmPassword" },
|
||||||
|
validationContext.ModelState.Keys.ToArray());
|
||||||
|
var modelState = validationContext.ModelState["user.ConfirmPassword"];
|
||||||
|
Assert.Empty(modelState.Errors);
|
||||||
|
Assert.Equal(modelState.ValidationState, ModelValidationState.Skipped);
|
||||||
|
|
||||||
|
var error = Assert.Single(validationContext.ModelState[""].Errors);
|
||||||
|
Assert.IsType<TooManyModelErrorsException>(error.Exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ForExcludedNonModelBoundType_Properties_NotMarkedAsSkiped()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var user = new User()
|
||||||
|
{
|
||||||
|
Password = "password-val",
|
||||||
|
ConfirmPassword = "not-password-val"
|
||||||
|
};
|
||||||
|
|
||||||
|
var testValidationContext = GetModelValidationContext(
|
||||||
|
user,
|
||||||
|
typeof(User),
|
||||||
|
"user",
|
||||||
|
new List<Type> { typeof(User) });
|
||||||
|
var validationContext = testValidationContext.ModelValidationContext;
|
||||||
|
var validator = new DefaultObjectValidator(
|
||||||
|
testValidationContext.ExcludeFiltersProvider,
|
||||||
|
testValidationContext.ModelMetadataProvider);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
validator.Validate(validationContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(validationContext.ModelState.ContainsKey("user.Password"));
|
||||||
|
Assert.False(validationContext.ModelState.ContainsKey("user.ConfirmPassword"));
|
||||||
|
var modelState = validationContext.ModelState["user"];
|
||||||
|
Assert.Empty(modelState.Errors);
|
||||||
|
Assert.Equal(modelState.ValidationState, ModelValidationState.Valid);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ForExcludedModelBoundTypes_Properties_MarkedAsSkipped()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var user = new User()
|
||||||
|
{
|
||||||
|
Password = "password-val",
|
||||||
|
ConfirmPassword = "not-password-val"
|
||||||
|
};
|
||||||
|
|
||||||
|
var testValidationContext = GetModelValidationContext(
|
||||||
|
user,
|
||||||
|
typeof(User),
|
||||||
|
"user",
|
||||||
|
new List<Type> { typeof(User) });
|
||||||
|
var validationContext = testValidationContext.ModelValidationContext;
|
||||||
|
|
||||||
|
// Set the value on model state as a model binder would.
|
||||||
|
validationContext.ModelState.SetModelValue(
|
||||||
|
"user.Password",
|
||||||
|
Mock.Of<ValueProviderResult>());
|
||||||
|
var validator = new DefaultObjectValidator(
|
||||||
|
testValidationContext.ExcludeFiltersProvider,
|
||||||
|
testValidationContext.ModelMetadataProvider);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
validator.Validate(validationContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var modelState = validationContext.ModelState["user.Password"];
|
||||||
|
Assert.Empty(modelState.Errors);
|
||||||
|
Assert.Equal(modelState.ValidationState, ModelValidationState.Skipped);
|
||||||
|
|
||||||
|
modelState = validationContext.ModelState["user.ConfirmPassword"];
|
||||||
|
Assert.Empty(modelState.Errors);
|
||||||
|
Assert.Equal(modelState.ValidationState, ModelValidationState.Skipped);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NonRequestBoundModel_MarkedAsSkipped()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var testValidationContext = GetModelValidationContext(
|
||||||
|
new TestServiceProvider(),
|
||||||
|
typeof(TestServiceProvider),
|
||||||
|
"serviceProvider");
|
||||||
|
|
||||||
|
var validationContext = testValidationContext.ModelValidationContext;
|
||||||
|
var validator = new DefaultObjectValidator(
|
||||||
|
testValidationContext.ExcludeFiltersProvider,
|
||||||
|
testValidationContext.ModelMetadataProvider);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
validator.Validate(validationContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var modelState = validationContext.ModelState["serviceProvider.TestService"];
|
||||||
|
Assert.Empty(modelState.Errors);
|
||||||
|
Assert.Equal(modelState.ValidationState, ModelValidationState.Skipped);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TestModelValidationContext GetModelValidationContext(
|
||||||
|
object model, Type type, string key = "", List<Type> excludedTypes = null)
|
||||||
{
|
{
|
||||||
var modelStateDictionary = new ModelStateDictionary();
|
var modelStateDictionary = new ModelStateDictionary();
|
||||||
var providers = new IModelValidatorProvider[]
|
var providers = new IModelValidatorProvider[]
|
||||||
|
|
@ -341,9 +491,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
new DataAnnotationsModelValidatorProvider(),
|
new DataAnnotationsModelValidatorProvider(),
|
||||||
new DataMemberModelValidatorProvider()
|
new DataMemberModelValidatorProvider()
|
||||||
};
|
};
|
||||||
var modelMetadataProvider = new EmptyModelMetadataProvider();
|
var modelMetadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||||
var excludedValidationTypesPredicate =
|
var excludedValidationTypesPredicate =
|
||||||
new List<IExcludeTypeValidationFilter>();
|
new List<IExcludeTypeValidationFilter>();
|
||||||
|
|
||||||
if (excludedTypes != null)
|
if (excludedTypes != null)
|
||||||
{
|
{
|
||||||
var mockExcludeTypeFilter = new Mock<IExcludeTypeValidationFilter>();
|
var mockExcludeTypeFilter = new Mock<IExcludeTypeValidationFilter>();
|
||||||
|
|
@ -354,18 +505,25 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
excludedValidationTypesPredicate.Add(mockExcludeTypeFilter.Object);
|
excludedValidationTypesPredicate.Add(mockExcludeTypeFilter.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ModelValidationContext(
|
var mockValidationExcludeFiltersProvider = new Mock<IValidationExcludeFiltersProvider>();
|
||||||
modelMetadataProvider,
|
mockValidationExcludeFiltersProvider.SetupGet(o => o.ExcludeFilters)
|
||||||
new CompositeModelValidatorProvider(providers),
|
.Returns(excludedValidationTypesPredicate);
|
||||||
modelStateDictionary,
|
return new TestModelValidationContext
|
||||||
new ModelMetadata(
|
{
|
||||||
provider: modelMetadataProvider,
|
ModelValidationContext = new ModelValidationContext(
|
||||||
containerType: typeof(object),
|
key,
|
||||||
modelAccessor: () => model,
|
new CompositeModelValidatorProvider(providers),
|
||||||
modelType: type,
|
modelStateDictionary,
|
||||||
propertyName: null),
|
new ModelMetadata(
|
||||||
containerMetadata: null,
|
provider: modelMetadataProvider,
|
||||||
excludeFromValidationFilters: excludedValidationTypesPredicate);
|
containerType: typeof(object),
|
||||||
|
modelAccessor: () => model,
|
||||||
|
modelType: type,
|
||||||
|
propertyName: null),
|
||||||
|
containerMetadata: null),
|
||||||
|
ModelMetadataProvider = modelMetadataProvider,
|
||||||
|
ExcludeFiltersProvider = mockValidationExcludeFiltersProvider.Object
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Person
|
public class Person
|
||||||
|
|
@ -468,6 +626,42 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
public Team Test { get; set; }
|
public Team Test { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class User : IValidatableObject
|
||||||
|
{
|
||||||
|
public string Password { get; set; }
|
||||||
|
|
||||||
|
[Compare("Password")]
|
||||||
|
public string ConfirmPassword { get; set; }
|
||||||
|
|
||||||
|
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||||
|
{
|
||||||
|
if (Password == "password")
|
||||||
|
{
|
||||||
|
yield return new ValidationResult("Password does not meet complexity requirements.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestServiceProvider
|
||||||
|
{
|
||||||
|
[FromServices]
|
||||||
|
[Required]
|
||||||
|
public ITestService TestService { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ITestService
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestModelValidationContext
|
||||||
|
{
|
||||||
|
public ModelValidationContext ModelValidationContext { get; set; }
|
||||||
|
|
||||||
|
public IModelMetadataProvider ModelMetadataProvider { get; set; }
|
||||||
|
|
||||||
|
public IValidationExcludeFiltersProvider ExcludeFiltersProvider { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -9,6 +9,140 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
public class ModelStateDictionaryTest
|
public class ModelStateDictionaryTest
|
||||||
{
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData(ModelValidationState.Valid)]
|
||||||
|
[InlineData(ModelValidationState.Unvalidated)]
|
||||||
|
public void MarkFieldSkipped_MarksFieldAsSkipped_IfStateIsNotInValid(ModelValidationState validationState)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var modelState = new ModelState
|
||||||
|
{
|
||||||
|
Value = GetValueProviderResult("value"),
|
||||||
|
ValidationState = validationState
|
||||||
|
};
|
||||||
|
|
||||||
|
var source = new ModelStateDictionary
|
||||||
|
{
|
||||||
|
{ "key", modelState }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
source.MarkFieldSkipped("key");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(ModelValidationState.Skipped, source["key"].ValidationState);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MarkFieldSkipped_MarksFieldAsSkipped_IfKeyIsNotPresent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var modelState = new ModelState
|
||||||
|
{
|
||||||
|
Value = GetValueProviderResult("value"),
|
||||||
|
ValidationState = ModelValidationState.Valid
|
||||||
|
};
|
||||||
|
|
||||||
|
var source = new ModelStateDictionary
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
source.MarkFieldSkipped("key");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(0, source.ErrorCount);
|
||||||
|
Assert.Equal(1, source.Count);
|
||||||
|
Assert.Equal(ModelValidationState.Skipped, source["key"].ValidationState);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MarkFieldSkipped_Throws_IfStateIsInvalid()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var modelState = new ModelState
|
||||||
|
{
|
||||||
|
Value = GetValueProviderResult("value"),
|
||||||
|
ValidationState = ModelValidationState.Invalid
|
||||||
|
};
|
||||||
|
|
||||||
|
var source = new ModelStateDictionary
|
||||||
|
{
|
||||||
|
{ "key", modelState }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var exception = Assert.Throws<InvalidOperationException>(() => source.MarkFieldSkipped("key"));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(
|
||||||
|
"A field previously marked invalid should not be marked skipped.",
|
||||||
|
exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(ModelValidationState.Skipped)]
|
||||||
|
[InlineData(ModelValidationState.Unvalidated)]
|
||||||
|
public void MarkFieldValid_MarksFieldAsValid_IfStateIsNotInvalid(ModelValidationState validationState)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var modelState = new ModelState
|
||||||
|
{
|
||||||
|
Value = GetValueProviderResult("value"),
|
||||||
|
ValidationState = validationState
|
||||||
|
};
|
||||||
|
|
||||||
|
var source = new ModelStateDictionary
|
||||||
|
{
|
||||||
|
{ "key", modelState }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
source.MarkFieldValid("key");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(ModelValidationState.Valid, source["key"].ValidationState);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MarkFieldValid_MarksFieldAsValid_IfKeyIsNotPresent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var source = new ModelStateDictionary();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
source.MarkFieldValid("key");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(0, source.ErrorCount);
|
||||||
|
Assert.Equal(1, source.Count);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, source["key"].ValidationState);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MarkFieldValid_Throws_IfStateIsInvalid()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var modelState = new ModelState
|
||||||
|
{
|
||||||
|
Value = GetValueProviderResult("value"),
|
||||||
|
ValidationState = ModelValidationState.Invalid
|
||||||
|
};
|
||||||
|
|
||||||
|
var source = new ModelStateDictionary
|
||||||
|
{
|
||||||
|
{ "key", modelState }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var exception = Assert.Throws<InvalidOperationException>(() => source.MarkFieldValid("key"));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(
|
||||||
|
"A field previously marked invalid should not be marked valid.",
|
||||||
|
exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CopyConstructor_CopiesModelStateData()
|
public void CopyConstructor_CopiesModelStateData()
|
||||||
{
|
{
|
||||||
|
|
@ -100,6 +234,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
Assert.Equal(ModelValidationState.Unvalidated, validationState);
|
Assert.Equal(ModelValidationState.Unvalidated, validationState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetValidationState_ReturnsValidationStateForKey_IgnoresChildren()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var msd = new ModelStateDictionary();
|
||||||
|
msd.AddModelError("foo.bar", "error text");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var validationState = msd.GetValidationState("foo");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(ModelValidationState.Unvalidated, validationState);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetFieldValidationState_ReturnsInvalidIfKeyChildContainsErrors()
|
public void GetFieldValidationState_ReturnsInvalidIfKeyChildContainsErrors()
|
||||||
{
|
{
|
||||||
|
|
@ -193,7 +341,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
},
|
},
|
||||||
{ "baz", new ModelState
|
{ "baz", new ModelState
|
||||||
{
|
{
|
||||||
ValidationState = ModelValidationState.Valid,
|
ValidationState = ModelValidationState.Skipped,
|
||||||
Value = GetValueProviderResult("quux", "bar")
|
Value = GetValueProviderResult("quux", "bar")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,568 +0,0 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.Linq;
|
|
||||||
using Microsoft.AspNet.Testing;
|
|
||||||
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 expected = new[]
|
|
||||||
{
|
|
||||||
"Validating parent1.",
|
|
||||||
"Validating parent2.",
|
|
||||||
"Validated parent1.",
|
|
||||||
"Validated parent2."
|
|
||||||
};
|
|
||||||
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(expected, log);
|
|
||||||
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 expected = new[]
|
|
||||||
{
|
|
||||||
"In OnValidating()",
|
|
||||||
"In LoggingValidatonAttribute.IsValid()",
|
|
||||||
"In IValidatableObject.Validate()",
|
|
||||||
"In OnValidated()"
|
|
||||||
};
|
|
||||||
var log = new List<string>();
|
|
||||||
var model = new LoggingValidatableObject(log);
|
|
||||||
var modelMetadata = GetModelMetadata(model);
|
|
||||||
var provider = new EmptyModelMetadataProvider();
|
|
||||||
var childMetadata = provider.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(expected, log);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validation order is primarily important when MaxAllowedErrors has been overridden.
|
|
||||||
[Fact]
|
|
||||||
public void Validate_OrdersUsingModelMetadata()
|
|
||||||
{
|
|
||||||
// Proper order of invocation:
|
|
||||||
// 1. OnValidating()
|
|
||||||
// 2. Child validators -- ordered using ModelMetadata.Order.
|
|
||||||
// 3. OnValidated()
|
|
||||||
|
|
||||||
// Arrange
|
|
||||||
var expected = new[]
|
|
||||||
{
|
|
||||||
"In OnValidating()",
|
|
||||||
"In LoggingValidatonAttribute.IsValid(OrderedProperty3)",
|
|
||||||
"In LoggingValidatonAttribute.IsValid(OrderedProperty2)",
|
|
||||||
"In LoggingValidatonAttribute.IsValid(OrderedProperty1)",
|
|
||||||
"In LoggingValidatonAttribute.IsValid(Property3)",
|
|
||||||
"In LoggingValidatonAttribute.IsValid(Property1)",
|
|
||||||
"In LoggingValidatonAttribute.IsValid(Property2)",
|
|
||||||
"In LoggingValidatonAttribute.IsValid(LastProperty)",
|
|
||||||
"In OnValidated()"
|
|
||||||
};
|
|
||||||
|
|
||||||
var log = new List<string>();
|
|
||||||
var model = new LoggingNonValidatableObject(log);
|
|
||||||
var provider = new DataAnnotationsModelMetadataProvider();
|
|
||||||
var modelMetadata = provider.GetMetadataForType(() => model, model.GetType());
|
|
||||||
var node = new ModelValidationNode(modelMetadata, "theKey")
|
|
||||||
{
|
|
||||||
ValidateAllProperties = true,
|
|
||||||
};
|
|
||||||
node.Validating += (sender, e) => log.Add("In OnValidating()");
|
|
||||||
node.Validated += (sender, e) => log.Add("In OnValidated()");
|
|
||||||
var context = CreateContext(modelMetadata, provider);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
node.Validate(context);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(expected, log);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Validate_ChildNodes_OverridesOrdering()
|
|
||||||
{
|
|
||||||
// Proper order of invocation:
|
|
||||||
// 1. OnValidating()
|
|
||||||
// 2. Child validators -- ordered using ChildNodes, then ModelMetadata.Order.
|
|
||||||
// 3. OnValidated()
|
|
||||||
|
|
||||||
// Arrange
|
|
||||||
var expected = new[]
|
|
||||||
{
|
|
||||||
"In OnValidating()",
|
|
||||||
"In LoggingValidatonAttribute.IsValid(LastProperty)",
|
|
||||||
"In LoggingValidatonAttribute.IsValid(OrderedProperty3)",
|
|
||||||
"In LoggingValidatonAttribute.IsValid(OrderedProperty2)",
|
|
||||||
"In LoggingValidatonAttribute.IsValid(OrderedProperty1)",
|
|
||||||
"In LoggingValidatonAttribute.IsValid(Property3)",
|
|
||||||
"In LoggingValidatonAttribute.IsValid(Property1)",
|
|
||||||
"In LoggingValidatonAttribute.IsValid(Property2)",
|
|
||||||
"In OnValidated()"
|
|
||||||
};
|
|
||||||
|
|
||||||
var log = new List<string>();
|
|
||||||
var model = new LoggingNonValidatableObject(log);
|
|
||||||
var provider = new DataAnnotationsModelMetadataProvider();
|
|
||||||
var modelMetadata = provider.GetMetadataForType(() => model, model.GetType());
|
|
||||||
var childMetadata = modelMetadata.Properties.FirstOrDefault(
|
|
||||||
property => property.PropertyName == "LastProperty");
|
|
||||||
Assert.NotNull(childMetadata); // Guard
|
|
||||||
|
|
||||||
var node = new ModelValidationNode(modelMetadata, "theKey")
|
|
||||||
{
|
|
||||||
ChildNodes =
|
|
||||||
{
|
|
||||||
new ModelValidationNode(childMetadata, "theKey.LastProperty")
|
|
||||||
},
|
|
||||||
ValidateAllProperties = true,
|
|
||||||
};
|
|
||||||
node.Validating += (sender, e) => log.Add("In OnValidating()");
|
|
||||||
node.Validated += (sender, e) => log.Add("In OnValidated()");
|
|
||||||
var context = CreateContext(modelMetadata, provider);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
node.Validate(context);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(expected, log);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Validate_SkipsRemainingValidationIfModelStateIsInvalid()
|
|
||||||
{
|
|
||||||
// Because a property validator fails, the model validator shouldn't run
|
|
||||||
|
|
||||||
// Arrange
|
|
||||||
var expected = new[]
|
|
||||||
{
|
|
||||||
"In OnValidating()",
|
|
||||||
"In IValidatableObject.Validate()",
|
|
||||||
"In OnValidated()"
|
|
||||||
};
|
|
||||||
var log = new List<string>();
|
|
||||||
var model = new LoggingValidatableObject(log);
|
|
||||||
var modelMetadata = GetModelMetadata(model);
|
|
||||||
var provider = new EmptyModelMetadataProvider();
|
|
||||||
var childMetadata = provider.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(expected, log);
|
|
||||||
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_ValidatesIfModelIsNull()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var modelMetadata = GetModelMetadata(typeof(ValidateAllPropertiesModel));
|
|
||||||
var node = new ModelValidationNode(modelMetadata, "theKey");
|
|
||||||
|
|
||||||
var context = CreateContext(modelMetadata);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
node.Validate(context);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
var modelState = Assert.Single(context.ModelState);
|
|
||||||
Assert.Equal("theKey", modelState.Key);
|
|
||||||
Assert.Equal(ModelValidationState.Invalid, modelState.Value.ValidationState);
|
|
||||||
|
|
||||||
var error = Assert.Single(modelState.Value.Errors);
|
|
||||||
Assert.Equal("A value is required but was not present in the request.", error.ErrorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[ReplaceCulture]
|
|
||||||
public void Validate_ValidateAllProperties_AddsValidationErrors()
|
|
||||||
{
|
|
||||||
// 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
|
|
||||||
var modelState = context.ModelState["theKey.RequiredString.Dummy"];
|
|
||||||
Assert.NotNull(modelState);
|
|
||||||
var error = Assert.Single(modelState.Errors);
|
|
||||||
Assert.Equal("existing Error Text", error.ErrorMessage);
|
|
||||||
|
|
||||||
modelState = context.ModelState["theKey.RangedInt"];
|
|
||||||
Assert.NotNull(modelState);
|
|
||||||
error = Assert.Single(modelState.Errors);
|
|
||||||
Assert.Equal("The field RangedInt must be between 10 and 30.", error.ErrorMessage);
|
|
||||||
|
|
||||||
Assert.DoesNotContain("theKey.RequiredString", context.ModelState.Keys);
|
|
||||||
Assert.DoesNotContain("theKey.ValidString", context.ModelState.Keys);
|
|
||||||
Assert.DoesNotContain("theKey", context.ModelState.Keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[ReplaceCulture]
|
|
||||||
public void Validate_ShortCircuits_IfModelStateHasReachedMaxNumberOfErrors()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var model = new ValidateAllPropertiesModel
|
|
||||||
{
|
|
||||||
RequiredString = null /* error */,
|
|
||||||
RangedInt = 0 /* error */,
|
|
||||||
ValidString = "cat" /* error */
|
|
||||||
};
|
|
||||||
var expectedMessage = ValidationAttributeUtil.GetRequiredErrorMessage("RequiredString");
|
|
||||||
|
|
||||||
var modelMetadata = GetModelMetadata(model);
|
|
||||||
var node = new ModelValidationNode(modelMetadata, "theKey")
|
|
||||||
{
|
|
||||||
ValidateAllProperties = true
|
|
||||||
};
|
|
||||||
var context = CreateContext(modelMetadata);
|
|
||||||
context.ModelState.MaxAllowedErrors = 3;
|
|
||||||
context.ModelState.AddModelError("somekey", "error text");
|
|
||||||
|
|
||||||
// Act
|
|
||||||
node.Validate(context);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(3, context.ModelState.Count);
|
|
||||||
var modelState = context.ModelState[string.Empty];
|
|
||||||
Assert.NotNull(modelState);
|
|
||||||
var error = Assert.Single(modelState.Errors);
|
|
||||||
Assert.IsType<TooManyModelErrorsException>(error.Exception);
|
|
||||||
|
|
||||||
// RequiredString is validated first due to ModelMetadata.Properties ordering (Reflection-based).
|
|
||||||
modelState = context.ModelState["theKey.RequiredString"];
|
|
||||||
Assert.NotNull(modelState);
|
|
||||||
error = Assert.Single(modelState.Errors);
|
|
||||||
Assert.Equal(expectedMessage, error.ErrorMessage);
|
|
||||||
|
|
||||||
// No room for the other validation errors.
|
|
||||||
Assert.DoesNotContain("theKey.RangedInt", context.ModelState.Keys);
|
|
||||||
Assert.DoesNotContain("theKey.ValidString", context.ModelState.Keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ModelMetadata GetModelMetadata()
|
|
||||||
{
|
|
||||||
return new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(object));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ModelMetadata GetModelMetadata(object model)
|
|
||||||
{
|
|
||||||
return new DataAnnotationsModelMetadataProvider().GetMetadataForType(() => model, model.GetType());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ModelMetadata GetModelMetadata(Type type)
|
|
||||||
{
|
|
||||||
return new DataAnnotationsModelMetadataProvider().GetMetadataForType(modelAccessor: null, modelType: type);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ModelValidationContext CreateContext(
|
|
||||||
ModelMetadata metadata = null,
|
|
||||||
IModelMetadataProvider metadataProvider = null)
|
|
||||||
{
|
|
||||||
var providers = new IModelValidatorProvider[]
|
|
||||||
{
|
|
||||||
new DataAnnotationsModelValidatorProvider(),
|
|
||||||
new DataMemberModelValidatorProvider()
|
|
||||||
};
|
|
||||||
if (metadataProvider == null)
|
|
||||||
{
|
|
||||||
metadataProvider = new EmptyModelMetadataProvider();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ModelValidationContext(metadataProvider,
|
|
||||||
new CompositeModelValidatorProvider(providers),
|
|
||||||
new ModelStateDictionary(),
|
|
||||||
metadata,
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
Assert.Null(validationContext.MemberName);
|
|
||||||
_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)
|
|
||||||
{
|
|
||||||
var validatableObject = Assert.IsType<LoggingValidatableObject>(value);
|
|
||||||
Assert.NotNull(validationContext);
|
|
||||||
Assert.Equal("ValidStringProperty", validationContext.MemberName);
|
|
||||||
validatableObject._log.Add("In LoggingValidatonAttribute.IsValid()");
|
|
||||||
return ValidationResult.Success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class LoggingNonValidatableObject
|
|
||||||
{
|
|
||||||
private readonly IList<string> _log;
|
|
||||||
|
|
||||||
public LoggingNonValidatableObject(IList<string> log)
|
|
||||||
{
|
|
||||||
_log = log;
|
|
||||||
}
|
|
||||||
|
|
||||||
[LoggingValidation]
|
|
||||||
[Display(Order = 10001)]
|
|
||||||
public string LastProperty { get; set; }
|
|
||||||
|
|
||||||
[LoggingValidation]
|
|
||||||
public string Property3 { get; set; }
|
|
||||||
[LoggingValidation]
|
|
||||||
public string Property1 { get; set; }
|
|
||||||
[LoggingValidation]
|
|
||||||
public string Property2 { get; set; }
|
|
||||||
|
|
||||||
[LoggingValidation]
|
|
||||||
[Display(Order = 23)]
|
|
||||||
public string OrderedProperty3 { get; set; }
|
|
||||||
[LoggingValidation]
|
|
||||||
[Display(Order = 23)]
|
|
||||||
public string OrderedProperty2 { get; set; }
|
|
||||||
[LoggingValidation]
|
|
||||||
[Display(Order = 23)]
|
|
||||||
public string OrderedProperty1 { get; set; }
|
|
||||||
|
|
||||||
private sealed class LoggingValidationAttribute : ValidationAttribute
|
|
||||||
{
|
|
||||||
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
|
|
||||||
{
|
|
||||||
Assert.Null(value);
|
|
||||||
Assert.NotNull(validationContext);
|
|
||||||
var nonValidatableObject =
|
|
||||||
Assert.IsType<LoggingNonValidatableObject>(validationContext.ObjectInstance);
|
|
||||||
|
|
||||||
nonValidatableObject._log.Add(
|
|
||||||
string.Format("In LoggingValidatonAttribute.IsValid({0})", validationContext.MemberName));
|
|
||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -147,28 +147,46 @@ namespace Microsoft.AspNet.Mvc
|
||||||
setup.Configure(mvcOptions);
|
setup.Configure(mvcOptions);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(5, mvcOptions.ValidationExcludeFilters.Count);
|
Assert.Equal(8, mvcOptions.ValidationExcludeFilters.Count);
|
||||||
|
var i = 0;
|
||||||
|
|
||||||
// Verify if the delegates registered by default exclude the given types.
|
// Verify if the delegates registered by default exclude the given types.
|
||||||
Assert.Equal(typeof(SimpleTypesExcludeFilter), mvcOptions.ValidationExcludeFilters[0].OptionType);
|
Assert.Equal(typeof(SimpleTypesExcludeFilter), mvcOptions.ValidationExcludeFilters[i++].OptionType);
|
||||||
Assert.Equal(typeof(DefaultTypeBasedExcludeFilter), mvcOptions.ValidationExcludeFilters[1].OptionType);
|
Assert.Equal(typeof(DefaultTypeBasedExcludeFilter), mvcOptions.ValidationExcludeFilters[i].OptionType);
|
||||||
var xObjectFilter
|
var xObjectFilter
|
||||||
= Assert.IsType<DefaultTypeBasedExcludeFilter>(mvcOptions.ValidationExcludeFilters[1].Instance);
|
= Assert.IsType<DefaultTypeBasedExcludeFilter>(mvcOptions.ValidationExcludeFilters[i++].Instance);
|
||||||
Assert.Equal(xObjectFilter.ExcludedType, typeof(XObject));
|
Assert.Equal(xObjectFilter.ExcludedType, typeof(XObject));
|
||||||
|
|
||||||
Assert.Equal(typeof(DefaultTypeBasedExcludeFilter), mvcOptions.ValidationExcludeFilters[2].OptionType);
|
Assert.Equal(typeof(DefaultTypeBasedExcludeFilter), mvcOptions.ValidationExcludeFilters[i].OptionType);
|
||||||
var typeFilter =
|
var typeFilter
|
||||||
Assert.IsType<DefaultTypeBasedExcludeFilter>(mvcOptions.ValidationExcludeFilters[2].Instance);
|
= Assert.IsType<DefaultTypeBasedExcludeFilter>(mvcOptions.ValidationExcludeFilters[i++].Instance);
|
||||||
Assert.Equal(typeFilter.ExcludedType, typeof(Type));
|
Assert.Equal(typeFilter.ExcludedType, typeof(Type));
|
||||||
|
|
||||||
Assert.Equal(typeof(DefaultTypeBasedExcludeFilter), mvcOptions.ValidationExcludeFilters[3].OptionType);
|
Assert.Equal(typeof(DefaultTypeBasedExcludeFilter), mvcOptions.ValidationExcludeFilters[i].OptionType);
|
||||||
var jTokenFilter
|
var jTokenFilter
|
||||||
= Assert.IsType<DefaultTypeBasedExcludeFilter>(mvcOptions.ValidationExcludeFilters[3].Instance);
|
= Assert.IsType<DefaultTypeBasedExcludeFilter>(mvcOptions.ValidationExcludeFilters[i++].Instance);
|
||||||
Assert.Equal(jTokenFilter.ExcludedType, typeof(JToken));
|
Assert.Equal(jTokenFilter.ExcludedType, typeof(JToken));
|
||||||
|
|
||||||
Assert.Equal(typeof(DefaultTypeNameBasedExcludeFilter), mvcOptions.ValidationExcludeFilters[4].OptionType);
|
Assert.Equal(typeof(DefaultTypeBasedExcludeFilter), mvcOptions.ValidationExcludeFilters[i].OptionType);
|
||||||
|
var cancellationTokenFilter
|
||||||
|
= Assert.IsType<DefaultTypeBasedExcludeFilter>(mvcOptions.ValidationExcludeFilters[i++].Instance);
|
||||||
|
Assert.Equal(cancellationTokenFilter.ExcludedType, typeof(System.Threading.CancellationToken));
|
||||||
|
|
||||||
|
Assert.Equal(typeof(DefaultTypeBasedExcludeFilter), mvcOptions.ValidationExcludeFilters[i].OptionType);
|
||||||
|
var formFileFilter
|
||||||
|
= Assert.IsType<DefaultTypeBasedExcludeFilter>(mvcOptions.ValidationExcludeFilters[i++].Instance);
|
||||||
|
Assert.Equal(formFileFilter.ExcludedType, typeof(Http.IFormFile));
|
||||||
|
|
||||||
|
Assert.Equal(
|
||||||
|
typeof(DefaultTypeBasedExcludeFilter),
|
||||||
|
mvcOptions.ValidationExcludeFilters[i].OptionType);
|
||||||
|
var formCollectionFilter
|
||||||
|
= Assert.IsType<DefaultTypeBasedExcludeFilter>(mvcOptions.ValidationExcludeFilters[i++].Instance);
|
||||||
|
Assert.Equal(formCollectionFilter.ExcludedType, typeof(Http.IFormCollection));
|
||||||
|
|
||||||
|
Assert.Equal(typeof(DefaultTypeNameBasedExcludeFilter), mvcOptions.ValidationExcludeFilters[i].OptionType);
|
||||||
var xmlNodeFilter =
|
var xmlNodeFilter =
|
||||||
Assert.IsType<DefaultTypeNameBasedExcludeFilter>(mvcOptions.ValidationExcludeFilters[4].Instance);
|
Assert.IsType<DefaultTypeNameBasedExcludeFilter>(mvcOptions.ValidationExcludeFilters[i++].Instance);
|
||||||
Assert.Equal(xmlNodeFilter.ExcludedTypeName, "System.Xml.XmlNode");
|
Assert.Equal(xmlNodeFilter.ExcludedTypeName, "System.Xml.XmlNode");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ namespace ModelBindingWebSite.Controllers
|
||||||
|
|
||||||
private class OrderStatusBinder : IModelBinder
|
private class OrderStatusBinder : IModelBinder
|
||||||
{
|
{
|
||||||
public Task<bool> BindModelAsync(ModelBindingContext bindingContext)
|
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
if (typeof(OrderStatus).IsAssignableFrom(bindingContext.ModelType))
|
if (typeof(OrderStatus).IsAssignableFrom(bindingContext.ModelType))
|
||||||
{
|
{
|
||||||
|
|
@ -90,21 +90,17 @@ namespace ModelBindingWebSite.Controllers
|
||||||
// Doing something slightly different here to make sure we don't get accidentally bound
|
// Doing something slightly different here to make sure we don't get accidentally bound
|
||||||
// by the type converter binder.
|
// by the type converter binder.
|
||||||
OrderStatus model;
|
OrderStatus model;
|
||||||
if (Enum.TryParse<OrderStatus>("Status" + request.Query.Get("status"), out model))
|
var isModelSet = Enum.TryParse<OrderStatus>("Status" + request.Query.Get("status"), out model);
|
||||||
{
|
return Task.FromResult(new ModelBindingResult(model, "status", isModelSet));
|
||||||
bindingContext.Model = model;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.FromResult(false);
|
return Task.FromResult<ModelBindingResult>(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ProductModelBinder : IModelBinder
|
private class ProductModelBinder : IModelBinder
|
||||||
{
|
{
|
||||||
public async Task<bool> BindModelAsync(ModelBindingContext bindingContext)
|
public async Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
if (typeof(Product).IsAssignableFrom(bindingContext.ModelType))
|
if (typeof(Product).IsAssignableFrom(bindingContext.ModelType))
|
||||||
{
|
{
|
||||||
|
|
@ -120,11 +116,10 @@ namespace ModelBindingWebSite.Controllers
|
||||||
var value = await bindingContext.ValueProvider.GetValueAsync(key);
|
var value = await bindingContext.ValueProvider.GetValueAsync(key);
|
||||||
model.ProductId = (int)value.ConvertTo(typeof(int));
|
model.ProductId = (int)value.ConvertTo(typeof(int));
|
||||||
|
|
||||||
bindingContext.Model = model;
|
return new ModelBindingResult(model, key, true);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace ModelBindingWebSite.Controllers
|
||||||
|
{
|
||||||
|
[Route("Validation/[Action]")]
|
||||||
|
public class ValidationController : Controller
|
||||||
|
{
|
||||||
|
public bool SkipValidation(Resident resident)
|
||||||
|
{
|
||||||
|
return ModelState.IsValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AvoidRecursive(SelfishPerson selfishPerson)
|
||||||
|
{
|
||||||
|
return ModelState.IsValid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SelfishPerson
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public SelfishPerson MySelf { get { return this; } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace ModelBindingWebSite
|
namespace ModelBindingWebSite
|
||||||
{
|
{
|
||||||
public class Address
|
public class Address
|
||||||
{
|
{
|
||||||
public int Street { get; set; }
|
public int Street { get; set; }
|
||||||
public string State { get; set; }
|
public string State { get; set; }
|
||||||
|
|
||||||
|
[Range(10000, 99999)]
|
||||||
public int Zip { get; set; }
|
public int Zip { get; set; }
|
||||||
|
|
||||||
public Country Country { get; set; }
|
public Country Country { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace ModelBindingWebSite
|
||||||
|
{
|
||||||
|
public class Resident : Person
|
||||||
|
{
|
||||||
|
public IEnumerable<Address> ShippingAddresses { get; set; }
|
||||||
|
|
||||||
|
public Address HomeAddress { get; set; }
|
||||||
|
|
||||||
|
[FromBody]
|
||||||
|
public Address OfficeAddress { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -30,6 +30,7 @@ namespace ModelBindingWebSite
|
||||||
m.ModelBinders.Insert(0, typeof(TestBindingSourceModelBinder));
|
m.ModelBinders.Insert(0, typeof(TestBindingSourceModelBinder));
|
||||||
|
|
||||||
m.AddXmlDataContractSerializerFormatter();
|
m.AddXmlDataContractSerializerFormatter();
|
||||||
|
m.ValidationExcludeFilters.Add(typeof(Address));
|
||||||
});
|
});
|
||||||
|
|
||||||
services.AddSingleton<ICalculator, DefaultCalculator>();
|
services.AddSingleton<ICalculator, DefaultCalculator>();
|
||||||
|
|
|
||||||
|
|
@ -15,17 +15,17 @@ namespace ModelBindingWebSite
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Task BindModelCoreAsync(ModelBindingContext bindingContext)
|
protected override Task<ModelBindingResult> BindModelCoreAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
var metadata = (FromTestAttribute)bindingContext.ModelMetadata.BinderMetadata;
|
var metadata = (FromTestAttribute)bindingContext.ModelMetadata.BinderMetadata;
|
||||||
bindingContext.Model = metadata.Value;
|
var model = metadata.Value;
|
||||||
|
|
||||||
if (!IsSimpleType(bindingContext.ModelType))
|
if (!IsSimpleType(bindingContext.ModelType))
|
||||||
{
|
{
|
||||||
bindingContext.Model = Activator.CreateInstance(bindingContext.ModelType);
|
model = Activator.CreateInstance(bindingContext.ModelType);
|
||||||
|
return Task.FromResult(new ModelBindingResult(model, bindingContext.ModelName, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.FromResult(true);
|
return Task.FromResult(new ModelBindingResult(null, bindingContext.ModelName, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsSimpleType(Type type)
|
private bool IsSimpleType(Type type)
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
index:@GetType().GetTypeInfo().Assembly.GetName()
|
index:@GetType().GetTypeInfo().Assembly.GetName()
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
Layout:@GetType().GetTypeInfo().Assembly.FullName
|
Layout:@GetType().GetTypeInfo().Assembly.FullName
|
||||||
@RenderBody()
|
@RenderBody()
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
@using System.Reflection
|
@using System.Reflection
|
||||||
@{ Layout = "/Views/Home/Layout.cshtml";}
|
@{ Layout = "/Views/Home/Layout.cshtml";}
|
||||||
_viewstart:@GetType().GetTypeInfo().Assembly.FullName
|
_viewstart:@GetType().GetTypeInfo().Assembly.FullName
|
||||||
|
|
||||||
Loading…
Reference in New Issue