Add `ErrorContext.Member` to `ErrorContext.Path` when clearly needed

- #8509

nits:
- use `ModelNames.CreatePropertyModelName(...)`
- move `exception` assignment up and reuse that variable
This commit is contained in:
Doug Bunting 2018-10-27 21:29:28 -07:00 committed by Doug Bunting
parent f5aae26b44
commit 1da7a89f59
3 changed files with 40 additions and 22 deletions

View File

@ -261,32 +261,48 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
{ {
successful = false; successful = false;
// Handle path combinations such as "" + "Property", "Parent" + "Property", or "Parent" + "[12]". // When ErrorContext.Path does not include ErrorContext.Member, add Member to form full path.
var key = eventArgs.ErrorContext.Path; var path = eventArgs.ErrorContext.Path;
if (!string.IsNullOrEmpty(context.ModelName)) var member = eventArgs.ErrorContext.Member?.ToString();
var addMember = !string.IsNullOrEmpty(member);
if (addMember)
{ {
if (string.IsNullOrEmpty(eventArgs.ErrorContext.Path)) // Path.Member case (path.Length < member.Length) needs no further checks.
if (path.Length == member.Length)
{ {
key = context.ModelName; // Add Member in Path.Memb case but not for Path.Path.
addMember = !string.Equals(path, member, StringComparison.Ordinal);
} }
else if (eventArgs.ErrorContext.Path[0] == '[') else if (path.Length > member.Length)
{ {
key = context.ModelName + eventArgs.ErrorContext.Path; // Finally, check whether Path already ends with Member.
} if (member[0] == '[')
else {
{ addMember = !path.EndsWith(member, StringComparison.Ordinal);
key = context.ModelName + "." + eventArgs.ErrorContext.Path; }
else
{
addMember = !path.EndsWith("." + member, StringComparison.Ordinal);
}
} }
} }
var metadata = GetPathMetadata(context.Metadata, eventArgs.ErrorContext.Path); if (addMember)
var modelStateException = WrapExceptionForModelState(eventArgs.ErrorContext.Error); {
context.ModelState.TryAddModelError(key, modelStateException, metadata); path = ModelNames.CreatePropertyModelName(path, member);
}
_logger.JsonInputException(eventArgs.ErrorContext.Error); // Handle path combinations such as ""+"Property", "Parent"+"Property", or "Parent"+"[12]".
var key = ModelNames.CreatePropertyModelName(context.ModelName, path);
exception = eventArgs.ErrorContext.Error; exception = eventArgs.ErrorContext.Error;
var metadata = GetPathMetadata(context.Metadata, path);
var modelStateException = WrapExceptionForModelState(exception);
context.ModelState.TryAddModelError(key, modelStateException, metadata);
_logger.JsonInputException(exception);
// Error must always be marked as handled // Error must always be marked as handled
// Failure to do so can cause the exception to be rethrown at every recursive level and // Failure to do so can cause the exception to be rethrown at every recursive level and
// overflow the stack for x64 CLR processes // overflow the stack for x64 CLR processes

View File

@ -44,7 +44,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
httpContext.Features.Set<IHttpResponseFeature>(new TestResponseFeature()); httpContext.Features.Set<IHttpResponseFeature>(new TestResponseFeature());
httpContext.Request.Body = new NonSeekableReadStream(contentBytes); httpContext.Request.Body = new NonSeekableReadStream(contentBytes);
httpContext.Request.ContentType = "application/json"; httpContext.Request.ContentType = "application/json";
var formatterContext = CreateInputFormatterContext(typeof(User), httpContext); var formatterContext = CreateInputFormatterContext(typeof(User), httpContext);
// Act // Act
@ -531,7 +531,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// missing password property here // missing password property here
var contentBytes = Encoding.UTF8.GetBytes("{ \"UserName\" : \"John\"}"); var contentBytes = Encoding.UTF8.GetBytes("{ \"UserName\" : \"John\"}");
var httpContext = GetHttpContext(contentBytes, "application/json;charset=utf-8"); var httpContext = GetHttpContext(contentBytes, "application/json;charset=utf-8");
var formatterContext = CreateInputFormatterContext(typeof(UserLogin), httpContext); var formatterContext = CreateInputFormatterContext(typeof(UserLogin), httpContext);
// Act // Act
@ -571,7 +571,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
[InlineData("{\"a\":{\"b\"}}", "a", "Invalid character after parsing property name. Expected ':' but got: }. Path 'a', line 1, position 9.")] [InlineData("{\"a\":{\"b\"}}", "a", "Invalid character after parsing property name. Expected ':' but got: }. Path 'a', line 1, position 9.")]
[InlineData("{\"age\":\"x\"}", "age", "Could not convert string to decimal: x. Path 'age', line 1, position 10.")] [InlineData("{\"age\":\"x\"}", "age", "Could not convert string to decimal: x. Path 'age', line 1, position 10.")]
[InlineData("{\"login\":1}", "login", "Error converting value 1 to type 'Microsoft.AspNetCore.Mvc.Formatters.JsonInputFormatterTest+UserLogin'. Path 'login', line 1, position 10.")] [InlineData("{\"login\":1}", "login", "Error converting value 1 to type 'Microsoft.AspNetCore.Mvc.Formatters.JsonInputFormatterTest+UserLogin'. Path 'login', line 1, position 10.")]
[InlineData("{\"login\":{\"username\":\"somevalue\"}}", "login", "Required property 'Password' not found in JSON. Path 'login', line 1, position 33.")] [InlineData("{\"login\":{\"username\":\"somevalue\"}}", "login.Password", "Required property 'Password' not found in JSON. Path 'login', line 1, position 33.")]
public async Task ReadAsync_WithAllowInputFormatterExceptionMessages_RegistersJsonInputExceptionsAsInputFormatterException( public async Task ReadAsync_WithAllowInputFormatterExceptionMessages_RegistersJsonInputExceptionsAsInputFormatterException(
string content, string content,
string modelStateKey, string modelStateKey,
@ -582,7 +582,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var contentBytes = Encoding.UTF8.GetBytes(content); var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes); var httpContext = GetHttpContext(contentBytes);
var formatterContext = CreateInputFormatterContext(typeof(User), httpContext); var formatterContext = CreateInputFormatterContext(typeof(User), httpContext);
// Act // Act

View File

@ -172,8 +172,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
public async Task CheckIfExcludedField_IsNotValidatedForNonBodyBoundModels() public async Task CheckIfExcludedField_IsNotValidatedForNonBodyBoundModels()
{ {
// Arrange // Arrange
var kvps = new List<KeyValuePair<string, string>>(); var kvps = new List<KeyValuePair<string, string>>
kvps.Add(new KeyValuePair<string, string>("Alias", "xyz")); {
new KeyValuePair<string, string>("Alias", "xyz"),
};
var content = new FormUrlEncodedContent(kvps); var content = new FormUrlEncodedContent(kvps);
// Act // Act
@ -316,7 +318,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
validationProblemDetails.Errors, validationProblemDetails.Errors,
error => error =>
{ {
Assert.Empty(error.Key); Assert.Equal("isbn", error.Key);
Assert.Equal(new[] { "Required property 'isbn' not found in JSON. Path '', line 1, position 44." }, error.Value); Assert.Equal(new[] { "Required property 'isbn' not found in JSON. Path '', line 1, position 44." }, error.Value);
}); });
} }