Limit the maximum number of Model errors to a reasonable value.
Fixes #490
This commit is contained in:
parent
9befa6e3a2
commit
646c0d704d
|
|
@ -148,7 +148,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
errorHandler = (sender, e) =>
|
||||
{
|
||||
var exception = e.ErrorContext.Error;
|
||||
context.ActionContext.ModelState.AddModelError(e.ErrorContext.Path, e.ErrorContext.Error);
|
||||
context.ActionContext.ModelState.TryAddModelError(e.ErrorContext.Path, e.ErrorContext.Error);
|
||||
// Error must always be marked as handled
|
||||
// Failure to do so can cause the exception to be rethrown at every recursive level and
|
||||
// overflow the stack for x64 CLR processes
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
{
|
||||
private AntiForgeryOptions _antiForgeryOptions = new AntiForgeryOptions();
|
||||
private RazorViewEngineOptions _viewEngineOptions = new RazorViewEngineOptions();
|
||||
private int _maxModelStateErrors = 200;
|
||||
|
||||
public MvcOptions()
|
||||
{
|
||||
|
|
@ -94,6 +95,25 @@ namespace Microsoft.AspNet.Mvc
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum number of validation errors that are allowed by this application before further
|
||||
/// errors are ignored.
|
||||
/// </summary>
|
||||
public int MaxModelValidationErrors
|
||||
{
|
||||
get { return _maxModelStateErrors; }
|
||||
set
|
||||
{
|
||||
if (value < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value));
|
||||
}
|
||||
|
||||
_maxModelStateErrors = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a list of the <see cref="ModelBinderDescriptor" /> used by the
|
||||
/// Gets a list of the <see cref="ModelBinderDescriptor" /> used by the
|
||||
/// <see cref="ModelBinding.CompositeModelBinder" />.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using Microsoft.AspNet.Mvc.Logging;
|
|||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
|
|
@ -32,7 +33,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
public async Task RouteAsync([NotNull] RouteContext context)
|
||||
{
|
||||
var services = context.HttpContext.RequestServices;
|
||||
|
||||
|
||||
// Verify if AddMvc was done before calling UseMvc
|
||||
// We use the MvcMarkerService to make sure if all the services were added.
|
||||
MvcServicesHelper.ThrowIfMvcNotRegistered(services);
|
||||
|
|
@ -64,19 +65,22 @@ namespace Microsoft.AspNet.Mvc
|
|||
return;
|
||||
}
|
||||
|
||||
if (actionDescriptor.RouteValueDefaults != null)
|
||||
{
|
||||
foreach (var kvp in actionDescriptor.RouteValueDefaults)
|
||||
if (actionDescriptor.RouteValueDefaults != null)
|
||||
{
|
||||
if (!context.RouteData.Values.ContainsKey(kvp.Key))
|
||||
foreach (var kvp in actionDescriptor.RouteValueDefaults)
|
||||
{
|
||||
context.RouteData.Values.Add(kvp.Key, kvp.Value);
|
||||
if (!context.RouteData.Values.ContainsKey(kvp.Key))
|
||||
{
|
||||
context.RouteData.Values.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var actionContext = new ActionContext(context.HttpContext, context.RouteData, actionDescriptor);
|
||||
|
||||
var optionsAccessor = services.GetService<IOptionsAccessor<MvcOptions>>();
|
||||
actionContext.ModelState.MaxAllowedErrors = optionsAccessor.Options.MaxModelValidationErrors;
|
||||
|
||||
var contextAccessor = services.GetService<IContextAccessor<ActionContext>>();
|
||||
using (contextAccessor.SetContextSource(() => actionContext, PreventExchange))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -65,8 +65,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
// Only perform validation at the root of the object graph. ValidationNode will recursively walk the graph.
|
||||
// Ignore ComplexModelDto since it essentially wraps the primary object.
|
||||
if (newBindingContext.ModelMetadata.ContainerType == null &&
|
||||
newBindingContext.ModelMetadata.ModelType != typeof(ComplexModelDto))
|
||||
if (IsBindingAtRootOfObjectGraph(newBindingContext))
|
||||
{
|
||||
// run validation and return the model
|
||||
// If we fell back to an empty prefix above and are dealing with simple types,
|
||||
|
|
@ -92,7 +91,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> TryBind([NotNull] ModelBindingContext bindingContext)
|
||||
private async Task<bool> TryBind(ModelBindingContext bindingContext)
|
||||
{
|
||||
// TODO: RuntimeHelpers.EnsureSufficientExecutionStack does not exist in the CoreCLR.
|
||||
// Protects against stack overflow for deeply nested model binding
|
||||
|
|
@ -110,6 +109,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
return false;
|
||||
}
|
||||
|
||||
private static bool IsBindingAtRootOfObjectGraph(ModelBindingContext bindingContext)
|
||||
{
|
||||
// We're at the root of the object graph if the model does does not have a container.
|
||||
// This statement is true for complex types at the root twice over - once with the actual model
|
||||
// and once when when it is represented by a ComplexModelDto. Ignore the latter case.
|
||||
|
||||
return bindingContext.ModelMetadata.ContainerType == null &&
|
||||
bindingContext.ModelMetadata.ModelType != typeof(ComplexModelDto);
|
||||
}
|
||||
|
||||
private static ModelBindingContext CreateNewBindingContext(ModelBindingContext oldBindingContext,
|
||||
string modelName,
|
||||
bool reuseValidationNode)
|
||||
|
|
|
|||
|
|
@ -127,10 +127,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
// var errorMessage = ModelBinderConfig.ValueRequiredErrorMessageProvider(e.ValidationContext,
|
||||
// modelMetadata,
|
||||
// incomingValue);
|
||||
var errorMessage = "A value is required.";
|
||||
var errorMessage = Resources.ModelBinderConfig_ValueRequired;
|
||||
if (errorMessage != null)
|
||||
{
|
||||
modelState.AddModelError(validationNode.ModelStateKey, errorMessage);
|
||||
modelState.TryAddModelError(validationNode.ModelStateKey, errorMessage);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -232,7 +232,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
// (oddly) succeeded.
|
||||
if (!addedError)
|
||||
{
|
||||
bindingContext.ModelState.AddModelError(
|
||||
bindingContext.ModelState.TryAddModelError(
|
||||
modelStateKey,
|
||||
Resources.FormatMissingRequiredMember(missingRequiredProperty));
|
||||
}
|
||||
|
|
@ -285,7 +285,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var validationContext = new ModelValidationContext(bindingContext, propertyMetadata);
|
||||
foreach (var validationResult in requiredValidator.Validate(validationContext))
|
||||
{
|
||||
bindingContext.ModelState.AddModelError(modelStateKey, validationResult.Message);
|
||||
bindingContext.ModelState.TryAddModelError(modelStateKey, validationResult.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -337,7 +337,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var addedError = false;
|
||||
foreach (var validationResult in validator.Validate(validationContext))
|
||||
{
|
||||
bindingContext.ModelState.AddModelError(modelStateKey, validationResult.Message);
|
||||
bindingContext.ModelState.TryAddModelError(modelStateKey, validationResult.Message);
|
||||
addedError = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -98,11 +98,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Internal
|
|||
{
|
||||
if (IsFormatException(ex))
|
||||
{
|
||||
bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex.Message);
|
||||
bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, ex.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex);
|
||||
bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,53 +9,101 @@ using Microsoft.AspNet.Mvc.ModelBinding.Internal;
|
|||
|
||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the state of an attempt to bind values from an HTTP Request to an action method, which includes
|
||||
/// validation information.
|
||||
/// </summary>
|
||||
public class ModelStateDictionary : IDictionary<string, ModelState>
|
||||
{
|
||||
private readonly IDictionary<string, ModelState> _innerDictionary;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModelStateDictionary"/> class.
|
||||
/// </summary>
|
||||
public ModelStateDictionary()
|
||||
{
|
||||
_innerDictionary = new Dictionary<string, ModelState>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModelStateDictionary"/> class by using values that are copied
|
||||
/// from the specified <paramref name="dictionary"/>.
|
||||
/// </summary>
|
||||
/// <param name="dictionary">The <see cref="ModelStateDictionary"/> to copy values from.</param>
|
||||
public ModelStateDictionary([NotNull] ModelStateDictionary dictionary)
|
||||
{
|
||||
_innerDictionary = new CopyOnWriteDictionary<string, ModelState>(dictionary,
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
MaxAllowedErrors = dictionary.MaxAllowedErrors;
|
||||
ErrorCount = dictionary.ErrorCount;
|
||||
HasRecordedMaxModelError = dictionary.HasRecordedMaxModelError;
|
||||
}
|
||||
|
||||
#region IDictionary properties
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum allowed errors in this instance of <see cref="ModelStateDictionary"/>.
|
||||
/// Defaults to <see cref="int.MaxValue"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value of this property is used to track the total number of calls to
|
||||
/// <see cref="AddModelError(string, Exception)"/> and <see cref="AddModelError(string, string)"/> after which
|
||||
/// an error is thrown for further invocations. Errors added via modifying <see cref="ModelState"/> do not
|
||||
/// count towards this limit.
|
||||
/// </remarks>
|
||||
public int MaxAllowedErrors { get; set; } = int.MaxValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a flag that determines if the total number of added errors (given by <see cref="ErrorCount"/>) is
|
||||
/// fewer than <see cref="MaxAllowedErrors"/>.
|
||||
/// </summary>
|
||||
public bool CanAddErrors
|
||||
{
|
||||
get { return ErrorCount < MaxAllowedErrors; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of errors added to this instance of <see cref="ModelStateDictionary"/> via
|
||||
/// <see cref="AddModelError(string, Exception)"/> and <see cref="AddModelError(string, string)"/>.
|
||||
/// </summary>
|
||||
public int ErrorCount { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Count
|
||||
{
|
||||
get { return _innerDictionary.Count; }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsReadOnly
|
||||
{
|
||||
get { return _innerDictionary.IsReadOnly; }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICollection<string> Keys
|
||||
{
|
||||
get { return _innerDictionary.Keys; }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICollection<ModelState> Values
|
||||
{
|
||||
get { return _innerDictionary.Values; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsValid
|
||||
{
|
||||
get { return ValidationState == ModelValidationState.Valid; }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ModelValidationState ValidationState
|
||||
{
|
||||
get { return GetValidity(_innerDictionary); }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ModelState this[[NotNull] string key]
|
||||
{
|
||||
get
|
||||
|
|
@ -80,20 +128,94 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
get { return _innerDictionary; }
|
||||
}
|
||||
|
||||
// Flag that indiciates if TooManyModelErrorException has already been added to this dictionary.
|
||||
private bool HasRecordedMaxModelError { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified <paramref name="exception"/> to the <see cref="ModelState.Errors"/> instance
|
||||
/// that is associated with the specified <paramref name="key"/>.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the <see cref="ModelState"/> to add errors to.</param>
|
||||
/// <param name="exception">The <see cref="Exception"/> to add.</param>
|
||||
public void AddModelError([NotNull] string key, [NotNull] Exception exception)
|
||||
{
|
||||
var modelState = GetModelStateForKey(key);
|
||||
modelState.ValidationState = ModelValidationState.Invalid;
|
||||
modelState.Errors.Add(exception);
|
||||
TryAddModelError(key, exception);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to add the specified <paramref name="exception"/> to the <see cref="ModelState.Errors"/>
|
||||
/// instance that is associated with the specified <paramref name="key"/>. If the maximum number of allowed
|
||||
/// errors has already been recorded, records a <see cref="TooManyModelErrorsException"/> exception instead.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the <see cref="ModelState"/> to add errors to.</param>
|
||||
/// <param name="exception">The <see cref="Exception"/> to add.</param>
|
||||
/// <returns>True if the error was added, false if the dictionary has already recorded
|
||||
/// at least <see cref="MaxAllowedErrors"/> number of errors.</returns>
|
||||
/// <remarks>
|
||||
/// This method only allows adding up to <see cref="MaxAllowedErrors"/> - 1. <see cref="MaxAllowedErrors"/>nt
|
||||
/// invocation would result in adding a <see cref="TooManyModelErrorsException"/> to the dictionary.
|
||||
/// </remarks>
|
||||
public bool TryAddModelError([NotNull] string key, [NotNull] Exception exception)
|
||||
{
|
||||
if (ErrorCount >= MaxAllowedErrors - 1)
|
||||
{
|
||||
EnsureMaxErrorsReachedRecorded();
|
||||
return false;
|
||||
}
|
||||
|
||||
ErrorCount++;
|
||||
AddModelErrorCore(key, exception);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified <paramref name="errorMessage"/> to the <see cref="ModelState.Errors"/> instance
|
||||
/// that is associated with the specified <paramref name="key"/>.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the <see cref="ModelState"/> to add errors to.</param>
|
||||
/// <param name="errorMessage">The error message to add.</param>
|
||||
public void AddModelError([NotNull] string key, [NotNull] string errorMessage)
|
||||
{
|
||||
TryAddModelError(key, errorMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to add the specified <paramref name="errorMessage"/> to the <see cref="ModelState.Errors"/>
|
||||
/// instance that is associated with the specified <paramref name="key"/>. If the maximum number of allowed
|
||||
/// errors has already been recorded, records a <see cref="TooManyModelErrorsException"/> exception instead.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the <see cref="ModelState"/> to add errors to.</param>
|
||||
/// <param name="errorMessage">The error message to add.</param>
|
||||
/// <returns>True if the error was added, false if the dictionary has already recorded
|
||||
/// at least <see cref="MaxAllowedErrors"/> number of errors.</returns>
|
||||
/// <remarks>
|
||||
/// This method only allows adding up to <see cref="MaxAllowedErrors"/> - 1. <see cref="MaxAllowedErrors"/>nt
|
||||
/// invocation would result in adding a <see cref="TooManyModelErrorsException"/> to the dictionary.
|
||||
/// </remarks>
|
||||
public bool TryAddModelError([NotNull] string key, [NotNull] string errorMessage)
|
||||
{
|
||||
if (ErrorCount >= MaxAllowedErrors - 1)
|
||||
{
|
||||
EnsureMaxErrorsReachedRecorded();
|
||||
return false;
|
||||
}
|
||||
|
||||
ErrorCount++;
|
||||
var modelState = GetModelStateForKey(key);
|
||||
modelState.ValidationState = ModelValidationState.Invalid;
|
||||
modelState.Errors.Add(errorMessage);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the aggregate <see cref="ModelValidationState"/> for items starting with the
|
||||
/// specified <paramref name="key"/>.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to look up model state errors for.</param>
|
||||
/// <returns>Returns <see cref="ModelValidationState.Unvalidated"/> if no entries are found for the specified
|
||||
/// key, <see cref="ModelValidationState.Invalid"/> if at least one instance is found with one or more model
|
||||
/// state errors; <see cref="ModelValidationState.Valid"/> otherwise.</returns>
|
||||
public ModelValidationState GetFieldValidationState([NotNull] string key)
|
||||
{
|
||||
var entries = DictionaryHelper.FindKeysWithPrefix(this, key);
|
||||
|
|
@ -105,6 +227,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
return GetValidity(entries);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks the <see cref="ModelState.ValidationState"/> for the entry with the specified <paramref name="key"/>
|
||||
/// as <see cref="ModelValidationState.Valid"/>.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the <see cref="ModelState"/> to mark as valid.</param>
|
||||
public void MarkFieldValid([NotNull] string key)
|
||||
{
|
||||
var modelState = GetModelStateForKey(key);
|
||||
|
|
@ -116,6 +243,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
modelState.ValidationState = ModelValidationState.Valid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the values from the specified <paramref name="dictionary"/> into this instance, overwriting
|
||||
/// existing values if keys are the same.
|
||||
/// </summary>
|
||||
/// <param name="dictionary">The <see cref="ModelStateDictionary"/> to copy values from.</param>
|
||||
public void Merge(ModelStateDictionary dictionary)
|
||||
{
|
||||
if (dictionary == null)
|
||||
|
|
@ -129,6 +261,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value for the <see cref="ModelState"/> with the specified <paramref name="key"/> to the
|
||||
/// specified <paramref name="value"/>.
|
||||
/// </summary>
|
||||
/// <param name="key">The key for the <see cref="ModelState"/> entry.</param>
|
||||
/// <param name="value">The value to assign.</param>
|
||||
public void SetModelValue([NotNull] string key, [NotNull] ValueProviderResult value)
|
||||
{
|
||||
GetModelStateForKey(key).Value = value;
|
||||
|
|
@ -165,61 +303,88 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
return validationState;
|
||||
}
|
||||
|
||||
#region IDictionary members
|
||||
private void EnsureMaxErrorsReachedRecorded()
|
||||
{
|
||||
if (!HasRecordedMaxModelError)
|
||||
{
|
||||
var exception = new TooManyModelErrorsException(Resources.ModelStateDictionary_MaxModelStateErrors);
|
||||
AddModelErrorCore(string.Empty, exception);
|
||||
HasRecordedMaxModelError = true;
|
||||
ErrorCount++;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddModelErrorCore(string key, Exception exception)
|
||||
{
|
||||
var modelState = GetModelStateForKey(key);
|
||||
modelState.ValidationState = ModelValidationState.Invalid;
|
||||
modelState.Errors.Add(exception);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Add(KeyValuePair<string, ModelState> item)
|
||||
{
|
||||
Add(item.Key, item.Value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Add([NotNull] string key, [NotNull] ModelState value)
|
||||
{
|
||||
_innerDictionary.Add(key, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Clear()
|
||||
{
|
||||
_innerDictionary.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Contains(KeyValuePair<string, ModelState> item)
|
||||
{
|
||||
return _innerDictionary.Contains(item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ContainsKey([NotNull] string key)
|
||||
{
|
||||
return _innerDictionary.ContainsKey(key);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CopyTo([NotNull] KeyValuePair<string, ModelState>[] array, int arrayIndex)
|
||||
{
|
||||
_innerDictionary.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Remove(KeyValuePair<string, ModelState> item)
|
||||
{
|
||||
return _innerDictionary.Remove(item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Remove([NotNull] string key)
|
||||
{
|
||||
return _innerDictionary.Remove(key);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetValue([NotNull] string key, out ModelState value)
|
||||
{
|
||||
return _innerDictionary.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerator<KeyValuePair<string, ModelState>> GetEnumerator()
|
||||
{
|
||||
return _innerDictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
@ -394,6 +394,22 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("DataAnnotationsModelMetadataProvider_UnreadableProperty"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The maximum number of allowed model errors has been reached.
|
||||
/// </summary>
|
||||
internal static string ModelStateDictionary_MaxModelStateErrors
|
||||
{
|
||||
get { return GetString("ModelStateDictionary_MaxModelStateErrors"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The maximum number of allowed model errors has been reached.
|
||||
/// </summary>
|
||||
internal static string FormatModelStateDictionary_MaxModelStateErrors()
|
||||
{
|
||||
return GetString("ModelStateDictionary_MaxModelStateErrors");
|
||||
}
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -189,4 +189,7 @@
|
|||
<data name="DataAnnotationsModelMetadataProvider_UnreadableProperty" xml:space="preserve">
|
||||
<value>{0} has a DisplayColumn attribute for {1}, but property {1} does not have a public 'get' method.</value>
|
||||
</data>
|
||||
<data name="ModelStateDictionary_MaxModelStateErrors" xml:space="preserve">
|
||||
<value>The maximum number of allowed model errors has been reached.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Exception"/> that is thrown when too many model errors are encountered.
|
||||
/// </summary>
|
||||
public class TooManyModelErrorsException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="TooManyModelErrorsException"/> with the specified
|
||||
/// exception <paramref name="message"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
public TooManyModelErrorsException([NotNull] string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -102,9 +102,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
public void Validate([NotNull] ModelValidationContext validationContext, ModelValidationNode parentNode)
|
||||
{
|
||||
if (SuppressValidation)
|
||||
if (SuppressValidation || !validationContext.ModelState.CanAddErrors)
|
||||
{
|
||||
// no-op
|
||||
// Short circuit if validation does not need to be applied or if we've reached the max number of validation errors.
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -171,7 +171,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
{
|
||||
var thisErrorKey = ModelBindingHelper.CreatePropertyModelName(propertyKeyRoot,
|
||||
propertyResult.MemberName);
|
||||
modelState.AddModelError(thisErrorKey, propertyResult.Message);
|
||||
modelState.TryAddModelError(thisErrorKey, propertyResult.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -194,7 +194,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
ModelMetadata.GetDisplayName());
|
||||
if (parentNode == null && ModelMetadata.Model == null)
|
||||
{
|
||||
modelState.AddModelError(modelStateKey, Resources.Validation_ValueNotFound);
|
||||
modelState.TryAddModelError(modelStateKey, Resources.Validation_ValueNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -207,7 +207,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
{
|
||||
var currentModelStateKey = ModelBindingHelper.CreatePropertyModelName(ModelStateKey,
|
||||
validationResult.MemberName);
|
||||
modelState.AddModelError(currentModelStateKey, validationResult.Message);
|
||||
modelState.TryAddModelError(currentModelStateKey, validationResult.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,6 +144,30 @@ namespace Microsoft.AspNet.Mvc
|
|||
actionContext.ModelState["Age"].Errors[0].Exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadAsync_UsesTryAddModelValidationErrorsToModelState_WhenCaptureErrorsIsSet()
|
||||
{
|
||||
// Arrange
|
||||
var content = "{name: 'Person Name', Age: 'not-an-age'}";
|
||||
var formatter = new JsonInputFormatter { CaptureDeserilizationErrors = true };
|
||||
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||
|
||||
var actionContext = GetActionContext(contentBytes);
|
||||
var metadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(User));
|
||||
var context = new InputFormatterContext(actionContext, metadata.ModelType);
|
||||
actionContext.ModelState.MaxAllowedErrors = 3;
|
||||
actionContext.ModelState.AddModelError("key1", "error1");
|
||||
actionContext.ModelState.AddModelError("key2", "error2");
|
||||
|
||||
// Act
|
||||
var model = await formatter.ReadAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.False(actionContext.ModelState.ContainsKey("age"));
|
||||
var error = Assert.Single(actionContext.ModelState[""].Errors);
|
||||
Assert.IsType<TooManyModelErrorsException>(error.Exception);
|
||||
}
|
||||
|
||||
private static ActionContext GetActionContext(byte[] contentBytes,
|
||||
string contentType = "application/xml")
|
||||
{
|
||||
|
|
|
|||
|
|
@ -16,8 +16,19 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
|||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<ArgumentNullException>(() => options.AntiForgeryOptions = null);
|
||||
Assert.Equal("The 'AntiForgeryOptions' property of 'Microsoft.AspNet.Mvc.MvcOptions' must not be null." +
|
||||
Assert.Equal("The 'AntiForgeryOptions' property of 'Microsoft.AspNet.Mvc.MvcOptions' must not be null." +
|
||||
"\r\nParameter name: value", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MaxValidationError_ThrowsIfValueIsOutOfRange()
|
||||
{
|
||||
// Arrange
|
||||
var options = new MvcOptions();
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => options.MaxModelValidationErrors = -1);
|
||||
Assert.Equal("value", ex.ParamName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ using Microsoft.AspNet.Mvc.Logging;
|
|||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -18,7 +19,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
public class MvcRouteHandlerTests
|
||||
{
|
||||
[Fact]
|
||||
public async void RouteAsync_Success_LogsCorrectValues()
|
||||
public async Task RouteAsync_Success_LogsCorrectValues()
|
||||
{
|
||||
// Arrange
|
||||
var sink = new TestSink();
|
||||
|
|
@ -56,7 +57,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async void RouteAsync_FailOnNoAction_LogsCorrectValues()
|
||||
public async Task RouteAsync_FailOnNoAction_LogsCorrectValues()
|
||||
{
|
||||
// Arrange
|
||||
var sink = new TestSink();
|
||||
|
|
@ -100,7 +101,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async void RouteAsync_FailOnNoInvoker_LogsCorrectValues()
|
||||
public async Task RouteAsync_FailOnNoInvoker_LogsCorrectValues()
|
||||
{
|
||||
// Arrange
|
||||
var sink = new TestSink();
|
||||
|
|
@ -144,10 +145,47 @@ namespace Microsoft.AspNet.Mvc
|
|||
Assert.Equal(false, values.Handled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RouteAsync_SetsMaxErrorCountOnModelStateDictionary()
|
||||
{
|
||||
// Arrange
|
||||
var expected = 199;
|
||||
var optionsAccessor = new Mock<IOptionsAccessor<MvcOptions>>();
|
||||
var options = new MvcOptions
|
||||
{
|
||||
MaxModelValidationErrors = expected
|
||||
};
|
||||
optionsAccessor.SetupGet(o => o.Options)
|
||||
.Returns(options);
|
||||
|
||||
var invoked = false;
|
||||
var mockInvokerFactory = new Mock<IActionInvokerFactory>();
|
||||
mockInvokerFactory.Setup(f => f.CreateInvoker(It.IsAny<ActionContext>()))
|
||||
.Callback<ActionContext>(c =>
|
||||
{
|
||||
Assert.Equal(expected, c.ModelState.MaxAllowedErrors);
|
||||
invoked = true;
|
||||
})
|
||||
.Returns(Mock.Of<IActionInvoker>());
|
||||
|
||||
var context = CreateRouteContext(
|
||||
invokerFactory: mockInvokerFactory.Object,
|
||||
optionsAccessor: optionsAccessor.Object);
|
||||
|
||||
var handler = new MvcRouteHandler();
|
||||
|
||||
// Act
|
||||
await handler.RouteAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.True(invoked);
|
||||
}
|
||||
|
||||
private RouteContext CreateRouteContext(
|
||||
IActionSelector actionSelector = null,
|
||||
IActionInvokerFactory invokerFactory = null,
|
||||
ILoggerFactory loggerFactory = null)
|
||||
ILoggerFactory loggerFactory = null,
|
||||
IOptionsAccessor<MvcOptions> optionsAccessor = null)
|
||||
{
|
||||
var mockContextAccessor = new Mock<IContextAccessor<ActionContext>>();
|
||||
mockContextAccessor.Setup(c => c.SetContextSource(
|
||||
|
|
@ -185,6 +223,15 @@ namespace Microsoft.AspNet.Mvc
|
|||
loggerFactory = NullLoggerFactory.Instance;
|
||||
}
|
||||
|
||||
if (optionsAccessor == null)
|
||||
{
|
||||
var mockOptionsAccessor = new Mock<IOptionsAccessor<MvcOptions>>();
|
||||
mockOptionsAccessor.SetupGet(o => o.Options)
|
||||
.Returns(new MvcOptions());
|
||||
|
||||
optionsAccessor = mockOptionsAccessor.Object;
|
||||
}
|
||||
|
||||
var httpContext = new Mock<HttpContext>();
|
||||
httpContext.Setup(h => h.RequestServices.GetService(typeof(IContextAccessor<ActionContext>)))
|
||||
.Returns(mockContextAccessor.Object);
|
||||
|
|
@ -196,6 +243,8 @@ namespace Microsoft.AspNet.Mvc
|
|||
.Returns(loggerFactory);
|
||||
httpContext.Setup(h => h.RequestServices.GetService(typeof(IEnumerable<MvcMarkerService>)))
|
||||
.Returns(new List<MvcMarkerService> { new MvcMarkerService() });
|
||||
httpContext.Setup(h => h.RequestServices.GetService(typeof(IOptionsAccessor<MvcOptions>)))
|
||||
.Returns(optionsAccessor);
|
||||
|
||||
return new RouteContext(httpContext.Object);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,13 @@
|
|||
// 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.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.TestHost;
|
||||
using Newtonsoft.Json;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||
|
|
@ -44,5 +47,48 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("\0", await response.Content.ReadAsStringAsync());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ModelBinding_LimitsErrorsToMaxErrorCount()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
var queryString = string.Join("=&", Enumerable.Range(0, 10).Select(i => "field" + i));
|
||||
|
||||
// Act
|
||||
var response = await client.GetStringAsync("http://localhost/Home/ModelWithTooManyValidationErrors?" + queryString);
|
||||
|
||||
//Assert
|
||||
var json = JsonConvert.DeserializeObject<Dictionary<string, string>>(response);
|
||||
// 8 is the value of MaxModelValidationErrors for the application being tested.
|
||||
Assert.Equal(8, json.Count);
|
||||
Assert.Equal("The Field1 field is required.", json["Field1.Field1"]);
|
||||
Assert.Equal("The Field2 field is required.", json["Field1.Field2"]);
|
||||
Assert.Equal("The Field3 field is required.", json["Field1.Field3"]);
|
||||
Assert.Equal("The Field1 field is required.", json["Field2.Field1"]);
|
||||
Assert.Equal("The Field2 field is required.", json["Field2.Field2"]);
|
||||
Assert.Equal("The Field3 field is required.", json["Field2.Field3"]);
|
||||
Assert.Equal("The Field1 field is required.", json["Field3.Field1"]);
|
||||
Assert.Equal("The maximum number of allowed model errors has been reached.", json[""]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ModelBinding_ValidatesAllPropertiesInModel()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
// Act
|
||||
var response = await client.GetStringAsync("http://localhost/Home/ModelWithFewValidationErrors?model=");
|
||||
|
||||
//Assert
|
||||
var json = JsonConvert.DeserializeObject<Dictionary<string, string>>(response);
|
||||
Assert.Equal(3, json.Count);
|
||||
Assert.Equal("The Field1 field is required.", json["model.Field1"]);
|
||||
Assert.Equal("The Field2 field is required.", json["model.Field2"]);
|
||||
Assert.Equal("The Field3 field is required.", json["model.Field3"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -266,6 +266,37 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
Assert.Equal("Password does not meet complexity requirements.", error.ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModel_UsesTryAddModelError()
|
||||
{
|
||||
// Arrange
|
||||
var validatorProvider = new DataAnnotationsModelValidatorProvider();
|
||||
var binder = CreateBinderWithDefaults();
|
||||
var valueProvider = new SimpleHttpValueProvider
|
||||
{
|
||||
{ "user.password", "password" },
|
||||
{ "user.confirmpassword", "password2" },
|
||||
};
|
||||
var bindingContext = CreateBindingContext(binder, valueProvider, typeof(User), validatorProvider);
|
||||
bindingContext.ModelState.MaxAllowedErrors = 2;
|
||||
bindingContext.ModelState.AddModelError("key1", "error1");
|
||||
bindingContext.ModelName = "user";
|
||||
|
||||
// Act
|
||||
await binder.BindModelAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
var modelState = bindingContext.ModelState["user.confirmpassword"];
|
||||
Assert.Empty(modelState.Errors);
|
||||
|
||||
modelState = bindingContext.ModelState["user"];
|
||||
Assert.Empty(modelState.Errors);
|
||||
|
||||
var error = Assert.Single(bindingContext.ModelState[""].Errors);
|
||||
Assert.IsType<TooManyModelErrorsException>(error.Exception);
|
||||
}
|
||||
|
||||
|
||||
private static ModelBindingContext CreateBindingContext(IModelBinder binder,
|
||||
IValueProvider valueProvider,
|
||||
Type type,
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var target = new ModelStateDictionary(source);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, target.ErrorCount);
|
||||
Assert.Equal(1, target.Count);
|
||||
Assert.Same(modelState, target["key"]);
|
||||
Assert.IsType<CopyOnWriteDictionary<string, ModelState>>(target.InnerDictionary);
|
||||
|
|
@ -41,6 +42,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
dictionary.AddModelError("some key", "some error");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, dictionary.ErrorCount);
|
||||
var kvp = Assert.Single(dictionary);
|
||||
Assert.Equal("some key", kvp.Key);
|
||||
var error = Assert.Single(kvp.Value.Errors);
|
||||
|
|
@ -59,6 +61,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
dictionary.AddModelError("some key", ex);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, dictionary.ErrorCount);
|
||||
var kvp = Assert.Single(dictionary);
|
||||
Assert.Equal("some key", kvp.Key);
|
||||
|
||||
|
|
@ -365,6 +368,137 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Assert.Equal(ModelValidationState.Valid, validationState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddModelError_WithErrorString_AddsTooManyModelErrors_WhenMaxErrorsIsReached()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "The maximum number of allowed model errors has been reached.";
|
||||
var dictionary = new ModelStateDictionary
|
||||
{
|
||||
MaxAllowedErrors = 5
|
||||
};
|
||||
dictionary.AddModelError("key1", "error1");
|
||||
dictionary.AddModelError("key2", new Exception());
|
||||
dictionary.AddModelError("key3", new Exception());
|
||||
dictionary.AddModelError("key4", "error4");
|
||||
dictionary.AddModelError("key5", "error5");
|
||||
dictionary.AddModelError("key6", "error6");
|
||||
|
||||
// Act and Assert
|
||||
Assert.False(dictionary.CanAddErrors);
|
||||
Assert.Equal(5, dictionary.ErrorCount);
|
||||
var error = Assert.Single(dictionary[""].Errors);
|
||||
Assert.IsType<TooManyModelErrorsException>(error.Exception);
|
||||
Assert.Equal(expected, error.Exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryAddModelError_WithErrorString_ReturnsFalse_AndAddsMaxModelErrorMessage()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "The maximum number of allowed model errors has been reached.";
|
||||
var dictionary = new ModelStateDictionary
|
||||
{
|
||||
MaxAllowedErrors = 3
|
||||
};
|
||||
|
||||
// Act and Assert
|
||||
var result = dictionary.TryAddModelError("key1", "error1");
|
||||
Assert.True(result);
|
||||
|
||||
result = dictionary.TryAddModelError("key2", new Exception());
|
||||
Assert.True(result);
|
||||
|
||||
result = dictionary.TryAddModelError("key3", "error3");
|
||||
Assert.False(result);
|
||||
|
||||
result = dictionary.TryAddModelError("key4", "error4");
|
||||
Assert.False(result);
|
||||
|
||||
Assert.False(dictionary.CanAddErrors);
|
||||
Assert.Equal(3, dictionary.ErrorCount);
|
||||
Assert.Equal(3, dictionary.Count);
|
||||
|
||||
var error = Assert.Single(dictionary[""].Errors);
|
||||
Assert.IsType<TooManyModelErrorsException>(error.Exception);
|
||||
Assert.Equal(expected, error.Exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddModelError_WithException_AddsTooManyModelError_WhenMaxErrorIsReached()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "The maximum number of allowed model errors has been reached.";
|
||||
var dictionary = new ModelStateDictionary
|
||||
{
|
||||
MaxAllowedErrors = 4
|
||||
};
|
||||
dictionary.AddModelError("key1", new Exception());
|
||||
dictionary.AddModelError("key2", "error2");
|
||||
dictionary.AddModelError("key3", "error3");
|
||||
dictionary.AddModelError("key3", new Exception());
|
||||
dictionary.AddModelError("key4", new InvalidOperationException());
|
||||
dictionary.AddModelError("key5", new FormatException());
|
||||
|
||||
// Act and Assert
|
||||
Assert.False(dictionary.CanAddErrors);
|
||||
Assert.Equal(4, dictionary.ErrorCount);
|
||||
Assert.Equal(4, dictionary.Count);
|
||||
var error = Assert.Single(dictionary[""].Errors);
|
||||
Assert.IsType<TooManyModelErrorsException>(error.Exception);
|
||||
Assert.Equal(expected, error.Exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryAddModelError_WithException_ReturnsFalse_AndAddsMaxModelErrorMessage()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "The maximum number of allowed model errors has been reached.";
|
||||
var dictionary = new ModelStateDictionary
|
||||
{
|
||||
MaxAllowedErrors = 3
|
||||
};
|
||||
|
||||
// Act and Assert
|
||||
var result = dictionary.TryAddModelError("key1", "error1");
|
||||
Assert.True(result);
|
||||
|
||||
result = dictionary.TryAddModelError("key2", new Exception());
|
||||
Assert.True(result);
|
||||
|
||||
result = dictionary.TryAddModelError("key3", new Exception());
|
||||
Assert.False(result);
|
||||
|
||||
Assert.Equal(3, dictionary.Count);
|
||||
var error = Assert.Single(dictionary[""].Errors);
|
||||
Assert.IsType<TooManyModelErrorsException>(error.Exception);
|
||||
Assert.Equal(expected, error.Exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModelStateDictionary_TracksAddedErrorsOverCopyConstructor()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "The maximum number of allowed model errors has been reached.";
|
||||
var dictionary = new ModelStateDictionary
|
||||
{
|
||||
MaxAllowedErrors = 3
|
||||
};
|
||||
|
||||
// Act
|
||||
dictionary.AddModelError("key1", "error1");
|
||||
dictionary.TryAddModelError("key3", new Exception());
|
||||
|
||||
var copy = new ModelStateDictionary(dictionary);
|
||||
copy.AddModelError("key2", "error2");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(3, copy.Count);
|
||||
var error = Assert.Single(copy[""].Errors);
|
||||
Assert.IsType<TooManyModelErrorsException>(error.Exception);
|
||||
Assert.Equal(expected, error.Exception.Message);
|
||||
}
|
||||
|
||||
private static ValueProviderResult GetValueProviderResult(object rawValue = null, string attemptedValue = null)
|
||||
{
|
||||
return new ValueProviderResult(rawValue ?? "some value",
|
||||
|
|
|
|||
|
|
@ -278,6 +278,39 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Assert.False(context.ModelState.ContainsKey("theKey"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[ReplaceCulture]
|
||||
public void Validate_ShortCircuits_IfModelStateHasReachedMaxNumberOfErrors()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ValidateAllPropertiesModel
|
||||
{
|
||||
RequiredString = null /* error */,
|
||||
RangedInt = 0 /* error */,
|
||||
ValidString = "cat" /* error */
|
||||
};
|
||||
|
||||
var modelMetadata = GetModelMetadata(model);
|
||||
var node = new ModelValidationNode(modelMetadata, "theKey")
|
||||
{
|
||||
ValidateAllProperties = true
|
||||
};
|
||||
var context = CreateContext(modelMetadata);
|
||||
context.ModelState.MaxAllowedErrors = 3;
|
||||
context.ModelState.AddModelError("somekey", "error text");
|
||||
|
||||
// Act
|
||||
node.Validate(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(3, context.ModelState.Count);
|
||||
Assert.IsType<TooManyModelErrorsException>(context.ModelState[""].Errors[0].Exception);
|
||||
Assert.Equal("The RequiredString field is required.",
|
||||
context.ModelState["theKey.RequiredString"].Errors[0].ErrorMessage);
|
||||
Assert.False(context.ModelState.ContainsKey("theKey.RangedInt"));
|
||||
Assert.False(context.ModelState.ContainsKey("theKey.ValidString"));
|
||||
}
|
||||
|
||||
private static ModelMetadata GetModelMetadata()
|
||||
{
|
||||
return new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(object));
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using System.Linq;
|
||||
using ModelBindingWebSite.Models;
|
||||
|
||||
namespace ModelBindingWebSite.Controllers
|
||||
{
|
||||
|
|
@ -12,5 +15,32 @@ namespace ModelBindingWebSite.Controllers
|
|||
{
|
||||
return Content(System.Text.Encoding.UTF8.GetString(byteValues));
|
||||
}
|
||||
|
||||
public object ModelWithTooManyValidationErrors(LargeModelWithValidation model)
|
||||
{
|
||||
return CreateValidationDictionary();
|
||||
}
|
||||
|
||||
public object ModelWithFewValidationErrors(ModelWithValidation model)
|
||||
{
|
||||
return CreateValidationDictionary();
|
||||
}
|
||||
|
||||
private Dictionary<string, string> CreateValidationDictionary()
|
||||
{
|
||||
var result = new Dictionary<string, string>();
|
||||
foreach (var item in ModelState)
|
||||
{
|
||||
var error = item.Value.Errors.SingleOrDefault();
|
||||
if (error != null)
|
||||
{
|
||||
var value = error.Exception != null ? error.Exception.Message :
|
||||
error.ErrorMessage;
|
||||
result.Add(item.Key, value);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace ModelBindingWebSite.Models
|
||||
{
|
||||
public class LargeModelWithValidation
|
||||
{
|
||||
[Required]
|
||||
public ModelWithValidation Field1 { get; set; }
|
||||
|
||||
[Required]
|
||||
public ModelWithValidation Field2 { get; set; }
|
||||
|
||||
[Required]
|
||||
public ModelWithValidation Field3 { get; set; }
|
||||
|
||||
[Required]
|
||||
public ModelWithValidation Field4 { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace ModelBindingWebSite
|
||||
{
|
||||
public class ModelWithValidation
|
||||
{
|
||||
[Required]
|
||||
public string Field1 { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Field2 { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Field3 { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
|
|
@ -17,7 +18,11 @@ namespace ModelBindingWebSite
|
|||
app.UseServices(services =>
|
||||
{
|
||||
// Add MVC services to the services container
|
||||
services.AddMvc(configuration);
|
||||
services.AddMvc(configuration)
|
||||
.SetupOptions<MvcOptions>(m =>
|
||||
{
|
||||
m.MaxModelValidationErrors = 8;
|
||||
});
|
||||
});
|
||||
|
||||
// Add MVC to the request pipeline
|
||||
|
|
|
|||
Loading…
Reference in New Issue