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

Commit 8d1c85a

Browse files
authored
Add overload for AddModelError without ModelMetadata (#7407)
Addresses #6102
1 parent e35d0bc commit 8d1c85a

File tree

4 files changed

+277
-12
lines changed

4 files changed

+277
-12
lines changed

src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelStateDictionary.cs

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,54 @@ public ModelStateEntry this[string key]
174174

175175
/// <summary>
176176
/// Adds the specified <paramref name="exception"/> to the <see cref="ModelStateEntry.Errors"/> instance
177-
/// that is associated with the specified <paramref name="key"/>.
177+
/// that is associated with the specified <paramref name="key"/>. If the maximum number of allowed
178+
/// errors has already been recorded, ensures that a <see cref="TooManyModelErrorsException"/> exception is
179+
/// recorded instead.
178180
/// </summary>
181+
/// <remarks>
182+
/// This method allows adding the <paramref name="exception"/> to the current <see cref="ModelStateDictionary"/>
183+
/// when <see cref="ModelMetadata"/> is not available or the exact <paramref name="exception"/>
184+
/// must be maintained for later use (even if it is for example a <see cref="FormatException"/>).
185+
/// Where <see cref="ModelMetadata"/> is available, use <see cref="AddModelError(string, Exception, ModelMetadata)"/> instead.
186+
/// </remarks>
179187
/// <param name="key">The key of the <see cref="ModelStateEntry"/> to add errors to.</param>
180188
/// <param name="exception">The <see cref="Exception"/> to add.</param>
189+
/// <returns>
190+
/// <c>True</c> if the given error was added, <c>false</c> if the error was ignored.
191+
/// See <see cref="MaxAllowedErrors"/>.
192+
/// </returns>
193+
public bool TryAddModelException(string key, Exception exception)
194+
{
195+
if (key == null)
196+
{
197+
throw new ArgumentNullException(nameof(key));
198+
}
199+
200+
if (exception == null)
201+
{
202+
throw new ArgumentNullException(nameof(exception));
203+
}
204+
205+
if (ErrorCount >= MaxAllowedErrors - 1)
206+
{
207+
EnsureMaxErrorsReachedRecorded();
208+
return false;
209+
}
210+
211+
ErrorCount++;
212+
AddModelErrorCore(key, exception);
213+
return true;
214+
}
215+
216+
/// <summary>
217+
/// Adds the specified <paramref name="exception"/> to the <see cref="ModelStateEntry.Errors"/> instance
218+
/// that is associated with the specified <paramref name="key"/>. If the maximum number of allowed
219+
/// errors has already been recorded, ensures that a <see cref="TooManyModelErrorsException"/> exception is
220+
/// recorded instead.
221+
/// </summary>
222+
/// <param name="key">The key of the <see cref="ModelStateEntry"/> to add errors to.</param>
223+
/// <param name="exception">The <see cref="Exception"/> to add. Some exception types will be replaced with
224+
/// a descriptive error message.</param>
181225
/// <param name="metadata">The <see cref="ModelMetadata"/> associated with the model.</param>
182226
public void AddModelError(string key, Exception exception, ModelMetadata metadata)
183227
{
@@ -202,10 +246,12 @@ public void AddModelError(string key, Exception exception, ModelMetadata metadat
202246
/// <summary>
203247
/// Attempts to add the specified <paramref name="exception"/> to the <see cref="ModelStateEntry.Errors"/>
204248
/// instance that is associated with the specified <paramref name="key"/>. If the maximum number of allowed
205-
/// errors has already been recorded, records a <see cref="TooManyModelErrorsException"/> exception instead.
249+
/// errors has already been recorded, ensures that a <see cref="TooManyModelErrorsException"/> exception is
250+
/// recorded instead.
206251
/// </summary>
207252
/// <param name="key">The key of the <see cref="ModelStateEntry"/> to add errors to.</param>
208-
/// <param name="exception">The <see cref="Exception"/> to add.</param>
253+
/// <param name="exception">The <see cref="Exception"/> to add. Some exception types will be replaced with
254+
/// a descriptive error message.</param>
209255
/// <param name="metadata">The <see cref="ModelMetadata"/> associated with the model.</param>
210256
/// <returns>
211257
/// <c>True</c> if the given error was added, <c>false</c> if the error was ignored.
@@ -277,7 +323,9 @@ public bool TryAddModelError(string key, Exception exception, ModelMetadata meta
277323

278324
/// <summary>
279325
/// Adds the specified <paramref name="errorMessage"/> to the <see cref="ModelStateEntry.Errors"/> instance
280-
/// that is associated with the specified <paramref name="key"/>.
326+
/// that is associated with the specified <paramref name="key"/>. If the maximum number of allowed
327+
/// errors has already been recorded, ensures that a <see cref="TooManyModelErrorsException"/> exception is
328+
/// recorded instead.
281329
/// </summary>
282330
/// <param name="key">The key of the <see cref="ModelStateEntry"/> to add errors to.</param>
283331
/// <param name="errorMessage">The error message to add.</param>
@@ -299,7 +347,8 @@ public void AddModelError(string key, string errorMessage)
299347
/// <summary>
300348
/// Attempts to add the specified <paramref name="errorMessage"/> to the <see cref="ModelStateEntry.Errors"/>
301349
/// instance that is associated with the specified <paramref name="key"/>. If the maximum number of allowed
302-
/// errors has already been recorded, records a <see cref="TooManyModelErrorsException"/> exception instead.
350+
/// errors has already been recorded, ensures that a <see cref="TooManyModelErrorsException"/> exception is
351+
/// recorded instead.
303352
/// </summary>
304353
/// <param name="key">The key of the <see cref="ModelStateEntry"/> to add errors to.</param>
305354
/// <param name="errorMessage">The error message to add.</param>

src/Microsoft.AspNetCore.Mvc.ViewFeatures/ModelStateDictionaryExtensions.cs

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ public static class ModelStateDictionaryExtensions
1515
{
1616
/// <summary>
1717
/// Adds the specified <paramref name="errorMessage"/> to the <see cref="ModelStateEntry.Errors"/> instance
18-
/// that is associated with the specified <paramref name="expression"/>.
18+
/// that is associated with the specified <paramref name="expression"/>. If the maximum number of allowed
19+
/// errors has already been recorded, ensures that a <see cref="TooManyModelErrorsException"/> exception is
20+
/// recorded instead.
1921
/// </summary>
2022
/// <typeparam name="TModel">The type of the model.</typeparam>
2123
/// <param name="modelState">The <see cref="ModelStateDictionary"/> instance this method extends.</param>
@@ -46,7 +48,42 @@ public static void AddModelError<TModel>(
4648

4749
/// <summary>
4850
/// Adds the specified <paramref name="exception"/> to the <see cref="ModelStateEntry.Errors"/> instance
49-
/// that is associated with the specified <paramref name="expression"/>.
51+
/// that is associated with the specified <paramref name="expression"/>. If the maximum number of allowed
52+
/// errors has already been recorded, ensures that a <see cref="TooManyModelErrorsException"/> exception is
53+
/// recorded instead.
54+
/// </summary>
55+
/// <remarks>
56+
/// This method allows adding the <paramref name="exception"/> to the current <see cref="ModelStateDictionary"/>
57+
/// when <see cref="ModelMetadata"/> is not available or the exact <paramref name="exception"/>
58+
/// must be maintained for later use (even if it is for example a <see cref="FormatException"/>).
59+
/// </remarks>
60+
/// <typeparam name="TModel">The type of the model.</typeparam>
61+
/// <param name="modelState">The <see cref="ModelStateDictionary"/> instance this method extends.</param>
62+
/// <param name="expression">An expression to be evaluated against an item in the current model.</param>
63+
/// <param name="exception">The <see cref="Exception"/> to add.</param>
64+
public static void TryAddModelException<TModel>(
65+
this ModelStateDictionary modelState,
66+
Expression<Func<TModel, object>> expression,
67+
Exception exception)
68+
{
69+
if (modelState == null)
70+
{
71+
throw new ArgumentNullException(nameof(modelState));
72+
}
73+
74+
if (expression == null)
75+
{
76+
throw new ArgumentNullException(nameof(expression));
77+
}
78+
79+
modelState.TryAddModelException(GetExpressionText(expression), exception);
80+
}
81+
82+
/// <summary>
83+
/// Adds the specified <paramref name="exception"/> to the <see cref="ModelStateEntry.Errors"/> instance
84+
/// that is associated with the specified <paramref name="expression"/>. If the maximum number of allowed
85+
/// errors has already been recorded, ensures that a <see cref="TooManyModelErrorsException"/> exception is
86+
/// recorded instead.
5087
/// </summary>
5188
/// <typeparam name="TModel">The type of the model.</typeparam>
5289
/// <param name="modelState">The <see cref="ModelStateDictionary"/> instance this method extends.</param>

test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/ModelStateDictionaryTest.cs

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,23 @@ public void CopyConstructor_CopiesModelStateData()
285285
Assert.Equal(entry.ValidationState, actual.ValidationState);
286286
}
287287

288+
[Fact]
289+
public void TryAddModelException_Succeeds()
290+
{
291+
// Arrange
292+
var dictionary = new ModelStateDictionary();
293+
var exception = new TestException();
294+
295+
// Act
296+
dictionary.TryAddModelException("some key", exception);
297+
298+
// Assert
299+
var kvp = Assert.Single(dictionary);
300+
Assert.Equal("some key", kvp.Key);
301+
var error = Assert.Single(kvp.Value.Errors);
302+
Assert.Same(exception, error.Exception);
303+
}
304+
288305
[Fact]
289306
public void AddModelErrorCreatesModelStateIfNotPresent()
290307
{
@@ -695,13 +712,15 @@ public void AddModelError_WithErrorString_AddsTooManyModelErrors_WhenMaxErrorsIs
695712
};
696713
var provider = new EmptyModelMetadataProvider();
697714
var metadata = provider.GetMetadataForProperty(typeof(string), nameof(string.Length));
715+
716+
// Act
698717
dictionary.AddModelError("key1", "error1");
699718
dictionary.AddModelError("key2", new Exception(), metadata);
700719
dictionary.AddModelError("key3", new Exception(), metadata);
701720
dictionary.AddModelError("key4", "error4");
702721
dictionary.AddModelError("key5", "error5");
703722

704-
// Act and Assert
723+
// Assert
705724
Assert.True(dictionary.HasReachedMaxErrors);
706725
Assert.Equal(5, dictionary.ErrorCount);
707726
var error = Assert.Single(dictionary[string.Empty].Errors);
@@ -712,6 +731,35 @@ public void AddModelError_WithErrorString_AddsTooManyModelErrors_WhenMaxErrorsIs
712731
Assert.DoesNotContain("key5", dictionary.Keys);
713732
}
714733

734+
[Fact]
735+
public void TryAddModelException_ReturnsFalse_AndAddsMaxModelErrorMessage()
736+
{
737+
// Arrange
738+
var expected = "The maximum number of allowed model errors has been reached.";
739+
var dictionary = new ModelStateDictionary
740+
{
741+
MaxAllowedErrors = 3
742+
};
743+
744+
// Act and Assert
745+
var result = dictionary.TryAddModelError("key1", "error1");
746+
Assert.True(result);
747+
748+
result = dictionary.TryAddModelException("key2", new Exception());
749+
Assert.True(result);
750+
751+
result = dictionary.TryAddModelException("key3", new Exception());
752+
Assert.False(result);
753+
754+
Assert.Equal(3, dictionary.Count);
755+
var error = Assert.Single(dictionary[string.Empty].Errors);
756+
Assert.IsType<TooManyModelErrorsException>(error.Exception);
757+
Assert.Equal(expected, error.Exception.Message);
758+
759+
// TooManyModelErrorsException added instead of key3 exception.
760+
Assert.DoesNotContain("key3", dictionary.Keys);
761+
}
762+
715763
[Fact]
716764
public void TryAddModelError_WithErrorString_ReturnsFalse_AndAddsMaxModelErrorMessage()
717765
{
@@ -864,6 +912,21 @@ public void ModelStateDictionary_HasReachedMaxErrors(int errorCount, bool expect
864912
Assert.Equal(expected, canAdd);
865913
}
866914

915+
[Fact]
916+
public void ModelStateDictionary_ReturnExceptionMessage_WhenModelStateNotSet()
917+
{
918+
// Arrange
919+
var dictionary = new ModelStateDictionary();
920+
var exception = new FormatException("The supplied value is invalid for Length.");
921+
922+
// Act
923+
dictionary.TryAddModelException("key", exception);
924+
925+
// Assert
926+
var error = Assert.Single(dictionary["key"].Errors);
927+
Assert.Same(exception, error.Exception);
928+
}
929+
867930
[Fact]
868931
public void ModelStateDictionary_ReturnGenericErrorMessage_WhenModelStateNotSet()
869932
{
@@ -933,6 +996,22 @@ public void ModelStateDictionary_AddsCustomErrorMessage_WhenModelStateNotSet_Wit
933996
Assert.Equal(expected, error.ErrorMessage);
934997
}
935998

999+
[Fact]
1000+
public void TryAddModelException_ReturnExceptionMessage_WhenModelStateSet()
1001+
{
1002+
// Arrange
1003+
var dictionary = new ModelStateDictionary();
1004+
dictionary.SetModelValue("key", new string[] { "some value" }, "some value");
1005+
var exception = new FormatException("The value 'some value' is not valid for Length.");
1006+
1007+
// Act
1008+
dictionary.TryAddModelException("key", exception);
1009+
1010+
// Assert
1011+
var error = Assert.Single(dictionary["key"].Errors);
1012+
Assert.Same(exception, error.Exception);
1013+
}
1014+
9361015
[Fact]
9371016
public void ModelStateDictionary_ReturnSpecificErrorMessage_WhenModelStateSet()
9381017
{
@@ -1022,6 +1101,23 @@ public void ModelStateDictionary_NoErrorMessage_ForUnrecognizedException()
10221101
Assert.Empty(error.ErrorMessage);
10231102
}
10241103

1104+
[Fact]
1105+
public void TryAddModelException_AddsErrorMessage_ForInputFormatterException()
1106+
{
1107+
// Arrange
1108+
var dictionary = new ModelStateDictionary();
1109+
var exception = new InputFormatterException("This is an InputFormatterException.");
1110+
1111+
// Act
1112+
dictionary.TryAddModelException("key", exception);
1113+
1114+
// Assert
1115+
var entry = Assert.Single(dictionary);
1116+
Assert.Equal("key", entry.Key);
1117+
var error = Assert.Single(entry.Value.Errors);
1118+
Assert.Same(exception, error.Exception);
1119+
}
1120+
10251121
[Fact]
10261122
public void ModelStateDictionary_AddsErrorMessage_ForInputFormatterException()
10271123
{
@@ -1371,4 +1467,14 @@ private class OptionsAccessor : IOptions<MvcOptions>
13711467
public MvcOptions Value { get; } = new MvcOptions();
13721468
}
13731469
}
1470+
1471+
internal class TestException : Exception
1472+
{
1473+
public TestException()
1474+
{
1475+
Message = "This is a test exception";
1476+
}
1477+
1478+
public override string Message { get; }
1479+
}
13741480
}

0 commit comments

Comments
 (0)