diff --git a/Mvc.sln b/Mvc.sln
index eef6514905..098e30615f 100644
--- a/Mvc.sln
+++ b/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|x86.ActiveCfg = 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.Build.0 = Debug|Any CPU
{A19022EF-9BA3-4349-94E4-F48E13E1C8AE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
diff --git a/src/Microsoft.AspNet.Mvc.Core/Controller.cs b/src/Microsoft.AspNet.Mvc.Core/Controller.cs
index 39a4541c0c..1bb65494ad 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Controller.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Controller.cs
@@ -135,6 +135,9 @@ namespace Microsoft.AspNet.Mvc
[FromServices]
public IUrlHelper Url { get; set; }
+ [FromServices]
+ public IObjectModelValidator ObjectValidator { get; set; }
+
///
/// Gets or sets the for user associated with the executing action.
///
@@ -938,6 +941,7 @@ namespace Microsoft.AspNet.Mvc
MetadataProvider,
BindingContext.ModelBinder,
valueProvider,
+ ObjectValidator,
BindingContext.ValidatorProvider);
}
@@ -975,6 +979,7 @@ namespace Microsoft.AspNet.Mvc
MetadataProvider,
BindingContext.ModelBinder,
BindingContext.ValueProvider,
+ ObjectValidator,
BindingContext.ValidatorProvider,
includeExpressions);
}
@@ -1012,6 +1017,7 @@ namespace Microsoft.AspNet.Mvc
MetadataProvider,
BindingContext.ModelBinder,
BindingContext.ValueProvider,
+ ObjectValidator,
BindingContext.ValidatorProvider,
predicate);
}
@@ -1052,6 +1058,7 @@ namespace Microsoft.AspNet.Mvc
MetadataProvider,
BindingContext.ModelBinder,
valueProvider,
+ ObjectValidator,
BindingContext.ValidatorProvider,
includeExpressions);
}
@@ -1091,6 +1098,7 @@ namespace Microsoft.AspNet.Mvc
MetadataProvider,
BindingContext.ModelBinder,
valueProvider,
+ ObjectValidator,
BindingContext.ValidatorProvider,
predicate);
}
@@ -1128,21 +1136,15 @@ namespace Microsoft.AspNet.Mvc
modelAccessor: () => model,
modelType: model.GetType());
+ var modelName = prefix ?? string.Empty;
var validationContext = new ModelValidationContext(
- MetadataProvider,
+ modelName,
BindingContext.ValidatorProvider,
ModelState,
modelMetadata,
containerMetadata: null);
- var modelName = prefix ?? string.Empty;
-
- var validationNode = new ModelValidationNode(modelMetadata, modelName)
- {
- ValidateAllProperties = true
- };
- validationNode.Validate(validationContext);
-
+ ObjectValidator.Validate(validationContext);
return ModelState.IsValid;
}
diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs
index 0096ce1373..59661da018 100644
--- a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs
@@ -18,13 +18,16 @@ namespace Microsoft.AspNet.Mvc
{
private readonly IModelMetadataProvider _modelMetadataProvider;
private readonly MvcOptions _options;
+ private readonly IObjectModelValidator _validator;
public DefaultControllerActionArgumentBinder(
IModelMetadataProvider modelMetadataProvider,
+ IObjectModelValidator validator,
IOptions optionsAccessor)
{
_modelMetadataProvider = modelMetadataProvider;
_options = optionsAccessor.Options;
+ _validator = validator;
}
public async Task> GetActionArgumentsAsync(
@@ -91,15 +94,21 @@ namespace Microsoft.AspNet.Mvc
var modelState = actionContext.ModelState;
modelState.MaxAllowedErrors = _options.MaxModelValidationErrors;
-
foreach (var parameter in parameterMetadata)
{
var parameterType = parameter.ModelType;
var modelBindingContext = GetModelBindingContext(parameter, modelState, operationBindingContext);
- if (await bindingContext.ModelBinder.BindModelAsync(modelBindingContext) &&
- modelBindingContext.IsModelSet)
+ var modelBindingResult = await bindingContext.ModelBinder.BindModelAsync(modelBindingContext);
+ 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);
}
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs
index e6f23cf57b..98ad787728 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs
@@ -19,7 +19,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private readonly ActionContext _actionContext;
private readonly IScopedInstance _bindingContext;
private readonly IInputFormatterSelector _formatterSelector;
- private readonly IBodyModelValidator _bodyModelValidator;
private readonly IValidationExcludeFiltersProvider _bodyValidationExcludeFiltersProvider;
///
@@ -28,26 +27,23 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// An accessor to the .
/// An accessor to the .
/// The .
- /// The .
///
/// The .
///
public BodyModelBinder([NotNull] IScopedInstance context,
[NotNull] IScopedInstance bindingContext,
[NotNull] IInputFormatterSelector selector,
- [NotNull] IBodyModelValidator bodyModelValidator,
[NotNull] IValidationExcludeFiltersProvider bodyValidationExcludeFiltersProvider)
: base(BindingSource.Body)
{
_actionContext = context.Value;
_bindingContext = bindingContext;
_formatterSelector = selector;
- _bodyModelValidator = bodyModelValidator;
_bodyValidationExcludeFiltersProvider = bodyValidationExcludeFiltersProvider;
}
///
- protected async override Task BindModelCoreAsync([NotNull] ModelBindingContext bindingContext)
+ protected async override Task BindModelCoreAsync([NotNull] ModelBindingContext bindingContext)
{
var formatters = _bindingContext.Value.InputFormatters;
@@ -59,20 +55,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var unsupportedContentType = Resources.FormatUnsupportedContentType(
bindingContext.OperationBindingContext.HttpContext.Request.ContentType);
bindingContext.ModelState.AddModelError(bindingContext.ModelName, unsupportedContentType);
- return;
+ return new ModelBindingResult(null, bindingContext.ModelName, isModelSet: false);
}
- bindingContext.Model = await formatter.ReadAsync(formatterContext);
-
- // 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);
+ var model = await formatter.ReadAsync(formatterContext);
+ return new ModelBindingResult(model, bindingContext.ModelName, isModelSet: true);
}
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinders/ServicesModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinders/ServicesModelBinder.cs
index 07f4f809b4..79e65e0480 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ModelBinders/ServicesModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinders/ServicesModelBinder.cs
@@ -21,11 +21,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
///
- protected override Task BindModelCoreAsync([NotNull] ModelBindingContext bindingContext)
+ protected override Task BindModelCoreAsync([NotNull] ModelBindingContext bindingContext)
{
var requestServices = bindingContext.OperationBindingContext.HttpContext.RequestServices;
- bindingContext.Model = requestServices.GetRequiredService(bindingContext.ModelType);
- return Task.FromResult(true);
+ var model = requestServices.GetRequiredService(bindingContext.ModelType);
+ return Task.FromResult(new ModelBindingResult(model, bindingContext.ModelName, true));
}
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/ModelBindingHelper.cs b/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/ModelBindingHelper.cs
index 86775f3a85..5285541e1f 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/ModelBindingHelper.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/ModelBindingHelper.cs
@@ -29,6 +29,8 @@ namespace Microsoft.AspNet.Mvc
/// The provider used for reading metadata for the model type.
/// The used for binding.
/// The used for looking up values.
+ /// The used for validating the
+ /// bound values.
/// The used for executing validation
/// on the model instance.
/// A that on completion returns true if the update is successful
@@ -40,6 +42,7 @@ namespace Microsoft.AspNet.Mvc
[NotNull] IModelMetadataProvider metadataProvider,
[NotNull] IModelBinder modelBinder,
[NotNull] IValueProvider valueProvider,
+ [NotNull] IObjectModelValidator objectModelValidator,
[NotNull] IModelValidatorProvider validatorProvider)
where TModel : class
{
@@ -52,6 +55,7 @@ namespace Microsoft.AspNet.Mvc
metadataProvider,
modelBinder,
valueProvider,
+ objectModelValidator,
validatorProvider,
predicate: (context, propertyName) => true);
}
@@ -71,6 +75,8 @@ namespace Microsoft.AspNet.Mvc
/// The provider used for reading metadata for the model type.
/// The used for binding.
/// The used for looking up values.
+ /// The used for validating the
+ /// bound values.
/// The used for executing validation
/// on the model
/// instance.
@@ -85,6 +91,7 @@ namespace Microsoft.AspNet.Mvc
[NotNull] IModelMetadataProvider metadataProvider,
[NotNull] IModelBinder modelBinder,
[NotNull] IValueProvider valueProvider,
+ [NotNull] IObjectModelValidator objectModelValidator,
[NotNull] IModelValidatorProvider validatorProvider,
[NotNull] params Expression>[] includeExpressions)
where TModel : class
@@ -100,6 +107,7 @@ namespace Microsoft.AspNet.Mvc
metadataProvider,
modelBinder,
valueProvider,
+ objectModelValidator,
validatorProvider,
predicate: predicate);
}
@@ -119,6 +127,8 @@ namespace Microsoft.AspNet.Mvc
/// The provider used for reading metadata for the model type.
/// The used for binding.
/// The used for looking up values.
+ /// /// The used for validating the
+ /// bound values.
/// The used for executing validation
/// on the model instance.
/// A predicate which can be used to
@@ -132,12 +142,13 @@ namespace Microsoft.AspNet.Mvc
[NotNull] IModelMetadataProvider metadataProvider,
[NotNull] IModelBinder modelBinder,
[NotNull] IValueProvider valueProvider,
+ [NotNull] IObjectModelValidator objectModelValidator,
[NotNull] IModelValidatorProvider validatorProvider,
[NotNull] Func predicate)
where TModel : class
{
var modelMetadata = metadataProvider.GetMetadataForType(
- modelAccessor: null,
+ modelAccessor: () => model,
modelType: model.GetType());
var operationBindingContext = new OperationBindingContext
@@ -152,7 +163,6 @@ namespace Microsoft.AspNet.Mvc
{
ModelMetadata = modelMetadata,
ModelName = prefix,
- Model = model,
ModelState = modelState,
ValueProvider = valueProvider,
FallbackToEmptyPrefix = true,
@@ -160,8 +170,12 @@ namespace Microsoft.AspNet.Mvc
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;
}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ArrayModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ArrayModelBinder.cs
index e231b5c00b..414903b27c 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ArrayModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ArrayModelBinder.cs
@@ -9,21 +9,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class ArrayModelBinder : CollectionModelBinder
{
- public override Task BindModelAsync(ModelBindingContext bindingContext)
+ public override Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext.ModelMetadata.IsReadOnly)
{
- return Task.FromResult(false);
+ return Task.FromResult(null);
}
return base.BindModelAsync(bindingContext);
}
- protected override bool CreateOrReplaceCollection(ModelBindingContext bindingContext,
- IList newCollection)
+ protected override object GetModel(IEnumerable newCollection)
{
- bindingContext.Model = newCollection.ToArray();
- return true;
+ return newCollection.ToArray();
}
}
}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/BinderTypeBasedModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/BinderTypeBasedModelBinder.cs
index 714325c68a..9d595443df 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/BinderTypeBasedModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/BinderTypeBasedModelBinder.cs
@@ -21,13 +21,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
new ConcurrentDictionary();
- public async Task BindModelAsync(ModelBindingContext bindingContext)
+ public async Task BindModelAsync(ModelBindingContext bindingContext)
{
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.
- return false;
+ return null;
}
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
- // specified by metadata.
- return true;
+ var modelBindingResult = result != null ?
+ new ModelBindingResult(result.Model, result.Key, result.IsModelSet) :
+ 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;
}
}
}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/BindingSourceModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/BindingSourceModelBinder.cs
index b89d479c98..bc1967782f 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/BindingSourceModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/BindingSourceModelBinder.cs
@@ -58,10 +58,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
///
/// A which will complete when model binding has completed.
///
- protected abstract Task BindModelCoreAsync([NotNull] ModelBindingContext bindingContext);
+ protected abstract Task BindModelCoreAsync([NotNull] ModelBindingContext bindingContext);
///
- public async Task BindModelAsync(ModelBindingContext context)
+ public async Task BindModelAsync(ModelBindingContext context)
{
var bindingSourceMetadata = context.ModelMetadata.BinderMetadata as IBindingSourceMetadata;
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
// 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
// its binding source.
- return true;
+ return modelBindingResult;
}
}
}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ByteArrayModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ByteArrayModelBinder.cs
index 6c8bcabf60..6a39463452 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ByteArrayModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ByteArrayModelBinder.cs
@@ -13,11 +13,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public class ByteArrayModelBinder : IModelBinder
{
///
- public async Task BindModelAsync([NotNull] ModelBindingContext bindingContext)
+ public async Task BindModelAsync([NotNull] ModelBindingContext bindingContext)
{
if (bindingContext.ModelType != typeof(byte[]))
{
- return false;
+ return null;
}
var valueProviderResult = await bindingContext.ValueProvider.GetValueAsync(bindingContext.ModelName);
@@ -25,7 +25,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// case 1: there was no element containing this data
if (valueProviderResult == null)
{
- return false;
+ return null;
}
var value = valueProviderResult.AttemptedValue;
@@ -33,19 +33,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// case 2: there was an element but it was left blank
if (string.IsNullOrEmpty(value))
{
- return false;
+ return null;
}
try
{
- bindingContext.Model = Convert.FromBase64String(value);
+ var model = Convert.FromBase64String(value);
+ return new ModelBindingResult(model, bindingContext.ModelName, true);
}
catch (Exception ex)
{
bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, ex);
}
- return true;
+ return new ModelBindingResult(model: null, key: bindingContext.ModelName, isModelSet: false);
}
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CancellationTokenModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CancellationTokenModelBinder.cs
index 9313dc17ab..0eb879f649 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CancellationTokenModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CancellationTokenModelBinder.cs
@@ -12,15 +12,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public class CancellationTokenModelBinder : IModelBinder
{
///
- public Task BindModelAsync(ModelBindingContext bindingContext)
+ public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext.ModelType == typeof(CancellationToken))
{
- bindingContext.Model = bindingContext.OperationBindingContext.HttpContext.RequestAborted;
- return Task.FromResult(true);
+ var model = bindingContext.OperationBindingContext.HttpContext.RequestAborted;
+ return Task.FromResult(new ModelBindingResult(model, bindingContext.ModelName, true));
}
- return Task.FromResult(false);
+ return Task.FromResult(null);
}
}
}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CollectionModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CollectionModelBinder.cs
index f2e6ff25d1..26d579d5dd 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CollectionModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CollectionModelBinder.cs
@@ -13,13 +13,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class CollectionModelBinder : IModelBinder
{
- public virtual async Task BindModelAsync(ModelBindingContext bindingContext)
+ public virtual async Task BindModelAsync(ModelBindingContext bindingContext)
{
ModelBindingHelper.ValidateBindingContext(bindingContext);
if (!await bindingContext.ValueProvider.ContainsPrefixAsync(bindingContext.ModelName))
{
- return false;
+ return null;
}
var valueProviderResult = await bindingContext.ValueProvider.GetValueAsync(bindingContext.ModelName);
@@ -27,13 +27,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
BindSimpleCollection(bindingContext, valueProviderResult.RawValue, valueProviderResult.Culture) :
BindComplexCollection(bindingContext);
var boundCollection = await bindCollectionTask;
-
- return CreateOrReplaceCollection(bindingContext, boundCollection);
+ var model = GetModel(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
// is [ "1", "2" ] and needs to be converted to an int[].
- internal async Task> BindSimpleCollection(ModelBindingContext bindingContext,
+ internal async Task> BindSimpleCollection(ModelBindingContext bindingContext,
object rawValue,
CultureInfo culture)
{
@@ -62,10 +62,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
};
object boundValue = null;
- if (await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(innerBindingContext))
+ var result = await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(innerBindingContext);
+ if (result != null)
{
- boundValue = innerBindingContext.Model;
- bindingContext.ValidationNode.ChildNodes.Add(innerBindingContext.ValidationNode);
+ boundValue = result.Model;
}
boundCollection.Add(ModelBindingHelper.CastOrDefault(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].
- private async Task> BindComplexCollection(ModelBindingContext bindingContext)
+ private async Task> BindComplexCollection(ModelBindingContext bindingContext)
{
var indexPropertyName = ModelBindingHelper.CreatePropertyModelName(bindingContext.ModelName, "index");
var valueProviderResultIndex = await bindingContext.ValueProvider.GetValueAsync(indexPropertyName);
@@ -82,7 +82,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return await BindComplexCollectionFromIndexes(bindingContext, indexNames);
}
- internal async Task> BindComplexCollectionFromIndexes(ModelBindingContext bindingContext,
+ internal async Task> BindComplexCollectionFromIndexes(ModelBindingContext bindingContext,
IEnumerable indexNames)
{
bool indexNamesIsFinite;
@@ -110,13 +110,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var modelType = bindingContext.ModelType;
- if (await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(childBindingContext))
+ var result = await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(childBindingContext);
+ if (result != null)
{
didBind = true;
- boundValue = childBindingContext.Model;
-
- // merge validation up
- bindingContext.ValidationNode.ChildNodes.Add(childBindingContext.ValidationNode);
+ boundValue = result.Model;
}
// 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
// being returned from the binder.
- protected virtual bool CreateOrReplaceCollection(ModelBindingContext bindingContext,
- IList newCollection)
+ protected virtual object GetModel(IEnumerable newCollection)
{
- CreateOrReplaceCollection(bindingContext, newCollection, () => new List());
- return true;
+ return newCollection;
}
internal static object[] RawValueToObjectArray(object rawValue)
@@ -165,23 +161,5 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// fallback
return new[] { rawValue };
}
-
- internal static void CreateOrReplaceCollection(ModelBindingContext bindingContext,
- IEnumerable incomingElements,
- Func> creator)
- {
- var collection = bindingContext.Model as ICollection;
- if (collection == null || collection.IsReadOnly)
- {
- collection = creator();
- bindingContext.Model = collection;
- }
-
- collection.Clear();
- foreach (var element in incomingElements)
- {
- collection.Add(element);
- }
- }
}
}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ComplexModelDto.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ComplexModelDto.cs
index 543f6de1fa..a3c2e92b34 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ComplexModelDto.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ComplexModelDto.cs
@@ -15,7 +15,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
ModelMetadata = modelMetadata;
PropertyMetadata = new Collection(propertyMetadata.ToList());
- Results = new Dictionary();
+ Results = new Dictionary();
}
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
// was never attempted, this dictionary will not contain a corresponding
// entry.
- public IDictionary Results { get; private set; }
+ public IDictionary Results { get; private set; }
}
}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ComplexModelDtoModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ComplexModelDtoModelBinder.cs
index c92fe2540b..7e65069dcc 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ComplexModelDtoModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ComplexModelDtoModelBinder.cs
@@ -8,37 +8,38 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
public sealed class ComplexModelDtoModelBinder : IModelBinder
{
- public async Task BindModelAsync(ModelBindingContext bindingContext)
+ public async Task BindModelAsync(ModelBindingContext bindingContext)
{
- if (bindingContext.ModelType == typeof(ComplexModelDto))
+ if (bindingContext.ModelType != typeof(ComplexModelDto))
{
- ModelBindingHelper.ValidateBindingContext(bindingContext,
- 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 null;
}
- 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);
}
}
}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ComplexModelDtoResult.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ComplexModelDtoResult.cs
deleted file mode 100644
index cd298dd477..0000000000
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ComplexModelDtoResult.cs
+++ /dev/null
@@ -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; }
- }
-}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs
index a39638b1cc..e34104e597 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
+using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
@@ -30,97 +31,86 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
///
public IReadOnlyList ModelBinders { get; }
- public virtual async Task BindModelAsync([NotNull] ModelBindingContext bindingContext)
+ public virtual async Task BindModelAsync([NotNull] ModelBindingContext bindingContext)
{
var newBindingContext = CreateNewBindingContext(bindingContext,
- bindingContext.ModelName,
- reuseValidationNode: true);
+ bindingContext.ModelName);
- var boundSuccessfully = await TryBind(newBindingContext);
- if (!boundSuccessfully && !string.IsNullOrEmpty(bindingContext.ModelName)
+ var modelBindingResult = await TryBind(newBindingContext);
+ if (modelBindingResult == null && !string.IsNullOrEmpty(bindingContext.ModelName)
&& bindingContext.FallbackToEmptyPrefix)
{
// fallback to empty prefix?
newBindingContext = CreateNewBindingContext(bindingContext,
- modelName: string.Empty,
- reuseValidationNode: false);
- boundSuccessfully = await TryBind(newBindingContext);
+ modelName: string.Empty);
+ modelBindingResult = await TryBind(newBindingContext);
}
- if (!boundSuccessfully)
+ if (modelBindingResult == null)
{
- return false; // 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);
+ return null; // something went wrong
}
bindingContext.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 TryBind(ModelBindingContext bindingContext)
+ private async Task TryBind(ModelBindingContext bindingContext)
{
RuntimeHelpers.EnsureSufficientExecutionStack();
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.
- return false;
- }
-
- 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);
+ return null;
}
private static ModelBindingContext CreateNewBindingContext(ModelBindingContext oldBindingContext,
- string modelName,
- bool reuseValidationNode)
+ string modelName)
{
var newBindingContext = new ModelBindingContext
{
- IsModelSet = oldBindingContext.IsModelSet,
ModelMetadata = oldBindingContext.ModelMetadata,
ModelName = modelName,
ModelState = oldBindingContext.ModelState,
@@ -129,12 +119,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
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);
// If the property has a specified data binding sources, we need to filter the set of value providers
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/DictionaryModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/DictionaryModelBinder.cs
index 53cea8a8d0..328670a8f4 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/DictionaryModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/DictionaryModelBinder.cs
@@ -3,37 +3,15 @@
using System;
using System.Collections.Generic;
+using System.Linq;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class DictionaryModelBinder : CollectionModelBinder>
{
- protected override bool CreateOrReplaceCollection(ModelBindingContext bindingContext,
- IList> newCollection)
+ protected override object GetModel(IEnumerable> newCollection)
{
- CreateOrReplaceDictionary(bindingContext, newCollection, () => new Dictionary());
- return true;
- }
-
- private static void CreateOrReplaceDictionary(ModelBindingContext bindingContext,
- IEnumerable> incomingElements,
- Func> creator)
- {
- var dictionary = bindingContext.Model as IDictionary;
- 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;
- }
- }
+ return newCollection.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
}
}
}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/FormCollectionModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/FormCollectionModelBinder.cs
index 50a2407e28..31cb016a29 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/FormCollectionModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/FormCollectionModelBinder.cs
@@ -16,35 +16,36 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public class FormCollectionModelBinder : IModelBinder
{
///
- public async Task BindModelAsync([NotNull] ModelBindingContext bindingContext)
+ public async Task BindModelAsync([NotNull] ModelBindingContext bindingContext)
{
if (bindingContext.ModelType != typeof(IFormCollection) &&
bindingContext.ModelType != typeof(FormCollection))
{
- return false;
+ return null;
}
+ object model = null;
var request = bindingContext.OperationBindingContext.HttpContext.Request;
if (request.HasFormContentType)
{
var form = await request.ReadFormAsync();
if (bindingContext.ModelType.IsAssignableFrom(form.GetType()))
{
- bindingContext.Model = form;
+ model = form;
}
else
{
var formValuesLookup = form.ToDictionary(p => p.Key,
p => p.Value);
- bindingContext.Model = new FormCollection(formValuesLookup, form.Files);
+ model = new FormCollection(formValuesLookup, form.Files);
}
}
else
{
- bindingContext.Model = new FormCollection(new Dictionary());
+ model = new FormCollection(new Dictionary());
}
- return true;
+ return new ModelBindingResult(model, bindingContext.ModelName, true);
}
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/FormFileModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/FormFileModelBinder.cs
index e8796a45b4..d04ed36833 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/FormFileModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/FormFileModelBinder.cs
@@ -18,33 +18,23 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public class FormFileModelBinder : IModelBinder
{
///
- public async Task BindModelAsync([NotNull] ModelBindingContext bindingContext)
+ public async Task BindModelAsync([NotNull] ModelBindingContext bindingContext)
{
if (bindingContext.ModelType == typeof(IFormFile))
{
var postedFiles = await GetFormFilesAsync(bindingContext);
var value = postedFiles.FirstOrDefault();
- if (value != null)
- {
- bindingContext.Model = value;
- }
-
- return true;
+ return new ModelBindingResult(value, bindingContext.ModelName, value != null);
}
else if (typeof(IEnumerable).GetTypeInfo().IsAssignableFrom(
bindingContext.ModelType.GetTypeInfo()))
{
var postedFiles = await GetFormFilesAsync(bindingContext);
var value = ModelBindingHelper.ConvertValuesToCollectionType(bindingContext.ModelType, postedFiles);
- if (value != null)
- {
- bindingContext.Model = value;
- }
-
- return true;
+ return new ModelBindingResult(value, bindingContext.ModelName, value != null);
}
- return false;
+ return null;
}
private async Task> GetFormFilesAsync(ModelBindingContext bindingContext)
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/GenericModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/GenericModelBinder.cs
index 649dd32c67..286d5f261a 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/GenericModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/GenericModelBinder.cs
@@ -12,20 +12,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class GenericModelBinder : IModelBinder
{
- public async Task BindModelAsync(ModelBindingContext bindingContext)
+ public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var binderType = ResolveBinderType(bindingContext.ModelType);
if (binderType != null)
{
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
// true so that none of the other model binders participate.
- return true;
+ return modelBindingResult;
}
- return false;
+ return null;
}
private static Type ResolveBinderType(Type modelType)
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/HeaderModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/HeaderModelBinder.cs
index 89c0192458..c8fa10028b 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/HeaderModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/HeaderModelBinder.cs
@@ -23,19 +23,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
///
- protected override Task BindModelCoreAsync([NotNull] ModelBindingContext bindingContext)
+ protected override Task BindModelCoreAsync([NotNull] ModelBindingContext bindingContext)
{
var request = bindingContext.OperationBindingContext.HttpContext.Request;
var modelMetadata = bindingContext.ModelMetadata;
// 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;
+ object model = null;
if (bindingContext.ModelType == typeof(string))
{
var value = request.Headers.Get(headerName);
if (value != null)
{
- bindingContext.Model = value;
+ model = value;
}
}
else if (typeof(IEnumerable).GetTypeInfo().IsAssignableFrom(
@@ -44,14 +45,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var values = request.Headers.GetCommaSeparatedValues(headerName);
if (values != null)
{
- bindingContext.Model = ModelBindingHelper.ConvertValuesToCollectionType(
+ model = ModelBindingHelper.ConvertValuesToCollectionType(
bindingContext.ModelType,
values);
}
}
- // Always return true as header model binder is supposed to always handle IHeaderBinderMetadata.
- return Task.FromResult(true);
+ return Task.FromResult(new ModelBindingResult(model, bindingContext.ModelName, model != null));
}
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/IModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/IModelBinder.cs
index 538110dc17..e8724068e8 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/IModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/IModelBinder.cs
@@ -14,7 +14,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// Async function to bind to a particular model.
///
/// The binding context which has the object to be bound.
- /// A Task with a bool implying the success or failure of the operation.
- Task BindModelAsync(ModelBindingContext bindingContext);
+ /// A Task which on completion returns a which represents the result
+ /// of the model binding process.
+ ///
+ ///
+ /// A null return value means that this model binder was not able to handle the request.
+ /// Returning null ensures that subsequent model binders are run. If a non null value indicates
+ /// that the model binder was able to handle the request.
+ ///
+ Task BindModelAsync(ModelBindingContext bindingContext);
}
}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/KeyValuePairModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/KeyValuePairModelBinder.cs
index e04a17d282..4a75fbd432 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/KeyValuePairModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/KeyValuePairModelBinder.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
+using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
@@ -9,23 +10,39 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
public sealed class KeyValuePairModelBinder : IModelBinder
{
- public async Task BindModelAsync(ModelBindingContext bindingContext)
+ public async Task BindModelAsync(ModelBindingContext bindingContext)
{
ModelBindingHelper.ValidateBindingContext(bindingContext,
typeof(KeyValuePair),
allowNullModel: true);
- var keyResult = await TryBindStrongModel(bindingContext, "key");
- var valueResult = await TryBindStrongModel(bindingContext, "value");
-
- if (keyResult.Success && valueResult.Success)
+ var keyResult = await TryBindStrongModel(bindingContext, "Key");
+ var valueResult = await TryBindStrongModel(bindingContext, "Value");
+ var model = bindingContext.ModelMetadata.Model;
+ var isModelSet = false;
+ if (keyResult.IsModelSet && valueResult.IsModelSet)
{
- bindingContext.Model = new KeyValuePair(keyResult.Model, valueResult.Model);
+ model = new KeyValuePair(
+ ModelBindingHelper.CastOrDefault(keyResult.Model),
+ ModelBindingHelper.CastOrDefault(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> TryBindStrongModel(ModelBindingContext parentBindingContext,
+ internal async Task TryBindStrongModel(ModelBindingContext parentBindingContext,
string propertyName)
{
var propertyModelMetadata =
@@ -35,29 +52,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
ModelBindingHelper.CreatePropertyModelName(parentBindingContext.ModelName, propertyName);
var propertyBindingContext =
new ModelBindingContext(parentBindingContext, propertyModelName, propertyModelMetadata);
-
- if (await propertyBindingContext.OperationBindingContext.ModelBinder.BindModelAsync(propertyBindingContext))
+ var modelBindingResult =
+ await propertyBindingContext.OperationBindingContext.ModelBinder.BindModelAsync(propertyBindingContext);
+ if (modelBindingResult != null)
{
- var untypedModel = propertyBindingContext.Model;
- var model = ModelBindingHelper.CastOrDefault(untypedModel);
- parentBindingContext.ValidationNode.ChildNodes.Add(propertyBindingContext.ValidationNode);
- return new BindResult(success: true, model: model);
+ return modelBindingResult;
}
- return new BindResult(success: false, model: default(TModel));
- }
-
- internal sealed class BindResult
- {
- public BindResult(bool success, TModel model)
- {
- Success = success;
- Model = model;
- }
-
- public bool Success { get; private set; }
-
- public TModel Model { get; private set; }
+ return new ModelBindingResult(model: default(TModel), key: propertyModelName, isModelSet: false);
}
}
}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs
index 7c6a4458d8..8b98d5b1ca 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs
@@ -14,12 +14,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class MutableObjectModelBinder : IModelBinder
{
- public virtual async Task BindModelAsync(ModelBindingContext bindingContext)
+ public virtual async Task BindModelAsync(ModelBindingContext bindingContext)
{
ModelBindingHelper.ValidateBindingContext(bindingContext);
if (!CanBindType(bindingContext.ModelType))
{
- return false;
+ return null;
}
var mutableObjectBinderContext = new MutableObjectBinderContext()
@@ -30,17 +30,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
if (!(await CanCreateModel(mutableObjectBinderContext)))
{
- return false;
+ return null;
}
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
- ProcessDto(bindingContext, dto);
- // complex models require full validation
- bindingContext.ValidationNode.ValidateAllProperties = true;
- return true;
+ ProcessDto(bindingContext, (ComplexModelDto)result.Model);
+ return
+ new ModelBindingResult(bindingContext.ModelMetadata.Model, bindingContext.ModelName, isModelSet: true);
}
protected virtual bool CanUpdateProperty(ModelMetadata propertyMetadata)
@@ -236,7 +235,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return true;
}
- private async Task CreateAndPopulateDto(ModelBindingContext bindingContext,
+ private async Task CreateAndPopulateDto(ModelBindingContext bindingContext,
IEnumerable propertyMetadatas)
{
// create a DTO and call into the DTO binder
@@ -247,8 +246,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var dtoBindingContext =
new ModelBindingContext(bindingContext, bindingContext.ModelName, complexModelDtoMetadata);
- await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(dtoBindingContext);
- return (ComplexModelDto)dtoBindingContext.Model;
+ return await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(dtoBindingContext);
}
protected virtual object CreateModel(ModelBindingContext bindingContext)
@@ -258,36 +256,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return Activator.CreateInstance(bindingContext.ModelType);
}
- // Called when the property setter null check failed, allows us to add our own error message to ModelState.
- internal static EventHandler 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)
{
- 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);
// 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);
foreach (var missingRequiredProperty in validationInfo.RequiredProperties)
{
var addedError = false;
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
// 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,
out requiredValidator);
SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator);
- bindingContext.ValidationNode.ChildNodes.Add(dtoResult.ValidationNode);
}
}
}
protected virtual void SetProperty(ModelBindingContext bindingContext,
ModelMetadata propertyMetadata,
- ComplexModelDtoResult dtoResult,
+ ModelBindingResult dtoResult,
IModelValidator requiredValidator)
{
var bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase;
@@ -443,7 +415,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
object value;
var hasDefaultValue = false;
- if (dtoResult.IsModelBound)
+ if (dtoResult.IsModelSet)
{
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.
if (value == null)
{
- var modelStateKey = dtoResult.ValidationNode.ModelStateKey;
+ var modelStateKey = dtoResult.Key;
var validationState = bindingContext.ModelState.GetFieldValidationState(modelStateKey);
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
// value.
@@ -484,7 +456,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
try
{
- property.SetValue(bindingContext.Model, value);
+ property.SetValue(bindingContext.ModelMetadata.Model, value);
}
catch (Exception ex)
{
@@ -495,7 +467,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
ex = targetInvocationException.InnerException;
}
- var modelStateKey = dtoResult.ValidationNode.ModelStateKey;
+ var modelStateKey = dtoResult.Key;
var validationState = bindingContext.ModelState.GetFieldValidationState(modelStateKey);
if (validationState == ModelValidationState.Unvalidated)
{
@@ -506,11 +478,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
else
{
// 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);
if (validationState == ModelValidationState.Unvalidated)
{
- dtoResult.ValidationNode.Validated += CreateNullCheckFailedHandler(propertyMetadata, value);
+ var errorMessage = Resources.ModelBinderConfig_ValueRequired;
+ bindingContext.ModelState.TryAddModelError(modelStateKey, errorMessage);
}
}
}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/TypeConverterModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/TypeConverterModelBinder.cs
index 8a02d22683..bc9090aa33 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/TypeConverterModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/TypeConverterModelBinder.cs
@@ -9,20 +9,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
public sealed class TypeConverterModelBinder : IModelBinder
{
- public async Task BindModelAsync(ModelBindingContext bindingContext)
+ public async Task BindModelAsync(ModelBindingContext bindingContext)
{
ModelBindingHelper.ValidateBindingContext(bindingContext);
if (!TypeHelper.HasStringConverter(bindingContext.ModelType))
{
// this type cannot be converted
- return false;
+ return null;
}
var valueProviderResult = await bindingContext.ValueProvider.GetValueAsync(bindingContext.ModelName);
if (valueProviderResult == null)
{
- return false; // no entry
+ return null; // no entry
}
object newModel;
@@ -31,14 +31,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
newModel = valueProviderResult.ConvertTo(bindingContext.ModelType);
ModelBindingHelper.ReplaceEmptyStringWithNull(bindingContext.ModelMetadata, ref newModel);
- bindingContext.Model = newModel;
+ return new ModelBindingResult(newModel, bindingContext.ModelName, true);
}
catch (Exception ex)
{
bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, ex);
}
- return true;
+ return new ModelBindingResult(null, bindingContext.ModelName, false);
}
}
}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/TypeMatchModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/TypeMatchModelBinder.cs
index b23b1897f5..9c7085054f 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/TypeMatchModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/TypeMatchModelBinder.cs
@@ -8,21 +8,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
public sealed class TypeMatchModelBinder : IModelBinder
{
- public async Task BindModelAsync(ModelBindingContext bindingContext)
+ public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var valueProviderResult = await GetCompatibleValueProviderResult(bindingContext);
if (valueProviderResult == null)
{
// conversion would have failed
- return false;
+ return null;
}
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
var model = valueProviderResult.RawValue;
ModelBindingHelper.ReplaceEmptyStringWithNull(bindingContext.ModelMetadata, ref model);
- bindingContext.Model = model;
-
- return true;
+ return new ModelBindingResult(model, bindingContext.ModelName, true);
}
internal static async Task GetCompatibleValueProviderResult(ModelBindingContext context)
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/IObjectModelValidator.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/IObjectModelValidator.cs
new file mode 100644
index 0000000000..f1f593b2aa
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/IObjectModelValidator.cs
@@ -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
+{
+ ///
+ /// Provides methods to validate an object graph.
+ ///
+ public interface IObjectModelValidator
+ {
+ ///
+ /// Validates the given model in .
+ ///
+ /// The associated with the current call.
+ ///
+ void Validate(ModelValidationContext validationContext);
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Internal/CollectionModelBinderUtil.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Internal/CollectionModelBinderUtil.cs
index dff0d1a4e6..a95a874c51 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Internal/CollectionModelBinderUtil.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Internal/CollectionModelBinderUtil.cs
@@ -19,25 +19,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Internal
indexNames = indexes;
}
}
+
return indexNames;
}
-
- public static void CreateOrReplaceCollection(ModelBindingContext bindingContext,
- IEnumerable incomingElements,
- Func> creator)
- {
- var collection = bindingContext.Model as ICollection;
- if (collection == null || collection.IsReadOnly)
- {
- collection = creator();
- bindingContext.Model = collection;
- }
-
- collection.Clear();
- foreach (var element in incomingElements)
- {
- collection.Add(element);
- }
- }
}
}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Internal/ModelBindingHelper.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Internal/ModelBindingHelper.cs
index 37ea739747..60236648a4 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Internal/ModelBindingHelper.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Internal/ModelBindingHelper.cs
@@ -80,17 +80,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Internal
throw new ArgumentException(message, "bindingContext");
}
- if (!allowNullModel && bindingContext.Model == null)
+ if (!allowNullModel && bindingContext.ModelMetadata.Model == null)
{
var message = Resources.FormatModelBinderUtil_ModelCannotBeNull(requiredType);
throw new ArgumentException(message, "bindingContext");
}
- if (bindingContext.Model != null &&
+ if (bindingContext.ModelMetadata.Model != null &&
!bindingContext.ModelType.GetTypeInfo().IsAssignableFrom(requiredType.GetTypeInfo()))
{
var message = Resources.FormatModelBinderUtil_ModelInstanceIsWrong(
- bindingContext.Model.GetType(),
+ bindingContext.ModelMetadata.Model.GetType(),
requiredType);
throw new ArgumentException(message, "bindingContext");
}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ModelBindingContext.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ModelBindingContext.cs
index 98799eb181..8bb3505b60 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/ModelBindingContext.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ModelBindingContext.cs
@@ -17,7 +17,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private string _modelName;
private ModelStateDictionary _modelState;
- private ModelValidationNode _validationNode;
private Func _propertyFilter;
///
@@ -55,36 +54,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
///
public OperationBindingContext OperationBindingContext { get; set; }
- ///
- /// Gets or sets the model associated with this context.
- ///
- ///
- /// The property must be set to access this property.
- ///
- public object Model
- {
- get
- {
- EnsureModelMetadata();
- return ModelMetadata.Model;
- }
- set
- {
- IsModelSet = true;
-
- EnsureModelMetadata();
- ModelMetadata.Model = value;
- }
- }
-
- ///
- /// Gets or sets a value indicating whether or not the 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 null value.
- ///
- public bool IsModelSet { get; set; }
-
///
/// Gets or sets the metadata for the model associated with this context.
///
@@ -164,23 +133,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
set { _propertyFilter = value; }
}
- ///
- /// Gets or sets the instance used as a container for
- /// validation information.
- ///
- public ModelValidationNode ValidationNode
- {
- get
- {
- if (_validationNode == null)
- {
- _validationNode = new ModelValidationNode(ModelMetadata, ModelName);
- }
- return _validationNode;
- }
- set { _validationNode = value; }
- }
-
private void EnsureModelMetadata()
{
if (ModelMetadata == null)
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ModelBindingResult.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ModelBindingResult.cs
new file mode 100644
index 0000000000..afb14eb06d
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ModelBindingResult.cs
@@ -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
+{
+ ///
+ /// Contains the result of model binding.
+ ///
+ public class ModelBindingResult
+ {
+ ///
+ /// Creates a new .
+ ///
+ /// The model which was created by the .
+ /// The key using which was used to attempt binding the model.
+ /// A value that represents if the model has been set by the
+ /// .
+ public ModelBindingResult(object model, string key, bool isModelSet)
+ {
+ Model = model;
+ Key = key;
+ IsModelSet = isModelSet;
+ }
+
+ ///
+ /// Gets or sets the model associated with this context.
+ ///
+ public object Model { get; }
+
+ ///
+ /// 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.
+ ///
+ public string Key { get; }
+
+ ///
+ /// Gets or sets a value indicating whether or not the 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 null value.
+ ///
+ public bool IsModelSet { get; }
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ModelStateDictionary.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ModelStateDictionary.cs
index 90530144ab..9abd1515bf 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/ModelStateDictionary.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ModelStateDictionary.cs
@@ -132,10 +132,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
get { return _innerDictionary.Values; }
}
- ///
+ ///
+ /// Gets a value that indicates whether any model state values in this model state dictionary is invalid or not validated.
+ ///
public bool IsValid
{
- get { return ValidationState == ModelValidationState.Valid; }
+ get
+ {
+ return ValidationState == ModelValidationState.Valid || ValidationState == ModelValidationState.Skipped;
+ }
}
///
@@ -284,6 +289,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return GetValidity(entries);
}
+ ///
+ /// Returns for the .
+ ///
+ /// The key to look up model state errors for.
+ /// Returns if no entry is found for the specified
+ /// key, if an instance is found with one or more model
+ /// state errors; otherwise.
+ public ModelValidationState GetValidationState([NotNull] string key)
+ {
+ ModelState validationState;
+ if (TryGetValue(key, out validationState))
+ {
+ return validationState.ValidationState;
+ }
+
+ return ModelValidationState.Unvalidated;
+ }
+
///
/// Marks the for the entry with the specified
/// as .
@@ -300,6 +323,22 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
modelState.ValidationState = ModelValidationState.Valid;
}
+ ///
+ /// Marks the for the entry with the specified
+ /// as .
+ ///
+ /// The key of the to mark as skipped.
+ 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;
+ }
+
///
/// Copies the values from the specified into this instance, overwriting
/// existing values if keys are the same.
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ModelValidationState.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ModelValidationState.cs
index e8bd13a964..93cdfc021e 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/ModelValidationState.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ModelValidationState.cs
@@ -8,5 +8,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Unvalidated,
Invalid,
Valid,
+ Skipped
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Properties/Resources.Designer.cs
index ccb8a02331..b4bd26fbbb 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Properties/Resources.Designer.cs
@@ -42,6 +42,22 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return GetString("ArgumentCannotBeNullOrEmpty");
}
+ ///
+ /// A value is required.
+ ///
+ internal static string KeyValuePair_BothKeyAndValueMustBePresent
+ {
+ get { return GetString("KeyValuePair_BothKeyAndValueMustBePresent"); }
+ }
+
+ ///
+ /// A value is required.
+ ///
+ internal static string FormatKeyValuePair_BothKeyAndValueMustBePresent()
+ {
+ return GetString("KeyValuePair_BothKeyAndValueMustBePresent");
+ }
+
///
/// The property {0}.{1} could not be found.
///
@@ -330,6 +346,22 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return GetString("Validation_InvalidFieldCannotBeReset");
}
+ ///
+ /// A field previously marked invalid should not be marked skipped.
+ ///
+ internal static string Validation_InvalidFieldCannotBeReset_ToSkipped
+ {
+ get { return GetString("Validation_InvalidFieldCannotBeReset_ToSkipped"); }
+ }
+
+ ///
+ /// A field previously marked invalid should not be marked skipped.
+ ///
+ internal static string FormatValidation_InvalidFieldCannotBeReset_ToSkipped()
+ {
+ return GetString("Validation_InvalidFieldCannotBeReset_ToSkipped");
+ }
+
///
/// A value is required but was not present in the request.
///
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Resources.resx b/src/Microsoft.AspNet.Mvc.ModelBinding/Resources.resx
index fa74ff2293..d1104cd28e 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Resources.resx
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Resources.resx
@@ -123,6 +123,9 @@
Value cannot be null or empty.
+
+ A value is required.
+
The property {0}.{1} could not be found.
@@ -177,6 +180,9 @@
A field previously marked invalid should not be marked valid.
+
+ A field previously marked invalid should not be marked skipped.
+
A value is required but was not present in the request.
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/SimpleTypesExcludeFilter.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/SimpleTypesExcludeFilter.cs
index 85316acd77..9d86aa5d19 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/SimpleTypesExcludeFilter.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/SimpleTypesExcludeFilter.cs
@@ -40,7 +40,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
foreach (var actualType in actualTypes)
{
var underlyingType = Nullable.GetUnderlyingType(actualType) ?? actualType;
-
if (!IsSimpleType(underlyingType))
{
return false;
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DefaultBodyModelValidator.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DefaultBodyModelValidator.cs
deleted file mode 100644
index c42550e7d8..0000000000
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DefaultBodyModelValidator.cs
+++ /dev/null
@@ -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
-{
- ///
- /// Recursively validate an object.
- ///
- public class DefaultBodyModelValidator : IBodyModelValidator
- {
- ///
- public bool Validate(
- [NotNull] ModelValidationContext modelValidationContext,
- string keyPrefix)
- {
- var metadata = modelValidationContext.ModelMetadata;
- var validationContext = new ValidationContext()
- {
- ModelValidationContext = modelValidationContext,
- Visited = new HashSet