Clear ModelState errors of model before TryValidateModel or TryUpdateModel

This commit is contained in:
Kirthi Krishnamraju 2015-03-02 22:56:34 -08:00
parent e9f56055eb
commit 7b18d1d3f1
10 changed files with 500 additions and 27 deletions

View File

@ -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,

View File

@ -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);
}
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -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"]);
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -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)

View File

@ -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()
{