Fixes #2464 - Does not add extra skipped entries for model bound from services.
Also ensures that when a type is marked as skipped, any sub property which is model bound (and hence a modelstate un validated entry), is marked as skipped (otherwise it would cause the ModelState to be invalid). Also fixing a bug in model state dictionary FindKeyWithPrefix was not considering [0] & [0][0] as a valid prefix.
This commit is contained in:
parent
88ac4b94e4
commit
d0927bdc75
17
Mvc.sln
17
Mvc.sln
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 14
|
# Visual Studio 14
|
||||||
VisualStudioVersion = 14.0.22810.0
|
VisualStudioVersion = 14.0.22808.1
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
|
||||||
EndProject
|
EndProject
|
||||||
|
|
@ -166,6 +166,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.ApiExp
|
||||||
EndProject
|
EndProject
|
||||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.ApiExplorer.Test", "test\Microsoft.AspNet.Mvc.ApiExplorer.Test\Microsoft.AspNet.Mvc.ApiExplorer.Test.xproj", "{4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}"
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.ApiExplorer.Test", "test\Microsoft.AspNet.Mvc.ApiExplorer.Test\Microsoft.AspNet.Mvc.ApiExplorer.Test.xproj", "{4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Abstractions.Test", "test\Microsoft.AspNet.Mvc.Abstractions.Test\Microsoft.AspNet.Mvc.Abstractions.Test.xproj", "{DA000953-7532-4DF5-8DB9-8143DF98D999}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
@ -996,6 +998,18 @@ Global
|
||||||
{4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
{4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
{4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Release|x86.ActiveCfg = Release|Any CPU
|
{4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Release|x86.Build.0 = Release|Any CPU
|
{4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{DA000953-7532-4DF5-8DB9-8143DF98D999}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{DA000953-7532-4DF5-8DB9-8143DF98D999}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{DA000953-7532-4DF5-8DB9-8143DF98D999}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
|
{DA000953-7532-4DF5-8DB9-8143DF98D999}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||||
|
{DA000953-7532-4DF5-8DB9-8143DF98D999}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{DA000953-7532-4DF5-8DB9-8143DF98D999}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{DA000953-7532-4DF5-8DB9-8143DF98D999}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{DA000953-7532-4DF5-8DB9-8143DF98D999}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{DA000953-7532-4DF5-8DB9-8143DF98D999}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{DA000953-7532-4DF5-8DB9-8143DF98D999}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{DA000953-7532-4DF5-8DB9-8143DF98D999}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{DA000953-7532-4DF5-8DB9-8143DF98D999}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
@ -1077,5 +1091,6 @@ Global
|
||||||
{1154203C-7579-4525-906E-BC55268421C1} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
|
{1154203C-7579-4525-906E-BC55268421C1} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
|
||||||
{A2B72833-5D70-4C42-AE85-E0319926FB8A} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
|
{A2B72833-5D70-4C42-AE85-E0319926FB8A} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
|
||||||
{4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
|
{4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
|
||||||
|
{DA000953-7532-4DF5-8DB9-8143DF98D999} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
|
||||||
|
|
@ -281,7 +281,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
/// state errors; <see cref="ModelValidationState.Valid"/> otherwise.</returns>
|
/// state errors; <see cref="ModelValidationState.Valid"/> otherwise.</returns>
|
||||||
public ModelValidationState GetFieldValidationState([NotNull] string key)
|
public ModelValidationState GetFieldValidationState([NotNull] string key)
|
||||||
{
|
{
|
||||||
var entries = FindKeysWithPrefix(this, key);
|
var entries = FindKeysWithPrefix(key);
|
||||||
if (!entries.Any())
|
if (!entries.Any())
|
||||||
{
|
{
|
||||||
return ModelValidationState.Unvalidated;
|
return ModelValidationState.Unvalidated;
|
||||||
|
|
@ -378,7 +378,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
// If key is null or empty, clear all entries in the dictionary
|
// If key is null or empty, clear all entries in the dictionary
|
||||||
// else just clear the ones that have key as prefix
|
// else just clear the ones that have key as prefix
|
||||||
var entries = (string.IsNullOrEmpty(key)) ?
|
var entries = (string.IsNullOrEmpty(key)) ?
|
||||||
_innerDictionary : FindKeysWithPrefix(this, key);
|
_innerDictionary : FindKeysWithPrefix(key);
|
||||||
|
|
||||||
foreach (var entry in entries)
|
foreach (var entry in entries)
|
||||||
{
|
{
|
||||||
|
|
@ -502,17 +502,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
return GetEnumerator();
|
return GetEnumerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<KeyValuePair<string, TValue>> FindKeysWithPrefix<TValue>(
|
public IEnumerable<KeyValuePair<string, ModelState>> FindKeysWithPrefix([NotNull] string prefix)
|
||||||
[NotNull] IDictionary<string, TValue> dictionary,
|
|
||||||
[NotNull] string prefix)
|
|
||||||
{
|
{
|
||||||
TValue exactMatchValue;
|
ModelState exactMatchValue;
|
||||||
if (dictionary.TryGetValue(prefix, out exactMatchValue))
|
if (_innerDictionary.TryGetValue(prefix, out exactMatchValue))
|
||||||
{
|
{
|
||||||
yield return new KeyValuePair<string, TValue>(prefix, exactMatchValue);
|
yield return new KeyValuePair<string, ModelState>(prefix, exactMatchValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var entry in dictionary)
|
foreach (var entry in _innerDictionary)
|
||||||
{
|
{
|
||||||
var key = entry.Key;
|
var key = entry.Key;
|
||||||
|
|
||||||
|
|
@ -521,19 +519,30 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key.StartsWith("[", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
key = key.Substring(key.IndexOf('.') + 1);
|
|
||||||
if (string.Equals(prefix, key, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
yield return entry;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
if (!key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
continue;
|
|
||||||
|
if (key.StartsWith("[", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var subKey = key.Substring(key.IndexOf('.') + 1);
|
||||||
|
|
||||||
|
if (!subKey.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(prefix, subKey, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
yield return entry;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
key = subKey;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Everything is prefixed by the empty string
|
// Everything is prefixed by the empty string
|
||||||
|
|
|
||||||
|
|
@ -66,12 +66,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
||||||
var currentValidationNode = validationContext.ValidationNode;
|
var currentValidationNode = validationContext.ValidationNode;
|
||||||
if (currentValidationNode.SuppressValidation)
|
if (currentValidationNode.SuppressValidation)
|
||||||
{
|
{
|
||||||
// Short circuit if the node is marked to be suppressed
|
// Short circuit if the node is marked to be suppressed.
|
||||||
var validationState = modelState.GetFieldValidationState(modelKey);
|
// If there are any sub entries which were model bound, they need to be marked as skipped,
|
||||||
if (validationState == ModelValidationState.Unvalidated)
|
// Otherwise they will remain as unvalidated and the model state would be Invalid.
|
||||||
{
|
MarkChildNodesAsSkipped(modelKey, modelExplorer.Metadata, validationContext);
|
||||||
modelValidationContext.ModelState.MarkFieldSkipped(modelKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For validation purposes this model is valid.
|
// For validation purposes this model is valid.
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -103,7 +101,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
||||||
if (IsTypeExcludedFromValidation(_excludeFilters, modelType))
|
if (IsTypeExcludedFromValidation(_excludeFilters, modelType))
|
||||||
{
|
{
|
||||||
var result = ShallowValidate(modelKey, modelExplorer, validationContext, validators);
|
var result = ShallowValidate(modelKey, modelExplorer, validationContext, validators);
|
||||||
MarkPropertiesAsSkipped(modelKey, modelExplorer.Metadata, validationContext);
|
|
||||||
|
// If there are any sub entries which were model bound, they need to be marked as skipped,
|
||||||
|
// Otherwise they will remain as unvalidated and the model state would be Invalid.
|
||||||
|
MarkChildNodesAsSkipped(modelKey, modelExplorer.Metadata, validationContext);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,7 +128,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
||||||
return isValid;
|
return isValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MarkPropertiesAsSkipped(string currentModelKey, ModelMetadata metadata, ValidationContext validationContext)
|
private void MarkChildNodesAsSkipped(string currentModelKey, ModelMetadata metadata, ValidationContext validationContext)
|
||||||
{
|
{
|
||||||
var modelState = validationContext.ModelValidationContext.ModelState;
|
var modelState = validationContext.ModelValidationContext.ModelState;
|
||||||
var fieldValidationState = modelState.GetFieldValidationState(currentModelKey);
|
var fieldValidationState = modelState.GetFieldValidationState(currentModelKey);
|
||||||
|
|
@ -140,15 +141,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var childMetadata in metadata.Properties)
|
// At this point we just want to mark all sub-entries present in the model state as skipped.
|
||||||
|
var entries = modelState.FindKeysWithPrefix(currentModelKey);
|
||||||
|
foreach (var entry in entries)
|
||||||
{
|
{
|
||||||
var childKey = ModelNames.CreatePropertyModelName(currentModelKey, childMetadata.PropertyName);
|
entry.Value.ValidationState = ModelValidationState.Skipped;
|
||||||
var validationState = modelState.GetFieldValidationState(childKey);
|
|
||||||
|
|
||||||
if (validationState == ModelValidationState.Unvalidated)
|
|
||||||
{
|
|
||||||
validationContext.ModelValidationContext.ModelState.MarkFieldSkipped(childKey);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -293,6 +293,45 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
Assert.Equal(ModelValidationState.Valid, validationState);
|
Assert.Equal(ModelValidationState.Valid, validationState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("[0].foo.bar")]
|
||||||
|
[InlineData("[0].foo.bar[0]")]
|
||||||
|
public void GetFieldValidationState_IndexedPrefix_ReturnsInvalidIfKeyChildContainsErrors(string key)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var msd = new ModelStateDictionary();
|
||||||
|
msd.AddModelError(key, "error text");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var validationState = msd.GetFieldValidationState("[0].foo");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(ModelValidationState.Invalid, validationState);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("[0].foo.bar")]
|
||||||
|
[InlineData("[0].foo.bar[0]")]
|
||||||
|
public void GetFieldValidationState_IndexedPrefix_ReturnsValidIfModelStateDoesNotContainErrors(string key)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var validState = new ModelState
|
||||||
|
{
|
||||||
|
Value = new ValueProviderResult(null, null, null),
|
||||||
|
ValidationState = ModelValidationState.Valid
|
||||||
|
};
|
||||||
|
var msd = new ModelStateDictionary
|
||||||
|
{
|
||||||
|
{ key, validState }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var validationState = msd.GetFieldValidationState("[0].foo");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(ModelValidationState.Valid, validationState);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void IsValidPropertyReturnsFalseIfErrors()
|
public void IsValidPropertyReturnsFalseIfErrors()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -360,6 +360,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
||||||
.Validate(validationContext, topLevelValidationNode);
|
.Validate(validationContext, topLevelValidationNode);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
Assert.Equal(1, validationContext.ModelState.Count);
|
||||||
Assert.Contains("Street", validationContext.ModelState.Keys);
|
Assert.Contains("Street", validationContext.ModelState.Keys);
|
||||||
var streetState = validationContext.ModelState["Street"];
|
var streetState = validationContext.ModelState["Street"];
|
||||||
Assert.Equal(2, streetState.Errors.Count);
|
Assert.Equal(2, streetState.Errors.Count);
|
||||||
|
|
@ -427,7 +428,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
||||||
validator.Validate(validationContext, topLevelValidationNode);
|
validator.Validate(validationContext, topLevelValidationNode);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(new[] { "key1", "user.Password", "", "user.ConfirmPassword" },
|
Assert.Equal(new[] { "key1", "", "user.ConfirmPassword" },
|
||||||
validationContext.ModelState.Keys.ToArray());
|
validationContext.ModelState.Keys.ToArray());
|
||||||
var modelState = validationContext.ModelState["user.ConfirmPassword"];
|
var modelState = validationContext.ModelState["user.ConfirmPassword"];
|
||||||
Assert.Empty(modelState.Errors);
|
Assert.Empty(modelState.Errors);
|
||||||
|
|
@ -438,7 +439,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ForExcludedNonModelBoundType_Properties_NotMarkedAsSkiped()
|
public void ForExcludedNonModelBoundTypes_NoEntryInModelState()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var user = new User()
|
var user = new User()
|
||||||
|
|
@ -468,11 +469,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
||||||
validator.Validate(validationContext, topLevelValidationNode);
|
validator.Validate(validationContext, topLevelValidationNode);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(validationContext.ModelState.ContainsKey("user.Password"));
|
Assert.True(validationContext.ModelState.IsValid);
|
||||||
Assert.False(validationContext.ModelState.ContainsKey("user.ConfirmPassword"));
|
Assert.Empty(validationContext.ModelState);
|
||||||
var modelState = validationContext.ModelState["user"];
|
|
||||||
Assert.Empty(modelState.Errors);
|
|
||||||
Assert.Equal(modelState.ValidationState, ModelValidationState.Valid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -511,13 +509,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
||||||
validator.Validate(validationContext, topLevelValidationNode);
|
validator.Validate(validationContext, topLevelValidationNode);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var modelState = validationContext.ModelState["user.Password"];
|
var entry = Assert.Single(validationContext.ModelState);
|
||||||
Assert.Empty(modelState.Errors);
|
Assert.Equal("user.Password", entry.Key);
|
||||||
Assert.Equal(modelState.ValidationState, ModelValidationState.Skipped);
|
Assert.Empty(entry.Value.Errors);
|
||||||
|
Assert.Equal(entry.Value.ValidationState, ModelValidationState.Skipped);
|
||||||
|
}
|
||||||
|
|
||||||
modelState = validationContext.ModelState["user.ConfirmPassword"];
|
private class Person2
|
||||||
Assert.Empty(modelState.Errors);
|
{
|
||||||
Assert.Equal(modelState.ValidationState, ModelValidationState.Skipped);
|
public Address Address { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -525,22 +525,28 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var testValidationContext = GetModelValidationContext(
|
var testValidationContext = GetModelValidationContext(
|
||||||
new TestServiceProvider(),
|
new Person2()
|
||||||
typeof(TestServiceProvider));
|
{
|
||||||
|
Address = new Address { Street = "GreaterThan5Characters" }
|
||||||
|
},
|
||||||
|
typeof(Person2));
|
||||||
var validationContext = testValidationContext.ModelValidationContext;
|
var validationContext = testValidationContext.ModelValidationContext;
|
||||||
|
|
||||||
|
// Create an entry like a model binder would.
|
||||||
|
validationContext.ModelState.Add("person.Address", new ModelState());
|
||||||
|
|
||||||
var validator = new DefaultObjectValidator(
|
var validator = new DefaultObjectValidator(
|
||||||
testValidationContext.ExcludeFilters,
|
testValidationContext.ExcludeFilters,
|
||||||
testValidationContext.ModelMetadataProvider);
|
testValidationContext.ModelMetadataProvider);
|
||||||
var modelExplorer = testValidationContext.ModelValidationContext.ModelExplorer;
|
var modelExplorer = testValidationContext.ModelValidationContext.ModelExplorer;
|
||||||
var topLevelValidationNode = new ModelValidationNode(
|
var topLevelValidationNode = new ModelValidationNode(
|
||||||
"serviceProvider",
|
"person",
|
||||||
modelExplorer.Metadata,
|
modelExplorer.Metadata,
|
||||||
modelExplorer.Model);
|
modelExplorer.Model);
|
||||||
|
|
||||||
var propertyExplorer = modelExplorer.GetExplorerForProperty("TestService");
|
var propertyExplorer = modelExplorer.GetExplorerForProperty("Address");
|
||||||
var childNode = new ModelValidationNode(
|
var childNode = new ModelValidationNode(
|
||||||
"serviceProvider.TestService",
|
"person.Address",
|
||||||
propertyExplorer.Metadata,
|
propertyExplorer.Metadata,
|
||||||
propertyExplorer.Model)
|
propertyExplorer.Model)
|
||||||
{
|
{
|
||||||
|
|
@ -554,8 +560,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(validationContext.ModelState.IsValid);
|
Assert.True(validationContext.ModelState.IsValid);
|
||||||
var modelState = validationContext.ModelState["serviceProvider.TestService"];
|
Assert.Equal(1, validationContext.ModelState.Count);
|
||||||
Assert.Empty(modelState.Errors);
|
var modelState = validationContext.ModelState["person.Address"];
|
||||||
Assert.Equal(modelState.ValidationState, ModelValidationState.Skipped);
|
Assert.Equal(modelState.ValidationState, ModelValidationState.Skipped);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -603,7 +609,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(validationContext.ModelState.IsValid);
|
Assert.True(validationContext.ModelState.IsValid);
|
||||||
var modelState = validationContext.ModelState["items"];
|
Assert.Equal(3, validationContext.ModelState.Count);
|
||||||
|
var modelState = validationContext.ModelState["items[0]"];
|
||||||
|
Assert.Equal(modelState.ValidationState, ModelValidationState.Valid);
|
||||||
|
|
||||||
|
modelState = validationContext.ModelState["items[1]"];
|
||||||
|
Assert.Equal(modelState.ValidationState, ModelValidationState.Valid);
|
||||||
|
|
||||||
|
modelState = validationContext.ModelState["items[2]"];
|
||||||
Assert.Equal(modelState.ValidationState, ModelValidationState.Valid);
|
Assert.Equal(modelState.ValidationState, ModelValidationState.Valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -653,16 +666,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(validationContext.ModelState.IsValid);
|
Assert.True(validationContext.ModelState.IsValid);
|
||||||
var modelState = validationContext.ModelState["items"];
|
Assert.Equal(4, validationContext.ModelState.Count);
|
||||||
Assert.Equal(modelState.ValidationState, ModelValidationState.Valid);
|
var modelState = validationContext.ModelState["items[0].Key"];
|
||||||
modelState = validationContext.ModelState["items[0].Key"];
|
Assert.Equal(ModelValidationState.Skipped, modelState.ValidationState);
|
||||||
Assert.Equal(modelState.ValidationState, ModelValidationState.Skipped);
|
|
||||||
modelState = validationContext.ModelState["items[0].Value"];
|
modelState = validationContext.ModelState["items[0].Value"];
|
||||||
Assert.Equal(modelState.ValidationState, ModelValidationState.Skipped);
|
Assert.Equal(ModelValidationState.Skipped, modelState.ValidationState);
|
||||||
modelState = validationContext.ModelState["items[1].Key"];
|
modelState = validationContext.ModelState["items[1].Key"];
|
||||||
Assert.Equal(modelState.ValidationState, ModelValidationState.Skipped);
|
Assert.Equal(ModelValidationState.Skipped, modelState.ValidationState);
|
||||||
modelState = validationContext.ModelState["items[1].Value"];
|
modelState = validationContext.ModelState["items[1].Value"];
|
||||||
Assert.Equal(modelState.ValidationState, ModelValidationState.Skipped);
|
Assert.Equal(ModelValidationState.Skipped, modelState.ValidationState);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -690,8 +702,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(validationContext.ModelState.IsValid);
|
Assert.True(validationContext.ModelState.IsValid);
|
||||||
var key = Assert.Single(validationContext.ModelState.Keys);
|
|
||||||
Assert.Equal("person", key);
|
// Since Person is not IValidatable and we do not look at its properties, the state is empty.
|
||||||
|
Assert.Empty(validationContext.ModelState.Keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -171,7 +171,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CheckIfExcludedField_IsValidatedForNonBodyBoundModels()
|
public async Task CheckIfExcludedField_IsNotValidatedForNonBodyBoundModels()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
|
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
|
||||||
|
|
@ -185,7 +185,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
Assert.Equal("The Name field is required.", await response.Content.ReadAsStringAsync());
|
Assert.Equal("xyz", await response.Content.ReadAsStringAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ErrorCollection
|
private class ErrorCollection
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,9 @@ using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Builder;
|
using Microsoft.AspNet.Builder;
|
||||||
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
using Microsoft.Framework.DependencyInjection;
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
using ModelBindingWebSite.Controllers;
|
||||||
using ModelBindingWebSite.Models;
|
using ModelBindingWebSite.Models;
|
||||||
using ModelBindingWebSite.ViewModels;
|
using ModelBindingWebSite.ViewModels;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
@ -33,34 +35,12 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
var client = server.CreateClient();
|
var client = server.CreateClient();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await client.GetAsync("http://localhost/Validation/DoNotValidateParameter");
|
var response = await client.GetStringAsync("http://localhost/Validation/DoNotValidateParameter");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var stringValue = await response.Content.ReadAsStringAsync();
|
var modelState = JsonConvert.DeserializeObject<ModelStateDictionary>(response);
|
||||||
var isModelStateValid = JsonConvert.DeserializeObject<bool>(stringValue);
|
Assert.Empty(modelState);
|
||||||
Assert.True(isModelStateValid);
|
Assert.True(modelState.IsValid);
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task TypeBasedExclusion_ForBodyAndNonBodyBoundModels()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
|
|
||||||
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]
|
[Fact]
|
||||||
|
|
@ -76,8 +56,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var stringValue = await response.Content.ReadAsStringAsync();
|
var stringValue = await response.Content.ReadAsStringAsync();
|
||||||
var isModelStateValid = JsonConvert.DeserializeObject<bool>(stringValue);
|
var json = JsonConvert.DeserializeObject<ModelStateDictionary>(stringValue);
|
||||||
Assert.True(isModelStateValid);
|
Assert.True(json.IsValid);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
||||||
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(
|
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(
|
||||||
request =>
|
request =>
|
||||||
{
|
{
|
||||||
request.Body = new MemoryStream(Encoding.UTF8.GetBytes("{ \"Id\":1234 }"));
|
request.Body = new MemoryStream(Encoding.UTF8.GetBytes(string.Empty));
|
||||||
request.ContentType = "application/json";
|
request.ContentType = "application/json";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -67,7 +67,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task FromBodyOnActionParameter_EmptyBody_AddsModelStateError()
|
public async Task FromBodyOnActionParameter_EmptyBody_BindsToNullValue()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||||
|
|
@ -85,7 +85,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
||||||
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(
|
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(
|
||||||
request =>
|
request =>
|
||||||
{
|
{
|
||||||
request.Body = new MemoryStream(Encoding.UTF8.GetBytes("{ \"Id\":1234 }"));
|
request.Body = new MemoryStream(Encoding.UTF8.GetBytes(string.Empty));
|
||||||
request.ContentType = "application/json";
|
request.ContentType = "application/json";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -98,13 +98,9 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
||||||
// Assert
|
// Assert
|
||||||
Assert.NotNull(modelBindingResult);
|
Assert.NotNull(modelBindingResult);
|
||||||
Assert.True(modelBindingResult.IsModelSet);
|
Assert.True(modelBindingResult.IsModelSet);
|
||||||
var boundPerson = Assert.IsType<Person>(modelBindingResult.Model);
|
Assert.Null(modelBindingResult.Model);
|
||||||
Assert.NotNull(boundPerson);
|
Assert.Empty(modelState.Keys);
|
||||||
var key = Assert.Single(modelState.Keys);
|
Assert.True(modelState.IsValid);
|
||||||
Assert.Equal("Address", key);
|
|
||||||
Assert.False(modelState.IsValid);
|
|
||||||
var error = Assert.Single(modelState[key].Errors);
|
|
||||||
Assert.Equal("The Address field is required.",error.ErrorMessage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Person4
|
private class Person4
|
||||||
|
|
|
||||||
|
|
@ -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.ComponentModel.DataAnnotations;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -597,5 +598,60 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
||||||
Assert.Equal("Street2", entry.Value.AttemptedValue);
|
Assert.Equal("Street2", entry.Value.AttemptedValue);
|
||||||
Assert.Equal("Street2", entry.Value.RawValue);
|
Assert.Equal("Street2", entry.Value.RawValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class Person5
|
||||||
|
{
|
||||||
|
public IList<Address5> Addresses { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Address5
|
||||||
|
{
|
||||||
|
public int Zip { get; set; }
|
||||||
|
|
||||||
|
[StringLength(3)]
|
||||||
|
public string Street { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CollectionModelBinder_UsesCustomIndexes_AddsErrorsWithCorrectKeys()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||||
|
var parameter = new ParameterDescriptor()
|
||||||
|
{
|
||||||
|
Name = "parameter",
|
||||||
|
ParameterType = typeof(Person5)
|
||||||
|
};
|
||||||
|
|
||||||
|
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
|
||||||
|
{
|
||||||
|
var formCollection = new FormCollection(new Dictionary<string, string[]>()
|
||||||
|
{
|
||||||
|
{ "Addresses.index", new [] { "Key1" } },
|
||||||
|
{ "Addresses[Key1].Street", new [] { "Street1" } },
|
||||||
|
});
|
||||||
|
|
||||||
|
request.Form = formCollection;
|
||||||
|
request.ContentType = "application/x-www-form-urlencoded";
|
||||||
|
});
|
||||||
|
|
||||||
|
var modelState = new ModelStateDictionary();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(modelBindingResult);
|
||||||
|
Assert.True(modelBindingResult.IsModelSet);
|
||||||
|
Assert.IsType<Person5>(modelBindingResult.Model);
|
||||||
|
|
||||||
|
Assert.Equal(1, modelState.Count);
|
||||||
|
Assert.Equal(1, modelState.ErrorCount);
|
||||||
|
Assert.False(modelState.IsValid);
|
||||||
|
|
||||||
|
var entry = Assert.Single(modelState, kvp => kvp.Key == "Addresses[Key1].Street").Value;
|
||||||
|
var error = Assert.Single(entry.Errors);
|
||||||
|
Assert.Equal("The field Street must be a string with a maximum length of 3.", error.ErrorMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -49,21 +49,21 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DefaultControllerActionArgumentBinder GetArgumentBinder()
|
public static DefaultControllerActionArgumentBinder GetArgumentBinder(MvcOptions options = null)
|
||||||
{
|
{
|
||||||
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
return new DefaultControllerActionArgumentBinder(
|
return new DefaultControllerActionArgumentBinder(
|
||||||
metadataProvider,
|
metadataProvider,
|
||||||
GetObjectValidator());
|
GetObjectValidator(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IObjectModelValidator GetObjectValidator()
|
public static IObjectModelValidator GetObjectValidator(MvcOptions options = null)
|
||||||
{
|
{
|
||||||
var options = new TestMvcOptions();
|
options = options ?? new TestMvcOptions().Options;
|
||||||
options.Options.MaxModelValidationErrors = 5;
|
options.MaxModelValidationErrors = 5;
|
||||||
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
return new DefaultObjectValidator(
|
return new DefaultObjectValidator(
|
||||||
options.Options.ValidationExcludeFilters,
|
options.ValidationExcludeFilters,
|
||||||
metadataProvider);
|
metadataProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -273,7 +273,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
||||||
public IScopedInstance<ActionBindingContext> BindingContext { get; set; }
|
public IScopedInstance<ActionBindingContext> BindingContext { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(Skip = "FromServices should not have an entry in model state #2464.")]
|
[Fact]
|
||||||
public async Task MutableObjectModelBinder_BindsNestedPOCO_WithServicesModelBinder_WithPrefix_Success()
|
public async Task MutableObjectModelBinder_BindsNestedPOCO_WithServicesModelBinder_WithPrefix_Success()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -313,7 +313,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
||||||
Assert.Equal("bill", entry.Value.RawValue);
|
Assert.Equal("bill", entry.Value.RawValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(Skip = "FromServices should not have an entry in model state #2464.")]
|
[Fact]
|
||||||
public async Task MutableObjectModelBinder_BindsNestedPOCO_WithServicesModelBinder_WithEmptyPrefix_Success()
|
public async Task MutableObjectModelBinder_BindsNestedPOCO_WithServicesModelBinder_WithEmptyPrefix_Success()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -355,7 +355,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
||||||
|
|
||||||
// We don't provide enough data in this test for the 'Person' model to be created. So even though there is
|
// We don't provide enough data in this test for the 'Person' model to be created. So even though there is
|
||||||
// a [FromServices], it won't be used.
|
// a [FromServices], it won't be used.
|
||||||
[Fact(Skip = "FromServices should not have an entry in model state #2464.")]
|
[Fact]
|
||||||
public async Task MutableObjectModelBinder_BindsNestedPOCO_WithServicesModelBinder_WithPrefix_PartialData()
|
public async Task MutableObjectModelBinder_BindsNestedPOCO_WithServicesModelBinder_WithPrefix_PartialData()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -427,7 +427,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
||||||
var model = Assert.IsType<Order2>(modelBindingResult.Model);
|
var model = Assert.IsType<Order2>(modelBindingResult.Model);
|
||||||
Assert.Null(model.Customer);
|
Assert.Null(model.Customer);
|
||||||
|
|
||||||
Assert.Equal(0, modelState.Count); // Fails due to #2464
|
Assert.Equal(0, modelState.Count);
|
||||||
Assert.Equal(0, modelState.ErrorCount);
|
Assert.Equal(0, modelState.ErrorCount);
|
||||||
Assert.True(modelState.IsValid);
|
Assert.True(modelState.IsValid);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,15 +60,10 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
||||||
|
|
||||||
// ModelState
|
// ModelState
|
||||||
Assert.True(modelState.IsValid);
|
Assert.True(modelState.IsValid);
|
||||||
|
Assert.Empty(modelState.Keys);
|
||||||
Assert.Equal(1, modelState.Keys.Count);
|
|
||||||
var key = Assert.Single(modelState.Keys, k => k == "CustomParameter.Address.OutputFormatter");
|
|
||||||
Assert.Equal(ModelValidationState.Skipped, modelState[key].ValidationState);
|
|
||||||
Assert.Null(modelState[key].Value);
|
|
||||||
Assert.Empty(modelState[key].Errors);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(Skip = "Should be no entry for model bound using services. #2464")]
|
[Fact]
|
||||||
public async Task BindPropertyFromService_WithData_WithEmptyPrefix_GetsBound()
|
public async Task BindPropertyFromService_WithData_WithEmptyPrefix_GetsBound()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -99,13 +94,12 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
||||||
|
|
||||||
// ModelState
|
// ModelState
|
||||||
Assert.True(modelState.IsValid);
|
Assert.True(modelState.IsValid);
|
||||||
Assert.Equal(1, modelState.Keys.Count);
|
|
||||||
var key = Assert.Single(modelState.Keys, k => k == "Address");
|
// For non user bound models there should be no entry in model state.
|
||||||
Assert.Null(modelState[key].Value); // For non user bound models there should be no value.
|
Assert.Empty(modelState);
|
||||||
Assert.Empty(modelState[key].Errors);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(Skip = "#2464 ModelState should not have entry for non request bound models.")]
|
[Fact]
|
||||||
public async Task BindParameterFromService_WithData_GetsBound()
|
public async Task BindParameterFromService_WithData_GetsBound()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
|
@ -1005,6 +1007,82 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
||||||
Assert.False(modelState.IsValid);
|
Assert.False(modelState.IsValid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class Order11
|
||||||
|
{
|
||||||
|
public IEnumerable<Address> ShippingAddresses { get; set; }
|
||||||
|
|
||||||
|
public Address HomeAddress { get; set; }
|
||||||
|
|
||||||
|
[FromBody]
|
||||||
|
public Address OfficeAddress { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Address
|
||||||
|
{
|
||||||
|
public int Street { get; set; }
|
||||||
|
public string State { get; set; }
|
||||||
|
|
||||||
|
[Range(10000, 99999)]
|
||||||
|
public int Zip { get; set; }
|
||||||
|
|
||||||
|
public Country Country { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Country
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
[Fact]
|
||||||
|
public async Task TypeBasedExclusion_ForBodyAndNonBodyBoundModels()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var parameter = new ParameterDescriptor()
|
||||||
|
{
|
||||||
|
Name = "parameter",
|
||||||
|
ParameterType = typeof(Order11)
|
||||||
|
};
|
||||||
|
|
||||||
|
MvcOptions testOptions = null;
|
||||||
|
var input = "{\"OfficeAddress.Zip\":\"45\"}";
|
||||||
|
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
|
||||||
|
{
|
||||||
|
request.QueryString =
|
||||||
|
new QueryString("?HomeAddress.Country.Name=US&ShippingAddresses[0].Zip=45&HomeAddress.Zip=46");
|
||||||
|
request.Body = new MemoryStream(Encoding.UTF8.GetBytes(input));
|
||||||
|
request.ContentType = "application/json";
|
||||||
|
},
|
||||||
|
options => {
|
||||||
|
|
||||||
|
options.ValidationExcludeFilters.Add(typeof(Address));
|
||||||
|
testOptions = options;
|
||||||
|
});
|
||||||
|
|
||||||
|
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(testOptions);
|
||||||
|
var modelState = new ModelStateDictionary();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
|
||||||
|
|
||||||
|
Assert.Equal(3, modelState.Count);
|
||||||
|
Assert.Equal(0, modelState.ErrorCount);
|
||||||
|
Assert.True(modelState.IsValid);
|
||||||
|
|
||||||
|
var entry = Assert.Single(modelState, e => e.Key == "HomeAddress.Country.Name").Value;
|
||||||
|
Assert.Equal("US", entry.Value.AttemptedValue);
|
||||||
|
Assert.Equal("US", entry.Value.RawValue);
|
||||||
|
Assert.Equal(ModelValidationState.Skipped, entry.ValidationState);
|
||||||
|
|
||||||
|
entry = Assert.Single(modelState, e => e.Key == "ShippingAddresses[0].Zip").Value;
|
||||||
|
Assert.Equal("45", entry.Value.AttemptedValue);
|
||||||
|
Assert.Equal("45", entry.Value.RawValue);
|
||||||
|
Assert.Equal(ModelValidationState.Skipped, entry.ValidationState);
|
||||||
|
|
||||||
|
entry = Assert.Single(modelState, e => e.Key == "HomeAddress.Zip").Value;
|
||||||
|
Assert.Equal("46", entry.Value.AttemptedValue);
|
||||||
|
Assert.Equal("46", entry.Value.RawValue);
|
||||||
|
Assert.Equal(ModelValidationState.Skipped, entry.ValidationState);
|
||||||
|
}
|
||||||
|
|
||||||
private static void AssertRequiredError(string key, ModelError error)
|
private static void AssertRequiredError(string key, ModelError error)
|
||||||
{
|
{
|
||||||
Assert.Equal(string.Format("The {0} field is required.", key), error.ErrorMessage);
|
Assert.Equal(string.Format("The {0} field is required.", key), error.ErrorMessage);
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,6 @@ namespace FormatterWebSite
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public string GetDeveloperAlias(Developer developer)
|
public string GetDeveloperAlias(Developer developer)
|
||||||
{
|
{
|
||||||
// Since validation exclusion is currently only effective in case of body bound models.
|
|
||||||
if (ModelState.IsValid)
|
if (ModelState.IsValid)
|
||||||
{
|
{
|
||||||
return developer.Alias;
|
return developer.Alias;
|
||||||
|
|
|
||||||
|
|
@ -14,22 +14,60 @@ namespace ModelBindingWebSite.Controllers
|
||||||
[FromServices]
|
[FromServices]
|
||||||
public ITestService ControllerService { get; set; }
|
public ITestService ControllerService { get; set; }
|
||||||
|
|
||||||
public bool SkipValidation(Resident resident)
|
public object AvoidRecursive(SelfishPerson selfishPerson)
|
||||||
{
|
{
|
||||||
return ModelState.IsValid;
|
return new SerializableModelStateDictionary(ModelState);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AvoidRecursive(SelfishPerson selfishPerson)
|
public object DoNotValidateParameter([FromServices] ITestService service)
|
||||||
{
|
{
|
||||||
return ModelState.IsValid;
|
return ModelState;
|
||||||
}
|
|
||||||
|
|
||||||
public bool DoNotValidateParameter([FromServices] ITestService service)
|
|
||||||
{
|
|
||||||
return ModelState.IsValid;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class SerializableModelStateDictionary : Dictionary<string, Entry>
|
||||||
|
{
|
||||||
|
public bool IsValid { get; set; }
|
||||||
|
|
||||||
|
public int ErrorCount { get; set; }
|
||||||
|
|
||||||
|
public SerializableModelStateDictionary(ModelStateDictionary modelState)
|
||||||
|
{
|
||||||
|
var errorCount = 0;
|
||||||
|
foreach (var keyModelStatePair in modelState)
|
||||||
|
{
|
||||||
|
var key = keyModelStatePair.Key;
|
||||||
|
var value = keyModelStatePair.Value;
|
||||||
|
errorCount += value.Errors.Count;
|
||||||
|
var entry = new Entry()
|
||||||
|
{
|
||||||
|
Errors = value.Errors,
|
||||||
|
RawValue = value.Value.RawValue,
|
||||||
|
AttemptedValue = value.Value.AttemptedValue,
|
||||||
|
ValidationState = value.ValidationState
|
||||||
|
};
|
||||||
|
|
||||||
|
Add(key, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
IsValid = modelState.IsValid;
|
||||||
|
ErrorCount = errorCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Entry
|
||||||
|
{
|
||||||
|
public ModelValidationState ValidationState { get; set; }
|
||||||
|
|
||||||
|
public ModelErrorCollection Errors { get; set; }
|
||||||
|
|
||||||
|
public object RawValue { get; set; }
|
||||||
|
|
||||||
|
public string AttemptedValue { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public class SelfishPerson
|
public class SelfishPerson
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue