Clear ModelState errors of model before TryValidateModel or TryUpdateModel
This commit is contained in:
parent
e9f56055eb
commit
7b18d1d3f1
|
|
@ -910,7 +910,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
public virtual Task<bool> TryUpdateModelAsync<TModel>([NotNull] TModel model)
|
||||
where TModel : class
|
||||
{
|
||||
return TryUpdateModelAsync(model, prefix: null);
|
||||
return TryUpdateModelAsync(model, prefix: string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -1240,6 +1240,14 @@ namespace Microsoft.AspNet.Mvc
|
|||
var modelExplorer = MetadataProvider.GetModelExplorerForType(model.GetType(), model);
|
||||
|
||||
var modelName = prefix ?? string.Empty;
|
||||
|
||||
// Clear ModelStateDictionary entries for the model so that it will be re-validated.
|
||||
ModelBindingHelper.ClearValidationStateForModel(
|
||||
model.GetType(),
|
||||
ModelState,
|
||||
MetadataProvider,
|
||||
modelName);
|
||||
|
||||
var validationContext = new ModelValidationContext(
|
||||
modelName,
|
||||
BindingContext.ValidatorProvider,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
// 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.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
|
@ -254,6 +257,9 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
var modelMetadata = metadataProvider.GetMetadataForType(modelType);
|
||||
|
||||
// Clear ModelStateDictionary entries for the model so that it will be re-validated.
|
||||
ClearValidationStateForModel(modelType, modelState, metadataProvider, prefix);
|
||||
|
||||
var operationBindingContext = new OperationBindingContext
|
||||
{
|
||||
ModelBinder = modelBinder,
|
||||
|
|
@ -381,5 +387,62 @@ namespace Microsoft.AspNet.Mvc
|
|||
return prefix + "." + propertyName;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears <see cref="ModelStateDictionary"/> entries for <see cref="ModelMetadata"/>.
|
||||
/// </summary>
|
||||
/// <param name="modelMetadata">The <see cref="ModelMetadata"/>.</param>
|
||||
/// <param name="modelKey">The entry to clear. </param>
|
||||
/// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
|
||||
public static void ClearValidationStateForModel(
|
||||
[NotNull] Type modelType,
|
||||
[NotNull] ModelStateDictionary modelstate,
|
||||
[NotNull] IModelMetadataProvider metadataProvider,
|
||||
string modelKey)
|
||||
{
|
||||
// If modelkey is empty, we need to iterate through properties (obtained from ModelMetadata) and
|
||||
// clear validation state for all entries in ModelStateDictionary that start with each property name.
|
||||
// If modelkey is non-empty, clear validation state for all entries in ModelStateDictionary
|
||||
// that start with modelKey
|
||||
if (string.IsNullOrEmpty(modelKey))
|
||||
{
|
||||
var modelMetadata = metadataProvider.GetMetadataForType(modelType);
|
||||
if (modelMetadata.IsCollectionType)
|
||||
{
|
||||
var elementType = GetElementType(modelMetadata.ModelType);
|
||||
modelMetadata = metadataProvider.GetMetadataForType(elementType);
|
||||
}
|
||||
|
||||
foreach (var property in modelMetadata.Properties)
|
||||
{
|
||||
var childKey = property.BinderModelName ?? property.PropertyName;
|
||||
modelstate.ClearValidationState(childKey);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
modelstate.ClearValidationState(modelKey);
|
||||
}
|
||||
}
|
||||
|
||||
private static Type GetElementType(Type type)
|
||||
{
|
||||
Debug.Assert(typeof(IEnumerable).IsAssignableFrom(type));
|
||||
if (type.IsArray)
|
||||
{
|
||||
return type.GetElementType();
|
||||
}
|
||||
|
||||
foreach (var implementedInterface in type.GetInterfaces())
|
||||
{
|
||||
if (implementedInterface.IsGenericType() &&
|
||||
implementedInterface.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
||||
{
|
||||
return implementedInterface.GetGenericArguments()[0];
|
||||
}
|
||||
}
|
||||
|
||||
return typeof(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Framework.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ModelBinding.Internal
|
||||
|
|
@ -28,6 +29,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Internal
|
|||
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))
|
||||
{
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -369,6 +369,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
GetModelStateForKey(key).Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears <see cref="ModelStateDictionary"/> entries that match the key that is passed as parameter.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of <see cref="ModelStateDictionary"/> to clear.</param>
|
||||
public void ClearValidationState(string key)
|
||||
{
|
||||
// If key is null or empty, clear all entries in the dictionary
|
||||
// else just clear the ones that have key as prefix
|
||||
var entries = (string.IsNullOrEmpty(key)) ?
|
||||
_innerDictionary : DictionaryHelper.FindKeysWithPrefix(this, key);
|
||||
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
entry.Value.Errors.Clear();
|
||||
entry.Value.ValidationState = ModelValidationState.Unvalidated;
|
||||
}
|
||||
}
|
||||
|
||||
private ModelState GetModelStateForKey([NotNull] string key)
|
||||
{
|
||||
ModelState modelState;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System;
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -667,6 +668,126 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
|||
Assert.Equal(expectedMessage, exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(null)]
|
||||
public void ClearValidationStateForModel_EmtpyModelKey(string modelKey)
|
||||
{
|
||||
// Arrange
|
||||
var metadataProvider = new EmptyModelMetadataProvider();
|
||||
var dictionary = new ModelStateDictionary();
|
||||
dictionary["Name"] = new ModelState { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("Name", "MyProperty invalid.");
|
||||
dictionary["Id"] = new ModelState { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("Id", "Id invalid.");
|
||||
dictionary.AddModelError("Id", "Id is required.");
|
||||
dictionary["Category"] = new ModelState { ValidationState = ModelValidationState.Valid };
|
||||
|
||||
// Act
|
||||
ModelBindingHelper.ClearValidationStateForModel(
|
||||
typeof(Product),
|
||||
dictionary,
|
||||
metadataProvider,
|
||||
modelKey);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, dictionary["Name"].Errors.Count);
|
||||
Assert.Equal(ModelValidationState.Unvalidated, dictionary["Name"].ValidationState);
|
||||
Assert.Equal(0, dictionary["Id"].Errors.Count);
|
||||
Assert.Equal(ModelValidationState.Unvalidated, dictionary["Id"].ValidationState);
|
||||
Assert.Equal(0, dictionary["Category"].Errors.Count);
|
||||
Assert.Equal(ModelValidationState.Unvalidated, dictionary["Category"].ValidationState);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(null)]
|
||||
public void ClearValidationStateForCollectionsModel_EmtpyModelKey(string modelKey)
|
||||
{
|
||||
// Arrange
|
||||
var metadataProvider = new EmptyModelMetadataProvider();
|
||||
var dictionary = new ModelStateDictionary();
|
||||
dictionary["[0].Name"] = new ModelState { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("[0].Name", "Name invalid.");
|
||||
dictionary["[0].Id"] = new ModelState { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("[0].Id", "Id invalid.");
|
||||
dictionary.AddModelError("[0].Id", "Id required.");
|
||||
dictionary["[0].Category"] = new ModelState { ValidationState = ModelValidationState.Valid };
|
||||
|
||||
dictionary["[1].Name"] = new ModelState { ValidationState = ModelValidationState.Valid };
|
||||
dictionary["[1].Id"] = new ModelState { ValidationState = ModelValidationState.Valid };
|
||||
dictionary["[1].Category"] = new ModelState { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("[1].Category", "Category invalid.");
|
||||
|
||||
// Act
|
||||
ModelBindingHelper.ClearValidationStateForModel(
|
||||
typeof(List<Product>),
|
||||
dictionary,
|
||||
metadataProvider,
|
||||
modelKey);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, dictionary["[0].Name"].Errors.Count);
|
||||
Assert.Equal(ModelValidationState.Unvalidated, dictionary["[0].Name"].ValidationState);
|
||||
Assert.Equal(0, dictionary["[0].Id"].Errors.Count);
|
||||
Assert.Equal(ModelValidationState.Unvalidated, dictionary["[0].Id"].ValidationState);
|
||||
Assert.Equal(0, dictionary["[0].Category"].Errors.Count);
|
||||
Assert.Equal(ModelValidationState.Unvalidated, dictionary["[0].Category"].ValidationState);
|
||||
Assert.Equal(0, dictionary["[1].Name"].Errors.Count);
|
||||
Assert.Equal(ModelValidationState.Unvalidated, dictionary["[1].Name"].ValidationState);
|
||||
Assert.Equal(0, dictionary["[1].Id"].Errors.Count);
|
||||
Assert.Equal(ModelValidationState.Unvalidated, dictionary["[1].Id"].ValidationState);
|
||||
Assert.Equal(0, dictionary["[1].Category"].Errors.Count);
|
||||
Assert.Equal(ModelValidationState.Unvalidated, dictionary["[1].Category"].ValidationState);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("product")]
|
||||
[InlineData("product.Name")]
|
||||
[InlineData("product.Order[0].Name")]
|
||||
[InlineData("product.Order[0].Address.Street")]
|
||||
[InlineData("product.Category.Name")]
|
||||
[InlineData("product.Order")]
|
||||
public void ClearValidationStateForModel_NonEmtpyModelKey(string prefix)
|
||||
{
|
||||
// Arrange
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
|
||||
var dictionary = new ModelStateDictionary();
|
||||
dictionary["product.Name"] = new ModelState { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("product.Name", "Name invalid.");
|
||||
dictionary["product.Id"] = new ModelState { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("product.Id", "Id invalid.");
|
||||
dictionary.AddModelError("product.Id", "Id required.");
|
||||
dictionary["product.Category"] = new ModelState { ValidationState = ModelValidationState.Valid };
|
||||
dictionary["product.Category.Name"] = new ModelState { ValidationState = ModelValidationState.Valid };
|
||||
dictionary["product.Order[0].Name"] = new ModelState { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("product.Order[0].Name", "Order name invalid.");
|
||||
dictionary["product.Order[0].Address.Street"] =
|
||||
new ModelState { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("product.Order[0].Address.Street", "Street invalid.");
|
||||
dictionary["product.Order[1].Name"] = new ModelState { ValidationState = ModelValidationState.Valid };
|
||||
dictionary["product.Order[0]"] = new ModelState { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("product.Order[0]", "Order invalid.");
|
||||
|
||||
// Act
|
||||
ModelBindingHelper.ClearValidationStateForModel(
|
||||
typeof(Product),
|
||||
dictionary,
|
||||
metadataProvider,
|
||||
prefix);
|
||||
|
||||
// Assert
|
||||
foreach (var entry in dictionary.Keys)
|
||||
{
|
||||
if (entry.StartsWith(prefix))
|
||||
{
|
||||
Assert.Equal(0, dictionary[entry].Errors.Count);
|
||||
Assert.Equal(ModelValidationState.Unvalidated, dictionary[entry].ValidationState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IModelBinder GetCompositeBinder(params IModelBinder[] binders)
|
||||
{
|
||||
return new CompositeModelBinder(binders);
|
||||
|
|
@ -710,6 +831,25 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
|||
|
||||
public string ExcludedProperty { get; set; }
|
||||
}
|
||||
|
||||
private class Product
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public int Id { get; set; }
|
||||
public Category Category { get; set; }
|
||||
public List<Order> Orders { get; set; }
|
||||
}
|
||||
|
||||
public class Category
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public class Order
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public Address Address { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -2066,6 +2066,23 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
Assert.False(result[1].Checked);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryUpdateModel_ClearsModelStateEntries()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestHelper.CreateServer(_app, SiteName);
|
||||
var client = server.CreateClient();
|
||||
var url = "http://localhost/TryUpdateModel/TryUpdateModel_ClearsModelStateEntries";
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync(url);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
Assert.Equal(string.Empty, body);
|
||||
}
|
||||
|
||||
private async Task<TVal> ReadValue<TVal>(HttpResponseMessage response)
|
||||
{
|
||||
Assert.True(response.IsSuccessStatusCode);
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
Assert.Equal("CompanyName cannot be null or empty.", json["product.CompanyName"]);
|
||||
Assert.Equal("The field Price must be between 20 and 100.", json["product.Price"]);
|
||||
Assert.Equal("The Category field is required.", json["product.Category"]);
|
||||
Assert.Equal("The field Contact Us must be a string with a maximum length of 20."+
|
||||
Assert.Equal("The field Contact Us must be a string with a maximum length of 20." +
|
||||
"The field Contact Us must match the regular expression '^[0-9]*$'.", json["product.Contact"]);
|
||||
Assert.Equal("CompanyName cannot be null or empty.", json["CompanyName"]);
|
||||
Assert.Equal("The field Price must be between 20 and 100.", json["Price"]);
|
||||
|
|
@ -86,5 +86,37 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
var body = await response.Content.ReadAsStringAsync();
|
||||
Assert.Equal("{}", body);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryValidateModel_CollectionsModel_ReturnsErrorsForInvalidProperties()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestHelper.CreateServer(_app, SiteName);
|
||||
var client = server.CreateClient();
|
||||
var input = "[ { \"Price\": 2, \"Contact\": \"acvrdzersaererererfdsfdsfdsfsdf\", " +
|
||||
"\"ProductDetails\": {\"Detail1\": \"d1\", \"Detail2\": \"d2\", \"Detail3\": \"d3\"} }," +
|
||||
"{\"Price\": 2, \"Contact\": \"acvrdzersaererererfdsfdsfdsfsdf\", " +
|
||||
"\"ProductDetails\": {\"Detail1\": \"d1\", \"Detail2\": \"d2\", \"Detail3\": \"d3\"} }]";
|
||||
var content = new StringContent(input, Encoding.UTF8, "application/json");
|
||||
var url =
|
||||
"http://localhost/ModelMetadataTypeValidation/TryValidateModelWithCollectionsModel";
|
||||
|
||||
// Act
|
||||
var response = await client.PostAsync(url, content);
|
||||
|
||||
// Assert
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var json = JsonConvert.DeserializeObject<Dictionary<string, string>>(body);
|
||||
Assert.Equal("CompanyName cannot be null or empty.", json["[0].CompanyName"]);
|
||||
Assert.Equal("The field Price must be between 20 and 100.", json["[0].Price"]);
|
||||
Assert.Equal("The Category field is required.", json["[0].Category"]);
|
||||
Assert.Equal("The field Contact Us must be a string with a maximum length of 20." +
|
||||
"The field Contact Us must match the regular expression '^[0-9]*$'.", json["[0].Contact"]);
|
||||
Assert.Equal("CompanyName cannot be null or empty.", json["[1].CompanyName"]);
|
||||
Assert.Equal("The field Price must be between 20 and 100.", json["[1].Price"]);
|
||||
Assert.Equal("The Category field is required.", json["[1].Category"]);
|
||||
Assert.Equal("The field Contact Us must be a string with a maximum length of 20." +
|
||||
"The field Contact Us must match the regular expression '^[0-9]*$'.", json["[1].Contact"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Microsoft.Framework.Internal;
|
||||
using Xunit;
|
||||
|
|
@ -238,7 +239,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
[Fact]
|
||||
public void GetValidationState_ReturnsValidationStateForKey_IgnoresChildren()
|
||||
{
|
||||
// Arrange
|
||||
// Arrange
|
||||
var msd = new ModelStateDictionary();
|
||||
msd.AddModelError("foo.bar", "error text");
|
||||
|
||||
|
|
@ -249,12 +250,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Assert.Equal(ModelValidationState.Unvalidated, validationState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetFieldValidationState_ReturnsInvalidIfKeyChildContainsErrors()
|
||||
[Theory]
|
||||
[InlineData("foo")]
|
||||
[InlineData("foo.bar")]
|
||||
[InlineData("[0].foo.bar")]
|
||||
[InlineData("[0].foo.bar[0]")]
|
||||
public void GetFieldValidationState_ReturnsInvalidIfKeyChildContainsErrors(string key)
|
||||
{
|
||||
// Arrange
|
||||
var msd = new ModelStateDictionary();
|
||||
msd.AddModelError("foo.bar", "error text");
|
||||
msd.AddModelError(key, "error text");
|
||||
|
||||
// Act
|
||||
var validationState = msd.GetFieldValidationState("foo");
|
||||
|
|
@ -263,22 +268,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Assert.Equal(ModelValidationState.Invalid, validationState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetFieldValidationState_ReturnsInvalidIfKeyContainsErrors()
|
||||
{
|
||||
// Arrange
|
||||
var msd = new ModelStateDictionary();
|
||||
msd.AddModelError("foo", "error text");
|
||||
|
||||
// Act
|
||||
var validationState = msd.GetFieldValidationState("foo");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(ModelValidationState.Invalid, validationState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetFieldValidationState_ReturnsValidIfModelStateDoesNotContainErrors()
|
||||
[Theory]
|
||||
[InlineData("foo")]
|
||||
[InlineData("foo.bar")]
|
||||
[InlineData("[0].foo.bar")]
|
||||
[InlineData("[0].foo.bar[0]")]
|
||||
public void GetFieldValidationState_ReturnsValidIfModelStateDoesNotContainErrors(string key)
|
||||
{
|
||||
// Arrange
|
||||
var validState = new ModelState
|
||||
|
|
@ -288,7 +283,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
};
|
||||
var msd = new ModelStateDictionary
|
||||
{
|
||||
{ "foo", validState }
|
||||
{ key, validState }
|
||||
};
|
||||
|
||||
// Act
|
||||
|
|
@ -487,6 +482,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
[Theory]
|
||||
[InlineData("user")]
|
||||
[InlineData("user.Age")]
|
||||
[InlineData("product")]
|
||||
public void GetFieldValidity_ReturnsInvalid_IfAllKeysAreValidatedAndAnyEntryIsInvalid(string key)
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -494,6 +490,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
dictionary["user.Address"] = new ModelState { ValidationState = ModelValidationState.Valid };
|
||||
dictionary["user.Name"] = new ModelState { ValidationState = ModelValidationState.Valid };
|
||||
dictionary.AddModelError("user.Age", "Age is not a valid int");
|
||||
dictionary["[0].product.Name"] = new ModelState { ValidationState = ModelValidationState.Valid };
|
||||
dictionary["[0].product.Age[0]"] = new ModelState { ValidationState = ModelValidationState.Valid };
|
||||
dictionary.AddModelError("[1].product.Name", "Name is invalid");
|
||||
|
||||
// Act
|
||||
var validationState = dictionary.GetFieldValidationState(key);
|
||||
|
|
@ -733,6 +732,142 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Assert.Empty(error.ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModelStateDictionary_ClearEntriesThatMatchWithKey_NonEmptyKey()
|
||||
{
|
||||
// Arrange
|
||||
var dictionary = new ModelStateDictionary();
|
||||
|
||||
dictionary["Property1"] = new ModelState { ValidationState = ModelValidationState.Valid };
|
||||
|
||||
dictionary["Property2"] = new ModelState { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("Property2", "Property2 invalid.");
|
||||
|
||||
dictionary["Property3"] = new ModelState { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("Property3", "Property invalid.");
|
||||
|
||||
dictionary["Property4"] = new ModelState { ValidationState = ModelValidationState.Skipped };
|
||||
|
||||
// Act
|
||||
dictionary.ClearValidationState("Property1");
|
||||
dictionary.ClearValidationState("Property2");
|
||||
dictionary.ClearValidationState("Property4");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, dictionary["Property1"].Errors.Count);
|
||||
Assert.Equal(ModelValidationState.Unvalidated, dictionary["Property1"].ValidationState);
|
||||
Assert.Equal(0, dictionary["Property2"].Errors.Count);
|
||||
Assert.Equal(ModelValidationState.Unvalidated, dictionary["Property2"].ValidationState);
|
||||
Assert.Equal(1, dictionary["Property3"].Errors.Count);
|
||||
Assert.Equal(ModelValidationState.Invalid, dictionary["Property3"].ValidationState);
|
||||
Assert.Equal(0, dictionary["Property4"].Errors.Count);
|
||||
Assert.Equal(ModelValidationState.Unvalidated, dictionary["Property4"].ValidationState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModelStateDictionary_ClearEntriesPrefixedWithKey_NonEmptyKey()
|
||||
{
|
||||
// Arrange
|
||||
var dictionary = new ModelStateDictionary();
|
||||
|
||||
dictionary["Product"] = new ModelState { ValidationState = ModelValidationState.Valid };
|
||||
|
||||
dictionary["Product.Detail1"] = new ModelState { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("Product.Detail1", "Product Detail1 invalid.");
|
||||
|
||||
dictionary["Product.Detail2[0]"] = new ModelState { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("Product.Detail2[0]", "Product Detail2[0] invalid.");
|
||||
|
||||
dictionary["Product.Detail2[1]"] = new ModelState { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("Product.Detail2[1]", "Product Detail2[1] invalid.");
|
||||
|
||||
dictionary["Product.Detail2[2]"] = new ModelState { ValidationState = ModelValidationState.Skipped };
|
||||
|
||||
dictionary["Product.Detail3"] = new ModelState { ValidationState = ModelValidationState.Skipped };
|
||||
|
||||
dictionary["ProductName"] = new ModelState { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("ProductName", "ProductName invalid.");
|
||||
|
||||
// Act
|
||||
dictionary.ClearValidationState("Product");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, dictionary["Product"].Errors.Count);
|
||||
Assert.Equal(ModelValidationState.Unvalidated, dictionary["Product"].ValidationState);
|
||||
Assert.Equal(0, dictionary["Product.Detail1"].Errors.Count);
|
||||
Assert.Equal(ModelValidationState.Unvalidated, dictionary["Product.Detail1"].ValidationState);
|
||||
Assert.Equal(0, dictionary["Product.Detail2[0]"].Errors.Count);
|
||||
Assert.Equal(ModelValidationState.Unvalidated, dictionary["Product.Detail2[0]"].ValidationState);
|
||||
Assert.Equal(0, dictionary["Product.Detail2[1]"].Errors.Count);
|
||||
Assert.Equal(ModelValidationState.Unvalidated, dictionary["Product.Detail2[1]"].ValidationState);
|
||||
Assert.Equal(0, dictionary["Product.Detail2[2]"].Errors.Count);
|
||||
Assert.Equal(ModelValidationState.Unvalidated, dictionary["Product.Detail2[2]"].ValidationState);
|
||||
Assert.Equal(0, dictionary["Product.Detail3"].Errors.Count);
|
||||
Assert.Equal(ModelValidationState.Unvalidated, dictionary["Product.Detail3"].ValidationState);
|
||||
Assert.Equal(1, dictionary["ProductName"].Errors.Count);
|
||||
Assert.Equal(ModelValidationState.Invalid, dictionary["ProductName"].ValidationState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModelStateDictionary_ClearEntries_KeyHasDot_NonEmptyKey()
|
||||
{
|
||||
// Arrange
|
||||
var dictionary = new ModelStateDictionary();
|
||||
|
||||
dictionary["Product"] = new ModelState { ValidationState = ModelValidationState.Valid };
|
||||
|
||||
dictionary["Product.Detail1"] = new ModelState { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("Product.Detail1", "Product Detail1 invalid.");
|
||||
|
||||
dictionary["Product.Detail1.Name"] = new ModelState { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("Product.Detail1.Name", "Product Detail1 Name invalid.");
|
||||
|
||||
dictionary["Product.Detail1Name"] = new ModelState { ValidationState = ModelValidationState.Skipped };
|
||||
|
||||
// Act
|
||||
dictionary.ClearValidationState("Product.Detail1");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(ModelValidationState.Valid, dictionary["Product"].ValidationState);
|
||||
Assert.Equal(0, dictionary["Product.Detail1"].Errors.Count);
|
||||
Assert.Equal(ModelValidationState.Unvalidated, dictionary["Product.Detail1"].ValidationState);
|
||||
Assert.Equal(0, dictionary["Product.Detail1.Name"].Errors.Count);
|
||||
Assert.Equal(ModelValidationState.Unvalidated, dictionary["Product.Detail1.Name"].ValidationState);
|
||||
Assert.Equal(ModelValidationState.Skipped, dictionary["Product.Detail1Name"].ValidationState);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(null)]
|
||||
public void ModelStateDictionary_ClearsAllEntries_EmptyKey(string modelKey)
|
||||
{
|
||||
// Arrange
|
||||
var dictionary = new ModelStateDictionary();
|
||||
|
||||
dictionary["Property1"] = new ModelState { ValidationState = ModelValidationState.Valid };
|
||||
|
||||
dictionary["Property2"] = new ModelState { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("Property2", "Property2 invalid.");
|
||||
|
||||
dictionary["Property3"] = new ModelState { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("Property3", "Property invalid.");
|
||||
|
||||
dictionary["Property4"] = new ModelState { ValidationState = ModelValidationState.Skipped };
|
||||
|
||||
// Act
|
||||
dictionary.ClearValidationState(modelKey);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, dictionary["Property1"].Errors.Count);
|
||||
Assert.Equal(ModelValidationState.Unvalidated, dictionary["Property1"].ValidationState);
|
||||
Assert.Equal(0, dictionary["Property2"].Errors.Count);
|
||||
Assert.Equal(ModelValidationState.Unvalidated, dictionary["Property2"].ValidationState);
|
||||
Assert.Equal(0, dictionary["Property3"].Errors.Count);
|
||||
Assert.Equal(ModelValidationState.Unvalidated, dictionary["Property3"].ValidationState);
|
||||
Assert.Equal(0, dictionary["Property4"].Errors.Count);
|
||||
Assert.Equal(ModelValidationState.Unvalidated, dictionary["Property4"].ValidationState);
|
||||
}
|
||||
|
||||
private static ValueProviderResult GetValueProviderResult(object rawValue = null, string attemptedValue = null)
|
||||
{
|
||||
return new ValueProviderResult(rawValue ?? "some value",
|
||||
|
|
@ -740,4 +875,4 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,12 +2,14 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http.Core.Collections;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNet.Http.Core.Collections;
|
||||
using Microsoft.AspNet.WebUtilities;
|
||||
using ModelBindingWebSite.Models;
|
||||
|
||||
namespace ModelBindingWebSite.Controllers
|
||||
|
|
@ -158,6 +160,37 @@ namespace ModelBindingWebSite.Controllers
|
|||
return user;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> TryUpdateModel_ClearsModelStateEntries()
|
||||
{
|
||||
var result = new ObjectResult(null);
|
||||
|
||||
// Invalid model.
|
||||
var model = new MyModel
|
||||
{
|
||||
Id = 1,
|
||||
Price = -1
|
||||
};
|
||||
|
||||
// Validate model first and subsequent TryUpdateModel should remove
|
||||
//modelstate entries for model and re-validate.
|
||||
TryValidateModel(model);
|
||||
|
||||
// Update Name to a valid value and call TryUpdateModel
|
||||
model.Price = 1;
|
||||
await TryUpdateModelAsync<MyModel>(model);
|
||||
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
result.StatusCode = StatusCodes.Status204NoContent;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.StatusCode = StatusCodes.Status500InternalServerError;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private User GetUser(int id)
|
||||
{
|
||||
return new User
|
||||
|
|
@ -168,6 +201,14 @@ namespace ModelBindingWebSite.Controllers
|
|||
};
|
||||
}
|
||||
|
||||
private class MyModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
[Range(0,10)]
|
||||
public double Price { get; set; }
|
||||
}
|
||||
|
||||
public class CustomValueProvider : IValueProvider
|
||||
{
|
||||
public Task<bool> ContainsPrefixAsync(string prefix)
|
||||
|
|
|
|||
|
|
@ -31,13 +31,21 @@ namespace ValidationWebSite.Controllers
|
|||
{
|
||||
// Clear ModelState entry. TryValidateModel should not add entries except those found within the
|
||||
// passed model.
|
||||
ModelState["theImpossibleString"].Errors.Clear();
|
||||
ModelState.ClearValidationState("theImpossibleString");
|
||||
|
||||
TryValidateModel(product);
|
||||
|
||||
return CreateValidationDictionary();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public object TryValidateModelWithCollectionsModel([FromBody] List<ProductViewModel> products)
|
||||
{
|
||||
TryValidateModel(products);
|
||||
|
||||
return CreateValidationDictionary();
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public object TryValidateModelSoftwareViewModelWithPrefix()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue