Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Commit 646c0d7

Browse files
committed
Limit the maximum number of Model errors to a reasonable value.
Fixes #490
1 parent 9befa6e commit 646c0d7

File tree

22 files changed

+680
-36
lines changed

22 files changed

+680
-36
lines changed

src/Microsoft.AspNet.Mvc.Core/Formatters/JsonInputFormatter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ private Task<object> ReadInternal(InputFormatterContext context,
148148
errorHandler = (sender, e) =>
149149
{
150150
var exception = e.ErrorContext.Error;
151-
context.ActionContext.ModelState.AddModelError(e.ErrorContext.Path, e.ErrorContext.Error);
151+
context.ActionContext.ModelState.TryAddModelError(e.ErrorContext.Path, e.ErrorContext.Error);
152152
// Error must always be marked as handled
153153
// Failure to do so can cause the exception to be rethrown at every recursive level and
154154
// overflow the stack for x64 CLR processes

src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public class MvcOptions
1717
{
1818
private AntiForgeryOptions _antiForgeryOptions = new AntiForgeryOptions();
1919
private RazorViewEngineOptions _viewEngineOptions = new RazorViewEngineOptions();
20+
private int _maxModelStateErrors = 200;
2021

2122
public MvcOptions()
2223
{
@@ -94,6 +95,25 @@ public RazorViewEngineOptions ViewEngineOptions
9495
}
9596

9697
/// <summary>
98+
/// Gets or sets the maximum number of validation errors that are allowed by this application before further
99+
/// errors are ignored.
100+
/// </summary>
101+
public int MaxModelValidationErrors
102+
{
103+
get { return _maxModelStateErrors; }
104+
set
105+
{
106+
if (value < 0)
107+
{
108+
throw new ArgumentOutOfRangeException(nameof(value));
109+
}
110+
111+
_maxModelStateErrors = value;
112+
}
113+
}
114+
115+
/// <summary>
116+
/// Get a list of the <see cref="ModelBinderDescriptor" /> used by the
97117
/// Gets a list of the <see cref="ModelBinderDescriptor" /> used by the
98118
/// <see cref="ModelBinding.CompositeModelBinder" />.
99119
/// </summary>

src/Microsoft.AspNet.Mvc.Core/MvcRouteHandler.cs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using Microsoft.AspNet.Routing;
1212
using Microsoft.Framework.DependencyInjection;
1313
using Microsoft.Framework.Logging;
14+
using Microsoft.Framework.OptionsModel;
1415

1516
namespace Microsoft.AspNet.Mvc
1617
{
@@ -32,7 +33,7 @@ public string GetVirtualPath([NotNull] VirtualPathContext context)
3233
public async Task RouteAsync([NotNull] RouteContext context)
3334
{
3435
var services = context.HttpContext.RequestServices;
35-
36+
3637
// Verify if AddMvc was done before calling UseMvc
3738
// We use the MvcMarkerService to make sure if all the services were added.
3839
MvcServicesHelper.ThrowIfMvcNotRegistered(services);
@@ -64,19 +65,22 @@ public async Task RouteAsync([NotNull] RouteContext context)
6465
return;
6566
}
6667

67-
if (actionDescriptor.RouteValueDefaults != null)
68-
{
69-
foreach (var kvp in actionDescriptor.RouteValueDefaults)
68+
if (actionDescriptor.RouteValueDefaults != null)
7069
{
71-
if (!context.RouteData.Values.ContainsKey(kvp.Key))
70+
foreach (var kvp in actionDescriptor.RouteValueDefaults)
7271
{
73-
context.RouteData.Values.Add(kvp.Key, kvp.Value);
72+
if (!context.RouteData.Values.ContainsKey(kvp.Key))
73+
{
74+
context.RouteData.Values.Add(kvp.Key, kvp.Value);
75+
}
7476
}
7577
}
76-
}
7778

7879
var actionContext = new ActionContext(context.HttpContext, context.RouteData, actionDescriptor);
7980

81+
var optionsAccessor = services.GetService<IOptionsAccessor<MvcOptions>>();
82+
actionContext.ModelState.MaxAllowedErrors = optionsAccessor.Options.MaxModelValidationErrors;
83+
8084
var contextAccessor = services.GetService<IContextAccessor<ActionContext>>();
8185
using (contextAccessor.SetContextSource(() => actionContext, PreventExchange))
8286
{

src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,7 @@ public virtual async Task<bool> BindModelAsync([NotNull] ModelBindingContext bin
6565

6666
// Only perform validation at the root of the object graph. ValidationNode will recursively walk the graph.
6767
// Ignore ComplexModelDto since it essentially wraps the primary object.
68-
if (newBindingContext.ModelMetadata.ContainerType == null &&
69-
newBindingContext.ModelMetadata.ModelType != typeof(ComplexModelDto))
68+
if (IsBindingAtRootOfObjectGraph(newBindingContext))
7069
{
7170
// run validation and return the model
7271
// If we fell back to an empty prefix above and are dealing with simple types,
@@ -92,7 +91,7 @@ public virtual async Task<bool> BindModelAsync([NotNull] ModelBindingContext bin
9291
return true;
9392
}
9493

95-
private async Task<bool> TryBind([NotNull] ModelBindingContext bindingContext)
94+
private async Task<bool> TryBind(ModelBindingContext bindingContext)
9695
{
9796
// TODO: RuntimeHelpers.EnsureSufficientExecutionStack does not exist in the CoreCLR.
9897
// Protects against stack overflow for deeply nested model binding
@@ -110,6 +109,16 @@ private async Task<bool> TryBind([NotNull] ModelBindingContext bindingContext)
110109
return false;
111110
}
112111

112+
private static bool IsBindingAtRootOfObjectGraph(ModelBindingContext bindingContext)
113+
{
114+
// We're at the root of the object graph if the model does does not have a container.
115+
// This statement is true for complex types at the root twice over - once with the actual model
116+
// and once when when it is represented by a ComplexModelDto. Ignore the latter case.
117+
118+
return bindingContext.ModelMetadata.ContainerType == null &&
119+
bindingContext.ModelMetadata.ModelType != typeof(ComplexModelDto);
120+
}
121+
113122
private static ModelBindingContext CreateNewBindingContext(ModelBindingContext oldBindingContext,
114123
string modelName,
115124
bool reuseValidationNode)

src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,10 @@ internal static EventHandler<ModelValidatedEventArgs> CreateNullCheckFailedHandl
127127
// var errorMessage = ModelBinderConfig.ValueRequiredErrorMessageProvider(e.ValidationContext,
128128
// modelMetadata,
129129
// incomingValue);
130-
var errorMessage = "A value is required.";
130+
var errorMessage = Resources.ModelBinderConfig_ValueRequired;
131131
if (errorMessage != null)
132132
{
133-
modelState.AddModelError(validationNode.ModelStateKey, errorMessage);
133+
modelState.TryAddModelError(validationNode.ModelStateKey, errorMessage);
134134
}
135135
}
136136
};
@@ -232,7 +232,7 @@ internal void ProcessDto(ModelBindingContext bindingContext, ComplexModelDto dto
232232
// (oddly) succeeded.
233233
if (!addedError)
234234
{
235-
bindingContext.ModelState.AddModelError(
235+
bindingContext.ModelState.TryAddModelError(
236236
modelStateKey,
237237
Resources.FormatMissingRequiredMember(missingRequiredProperty));
238238
}
@@ -285,7 +285,7 @@ protected virtual void SetProperty(ModelBindingContext bindingContext,
285285
var validationContext = new ModelValidationContext(bindingContext, propertyMetadata);
286286
foreach (var validationResult in requiredValidator.Validate(validationContext))
287287
{
288-
bindingContext.ModelState.AddModelError(modelStateKey, validationResult.Message);
288+
bindingContext.ModelState.TryAddModelError(modelStateKey, validationResult.Message);
289289
}
290290
}
291291
}
@@ -337,7 +337,7 @@ private static bool RunValidator(IModelValidator validator,
337337
var addedError = false;
338338
foreach (var validationResult in validator.Validate(validationContext))
339339
{
340-
bindingContext.ModelState.AddModelError(modelStateKey, validationResult.Message);
340+
bindingContext.ModelState.TryAddModelError(modelStateKey, validationResult.Message);
341341
addedError = true;
342342
}
343343

src/Microsoft.AspNet.Mvc.ModelBinding/Internal/ModelBindingHelper.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,11 @@ internal static void AddModelErrorBasedOnExceptionType(ModelBindingContext bindi
9898
{
9999
if (IsFormatException(ex))
100100
{
101-
bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex.Message);
101+
bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, ex.Message);
102102
}
103103
else
104104
{
105-
bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex);
105+
bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, ex);
106106
}
107107
}
108108

0 commit comments

Comments
 (0)