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)
|
public virtual Task<bool> TryUpdateModelAsync<TModel>([NotNull] TModel model)
|
||||||
where TModel : class
|
where TModel : class
|
||||||
{
|
{
|
||||||
return TryUpdateModelAsync(model, prefix: null);
|
return TryUpdateModelAsync(model, prefix: string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -1240,6 +1240,14 @@ namespace Microsoft.AspNet.Mvc
|
||||||
var modelExplorer = MetadataProvider.GetModelExplorerForType(model.GetType(), model);
|
var modelExplorer = MetadataProvider.GetModelExplorerForType(model.GetType(), model);
|
||||||
|
|
||||||
var modelName = prefix ?? string.Empty;
|
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(
|
var validationContext = new ModelValidationContext(
|
||||||
modelName,
|
modelName,
|
||||||
BindingContext.ValidatorProvider,
|
BindingContext.ValidatorProvider,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
@ -254,6 +257,9 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
var modelMetadata = metadataProvider.GetMetadataForType(modelType);
|
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
|
var operationBindingContext = new OperationBindingContext
|
||||||
{
|
{
|
||||||
ModelBinder = modelBinder,
|
ModelBinder = modelBinder,
|
||||||
|
|
@ -381,5 +387,62 @@ namespace Microsoft.AspNet.Mvc
|
||||||
return prefix + "." + propertyName;
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Microsoft.Framework.Internal;
|
using Microsoft.Framework.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ModelBinding.Internal
|
namespace Microsoft.AspNet.Mvc.ModelBinding.Internal
|
||||||
|
|
@ -28,6 +29,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Internal
|
||||||
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;
|
continue;
|
||||||
|
|
|
||||||
|
|
@ -369,6 +369,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
GetModelStateForKey(key).Value = value;
|
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)
|
private ModelState GetModelStateForKey([NotNull] string key)
|
||||||
{
|
{
|
||||||
ModelState modelState;
|
ModelState modelState;
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -667,6 +668,126 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
Assert.Equal(expectedMessage, exception.Message);
|
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)
|
private static IModelBinder GetCompositeBinder(params IModelBinder[] binders)
|
||||||
{
|
{
|
||||||
return new CompositeModelBinder(binders);
|
return new CompositeModelBinder(binders);
|
||||||
|
|
@ -710,6 +831,25 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
|
|
||||||
public string ExcludedProperty { get; set; }
|
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
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -2066,6 +2066,23 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
Assert.False(result[1].Checked);
|
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)
|
private async Task<TVal> ReadValue<TVal>(HttpResponseMessage response)
|
||||||
{
|
{
|
||||||
Assert.True(response.IsSuccessStatusCode);
|
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("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 field Price must be between 20 and 100.", json["product.Price"]);
|
||||||
Assert.Equal("The Category field is required.", json["product.Category"]);
|
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"]);
|
"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("CompanyName cannot be null or empty.", json["CompanyName"]);
|
||||||
Assert.Equal("The field Price must be between 20 and 100.", json["Price"]);
|
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();
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
Assert.Equal("{}", body);
|
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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Microsoft.Framework.Internal;
|
using Microsoft.Framework.Internal;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -238,7 +239,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetValidationState_ReturnsValidationStateForKey_IgnoresChildren()
|
public void GetValidationState_ReturnsValidationStateForKey_IgnoresChildren()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var msd = new ModelStateDictionary();
|
var msd = new ModelStateDictionary();
|
||||||
msd.AddModelError("foo.bar", "error text");
|
msd.AddModelError("foo.bar", "error text");
|
||||||
|
|
||||||
|
|
@ -249,12 +250,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
Assert.Equal(ModelValidationState.Unvalidated, validationState);
|
Assert.Equal(ModelValidationState.Unvalidated, validationState);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Theory]
|
||||||
public void GetFieldValidationState_ReturnsInvalidIfKeyChildContainsErrors()
|
[InlineData("foo")]
|
||||||
|
[InlineData("foo.bar")]
|
||||||
|
[InlineData("[0].foo.bar")]
|
||||||
|
[InlineData("[0].foo.bar[0]")]
|
||||||
|
public void GetFieldValidationState_ReturnsInvalidIfKeyChildContainsErrors(string key)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var msd = new ModelStateDictionary();
|
var msd = new ModelStateDictionary();
|
||||||
msd.AddModelError("foo.bar", "error text");
|
msd.AddModelError(key, "error text");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var validationState = msd.GetFieldValidationState("foo");
|
var validationState = msd.GetFieldValidationState("foo");
|
||||||
|
|
@ -263,22 +268,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
Assert.Equal(ModelValidationState.Invalid, validationState);
|
Assert.Equal(ModelValidationState.Invalid, validationState);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Theory]
|
||||||
public void GetFieldValidationState_ReturnsInvalidIfKeyContainsErrors()
|
[InlineData("foo")]
|
||||||
{
|
[InlineData("foo.bar")]
|
||||||
// Arrange
|
[InlineData("[0].foo.bar")]
|
||||||
var msd = new ModelStateDictionary();
|
[InlineData("[0].foo.bar[0]")]
|
||||||
msd.AddModelError("foo", "error text");
|
public void GetFieldValidationState_ReturnsValidIfModelStateDoesNotContainErrors(string key)
|
||||||
|
|
||||||
// Act
|
|
||||||
var validationState = msd.GetFieldValidationState("foo");
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(ModelValidationState.Invalid, validationState);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void GetFieldValidationState_ReturnsValidIfModelStateDoesNotContainErrors()
|
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var validState = new ModelState
|
var validState = new ModelState
|
||||||
|
|
@ -288,7 +283,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
};
|
};
|
||||||
var msd = new ModelStateDictionary
|
var msd = new ModelStateDictionary
|
||||||
{
|
{
|
||||||
{ "foo", validState }
|
{ key, validState }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -487,6 +482,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("user")]
|
[InlineData("user")]
|
||||||
[InlineData("user.Age")]
|
[InlineData("user.Age")]
|
||||||
|
[InlineData("product")]
|
||||||
public void GetFieldValidity_ReturnsInvalid_IfAllKeysAreValidatedAndAnyEntryIsInvalid(string key)
|
public void GetFieldValidity_ReturnsInvalid_IfAllKeysAreValidatedAndAnyEntryIsInvalid(string key)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -494,6 +490,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
dictionary["user.Address"] = new ModelState { ValidationState = ModelValidationState.Valid };
|
dictionary["user.Address"] = new ModelState { ValidationState = ModelValidationState.Valid };
|
||||||
dictionary["user.Name"] = new ModelState { ValidationState = ModelValidationState.Valid };
|
dictionary["user.Name"] = new ModelState { ValidationState = ModelValidationState.Valid };
|
||||||
dictionary.AddModelError("user.Age", "Age is not a valid int");
|
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
|
// Act
|
||||||
var validationState = dictionary.GetFieldValidationState(key);
|
var validationState = dictionary.GetFieldValidationState(key);
|
||||||
|
|
@ -733,6 +732,142 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
Assert.Empty(error.ErrorMessage);
|
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)
|
private static ValueProviderResult GetValueProviderResult(object rawValue = null, string attemptedValue = null)
|
||||||
{
|
{
|
||||||
return new ValueProviderResult(rawValue ?? "some value",
|
return new ValueProviderResult(rawValue ?? "some value",
|
||||||
|
|
@ -740,4 +875,4 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
CultureInfo.InvariantCulture);
|
CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,12 +2,14 @@
|
||||||
// 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;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Http.Core.Collections;
|
||||||
using Microsoft.AspNet.Mvc;
|
using Microsoft.AspNet.Mvc;
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
using System.Collections.Generic;
|
using Microsoft.AspNet.WebUtilities;
|
||||||
using Microsoft.AspNet.Http.Core.Collections;
|
|
||||||
using ModelBindingWebSite.Models;
|
using ModelBindingWebSite.Models;
|
||||||
|
|
||||||
namespace ModelBindingWebSite.Controllers
|
namespace ModelBindingWebSite.Controllers
|
||||||
|
|
@ -158,6 +160,37 @@ namespace ModelBindingWebSite.Controllers
|
||||||
return user;
|
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)
|
private User GetUser(int id)
|
||||||
{
|
{
|
||||||
return new User
|
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 class CustomValueProvider : IValueProvider
|
||||||
{
|
{
|
||||||
public Task<bool> ContainsPrefixAsync(string prefix)
|
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
|
// Clear ModelState entry. TryValidateModel should not add entries except those found within the
|
||||||
// passed model.
|
// passed model.
|
||||||
ModelState["theImpossibleString"].Errors.Clear();
|
ModelState.ClearValidationState("theImpossibleString");
|
||||||
|
|
||||||
TryValidateModel(product);
|
TryValidateModel(product);
|
||||||
|
|
||||||
return CreateValidationDictionary();
|
return CreateValidationDictionary();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public object TryValidateModelWithCollectionsModel([FromBody] List<ProductViewModel> products)
|
||||||
|
{
|
||||||
|
TryValidateModel(products);
|
||||||
|
|
||||||
|
return CreateValidationDictionary();
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public object TryValidateModelSoftwareViewModelWithPrefix()
|
public object TryValidateModelSoftwareViewModelWithPrefix()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue