JSON error handling (#11190)
* Exception handling with SystemTextJsonInputFormatter * Additional tests * Update ref package * PR feedback * Test fixes and feedback * Update refs * Restructure tests * Cleanup
This commit is contained in:
commit
151ae52661
|
|
@ -1877,7 +1877,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
}
|
||||
public partial class SystemTextJsonInputFormatter : Microsoft.AspNetCore.Mvc.Formatters.TextInputFormatter, Microsoft.AspNetCore.Mvc.Formatters.IInputFormatterExceptionPolicy
|
||||
{
|
||||
public SystemTextJsonInputFormatter(Microsoft.AspNetCore.Mvc.JsonOptions options) { }
|
||||
public SystemTextJsonInputFormatter(Microsoft.AspNetCore.Mvc.JsonOptions options, Microsoft.Extensions.Logging.ILogger<Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonInputFormatter> logger) { }
|
||||
Microsoft.AspNetCore.Mvc.Formatters.InputFormatterExceptionPolicy Microsoft.AspNetCore.Mvc.Formatters.IInputFormatterExceptionPolicy.ExceptionPolicy { get { throw null; } }
|
||||
public System.Text.Json.JsonSerializerOptions SerializerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
[System.Diagnostics.DebuggerStepThroughAttribute]
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ using System.Text.Json;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters.Json;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||
{
|
||||
|
|
@ -16,13 +18,19 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
/// </summary>
|
||||
public class SystemTextJsonInputFormatter : TextInputFormatter, IInputFormatterExceptionPolicy
|
||||
{
|
||||
private readonly ILogger<SystemTextJsonInputFormatter> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="SystemTextJsonInputFormatter"/>.
|
||||
/// </summary>
|
||||
/// <param name="options">The <see cref="JsonOptions"/>.</param>
|
||||
public SystemTextJsonInputFormatter(JsonOptions options)
|
||||
/// <param name="logger">The <see cref="ILogger"/>.</param>
|
||||
public SystemTextJsonInputFormatter(
|
||||
JsonOptions options,
|
||||
ILogger<SystemTextJsonInputFormatter> logger)
|
||||
{
|
||||
SerializerOptions = options.JsonSerializerOptions;
|
||||
_logger = logger;
|
||||
|
||||
SupportedEncodings.Add(UTF8EncodingWithoutBOM);
|
||||
SupportedEncodings.Add(UTF16EncodingLittleEndian);
|
||||
|
|
@ -67,6 +75,18 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
{
|
||||
model = await JsonSerializer.ReadAsync(inputStream, context.ModelType, SerializerOptions);
|
||||
}
|
||||
catch (JsonException jsonException)
|
||||
{
|
||||
var path = jsonException.Path;
|
||||
|
||||
var formatterException = new InputFormatterException(jsonException.Message, jsonException);
|
||||
|
||||
context.ModelState.TryAddModelError(path, formatterException, context.Metadata);
|
||||
|
||||
Log.JsonInputException(_logger, jsonException);
|
||||
|
||||
return InputFormatterResult.Failure();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (inputStream is TranscodingReadStream transcoding)
|
||||
|
|
@ -85,6 +105,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
}
|
||||
else
|
||||
{
|
||||
Log.JsonInputSuccess(_logger, context.ModelType);
|
||||
return InputFormatterResult.Success(model);
|
||||
}
|
||||
}
|
||||
|
|
@ -98,5 +119,29 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
|
||||
return new TranscodingReadStream(httpContext.Request.Body, encoding);
|
||||
}
|
||||
|
||||
private static class Log
|
||||
{
|
||||
private static readonly Action<ILogger, string, Exception> _jsonInputFormatterException;
|
||||
private static readonly Action<ILogger, string, Exception> _jsonInputSuccess;
|
||||
|
||||
static Log()
|
||||
{
|
||||
_jsonInputFormatterException = LoggerMessage.Define<string>(
|
||||
LogLevel.Debug,
|
||||
new EventId(1, "SystemTextJsonInputException"),
|
||||
"JSON input formatter threw an exception: {Message}");
|
||||
_jsonInputSuccess = LoggerMessage.Define<string>(
|
||||
LogLevel.Debug,
|
||||
new EventId(2, "SystemTextJsonInputSuccess"),
|
||||
"JSON input formatter succeeded, deserializing to type '{TypeName}'");
|
||||
}
|
||||
|
||||
public static void JsonInputException(ILogger logger, Exception exception)
|
||||
=> _jsonInputFormatterException(logger, exception.Message, exception);
|
||||
|
||||
public static void JsonInputSuccess(ILogger logger, Type modelType)
|
||||
=> _jsonInputSuccess(logger, modelType.FullName, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
options.Filters.Add(new UnsupportedContentTypeFilter());
|
||||
|
||||
// Set up default input formatters.
|
||||
options.InputFormatters.Add(new SystemTextJsonInputFormatter(_jsonOptions.Value));
|
||||
options.InputFormatters.Add(new SystemTextJsonInputFormatter(_jsonOptions.Value, _loggerFactory.CreateLogger<SystemTextJsonInputFormatter>()));
|
||||
|
||||
// Set up default output formatters.
|
||||
options.OutputFormatters.Add(new HttpNoContentOutputFormatter());
|
||||
|
|
|
|||
|
|
@ -3,16 +3,21 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.Extensions.Logging.Testing;
|
||||
using Newtonsoft.Json;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||
{
|
||||
public abstract class JsonInputFormatterTestBase
|
||||
public abstract class JsonInputFormatterTestBase : LoggedTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("application/json", true)]
|
||||
|
|
@ -102,6 +107,61 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
Assert.Equal("abcd", stringValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public virtual async Task JsonFormatter_EscapedKeys_Bracket()
|
||||
{
|
||||
var expectedKey = JsonFormatter_EscapedKeys_Bracket_Expected;
|
||||
|
||||
// Arrange
|
||||
var content = "[{\"It[s a key\":1234556}]";
|
||||
var formatter = GetInputFormatter();
|
||||
|
||||
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||
var httpContext = GetHttpContext(contentBytes);
|
||||
|
||||
var formatterContext = CreateInputFormatterContext(typeof(IEnumerable<IDictionary<string, short>>), httpContext);
|
||||
|
||||
// Act
|
||||
var result = await formatter.ReadAsync(formatterContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.HasError);
|
||||
Assert.Collection(
|
||||
formatterContext.ModelState.OrderBy(k => k.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal(expectedKey, kvp.Key);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public virtual async Task JsonFormatter_EscapedKeys()
|
||||
{
|
||||
var expectedKey = JsonFormatter_EscapedKeys_Expected;
|
||||
|
||||
// Arrange
|
||||
var content = "[{\"It\\\"s a key\": 1234556}]";
|
||||
var formatter = GetInputFormatter();
|
||||
|
||||
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||
var httpContext = GetHttpContext(contentBytes);
|
||||
|
||||
var formatterContext = CreateInputFormatterContext(
|
||||
typeof(IEnumerable<IDictionary<string, short>>), httpContext);
|
||||
|
||||
// Act
|
||||
var result = await formatter.ReadAsync(formatterContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.HasError);
|
||||
Assert.Collection(
|
||||
formatterContext.ModelState.OrderBy(k => k.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal(expectedKey, kvp.Key);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public virtual async Task JsonFormatterReadsDateTimeValue()
|
||||
{
|
||||
|
|
@ -199,6 +259,33 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
Assert.Equal(new int[] { 0, 23, 300 }, (IEnumerable<int>)result.Model);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public virtual async Task ReadAsync_ArrayOfObjects_HasCorrectKey()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = GetInputFormatter();
|
||||
|
||||
var content = "[{\"Age\": 5}, {\"Age\": 3}, {\"Age\": \"Cheese\"} ]";
|
||||
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||
var httpContext = GetHttpContext(contentBytes);
|
||||
|
||||
var formatterContext = CreateInputFormatterContext(typeof(List<ComplexModel>), httpContext);
|
||||
|
||||
var expectedKey = ReadAsync_ArrayOfObjects_HasCorrectKey_Expected;
|
||||
|
||||
// Act
|
||||
var result = await formatter.ReadAsync(formatterContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.HasError, "Model should have had an error!");
|
||||
Assert.Collection(formatterContext.ModelState.OrderBy(k => k.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal(expectedKey, kvp.Key);
|
||||
Assert.Single(kvp.Value.Errors);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public virtual async Task ReadAsync_AddsModelValidationErrorsToModelState()
|
||||
{
|
||||
|
|
@ -210,15 +297,19 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
var httpContext = GetHttpContext(contentBytes);
|
||||
|
||||
var formatterContext = CreateInputFormatterContext(typeof(ComplexModel), httpContext);
|
||||
var expectedKey = ReadAsync_AddsModelValidationErrorsToModelState_Expected;
|
||||
|
||||
// Act
|
||||
var result = await formatter.ReadAsync(formatterContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.HasError);
|
||||
Assert.Equal(
|
||||
"Could not convert string to decimal: not-an-age. Path 'Age', line 1, position 44.",
|
||||
formatterContext.ModelState["Age"].Errors[0].ErrorMessage);
|
||||
Assert.True(result.HasError, "Model should have had an error!");
|
||||
Assert.Collection(formatterContext.ModelState.OrderBy(k => k.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal(expectedKey, kvp.Key);
|
||||
Assert.Single(kvp.Value.Errors);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -227,19 +318,23 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
// Arrange
|
||||
var formatter = GetInputFormatter();
|
||||
|
||||
var content = "[0, 23, 300]";
|
||||
var content = "[0, 23, 33767]";
|
||||
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||
var httpContext = GetHttpContext(contentBytes);
|
||||
|
||||
var formatterContext = CreateInputFormatterContext(typeof(byte[]), httpContext);
|
||||
var formatterContext = CreateInputFormatterContext(typeof(short[]), httpContext);
|
||||
|
||||
var expectedValue = ReadAsync_InvalidArray_AddsOverflowErrorsToModelState_Expected;
|
||||
|
||||
// Act
|
||||
var result = await formatter.ReadAsync(formatterContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.HasError);
|
||||
Assert.Equal("The supplied value is invalid.", formatterContext.ModelState["[2]"].Errors[0].ErrorMessage);
|
||||
Assert.Null(formatterContext.ModelState["[2]"].Errors[0].Exception);
|
||||
Assert.True(result.HasError, "Model should have produced an error!");
|
||||
Assert.Collection(formatterContext.ModelState.OrderBy(k => k.Key),
|
||||
kvp => {
|
||||
Assert.Equal(expectedValue, kvp.Key);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -253,15 +348,19 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
var httpContext = GetHttpContext(contentBytes);
|
||||
|
||||
var formatterContext = CreateInputFormatterContext(typeof(ComplexModel[]), httpContext, modelName: "names");
|
||||
var expectedKey = ReadAsync_InvalidComplexArray_AddsOverflowErrorsToModelState_Expected;
|
||||
|
||||
// Act
|
||||
var result = await formatter.ReadAsync(formatterContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.HasError);
|
||||
Assert.Equal(
|
||||
"Error converting value 300 to type 'System.Byte'. Path '[1].Small', line 1, position 69.",
|
||||
formatterContext.ModelState["names[1].Small"].Errors[0].ErrorMessage);
|
||||
Assert.Collection(
|
||||
formatterContext.ModelState.OrderBy(k => k.Key),
|
||||
kvp => {
|
||||
Assert.Equal(expectedKey, kvp.Key);
|
||||
Assert.Single(kvp.Value.Errors);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -318,6 +417,65 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
Assert.Null(result.Model);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadAsync_ComplexPoco()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = GetInputFormatter();
|
||||
|
||||
var content = "{ \"Id\": 5, \"Person\": { \"Name\": \"name\", \"Numbers\": [3, 2, \"Hamburger\"]} }";
|
||||
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||
var httpContext = GetHttpContext(contentBytes);
|
||||
|
||||
var formatterContext = CreateInputFormatterContext(typeof(ComplexPoco), httpContext);
|
||||
|
||||
var expectedKey = ReadAsync_ComplexPoco_Expected;
|
||||
|
||||
// Act
|
||||
var result = await formatter.ReadAsync(formatterContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.HasError, "Model should have had an error!");
|
||||
Assert.Collection(formatterContext.ModelState.OrderBy(k => k.Key),
|
||||
kvp => {
|
||||
Assert.Equal(expectedKey, kvp.Key);
|
||||
Assert.Single(kvp.Value.Errors);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public virtual async Task ReadAsync_RequiredAttribute()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = GetInputFormatter();
|
||||
var content = "{ \"Id\": 5, \"Person\": {\"Numbers\": [3]} }";
|
||||
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||
var httpContext = GetHttpContext(contentBytes);
|
||||
|
||||
var formatterContext = CreateInputFormatterContext(typeof(ComplexPoco), httpContext);
|
||||
|
||||
// Act
|
||||
var result = await formatter.ReadAsync(formatterContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.HasError, "Model should have had an error!");
|
||||
Assert.Single(formatterContext.ModelState["Person.Name"].Errors);
|
||||
}
|
||||
|
||||
internal abstract string JsonFormatter_EscapedKeys_Bracket_Expected { get; }
|
||||
|
||||
internal abstract string JsonFormatter_EscapedKeys_Expected { get; }
|
||||
|
||||
internal abstract string ReadAsync_ArrayOfObjects_HasCorrectKey_Expected { get; }
|
||||
|
||||
internal abstract string ReadAsync_AddsModelValidationErrorsToModelState_Expected { get; }
|
||||
|
||||
internal abstract string ReadAsync_InvalidArray_AddsOverflowErrorsToModelState_Expected { get; }
|
||||
|
||||
internal abstract string ReadAsync_InvalidComplexArray_AddsOverflowErrorsToModelState_Expected { get; }
|
||||
|
||||
internal abstract string ReadAsync_ComplexPoco_Expected { get; }
|
||||
|
||||
protected abstract TextInputFormatter GetInputFormatter();
|
||||
|
||||
protected static HttpContext GetHttpContext(
|
||||
|
|
@ -356,6 +514,20 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
treatEmptyInputAsDefaultValue: treatEmptyInputAsDefaultValue);
|
||||
}
|
||||
|
||||
protected sealed class ComplexPoco
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public Person Person{ get; set; }
|
||||
}
|
||||
|
||||
protected sealed class Person
|
||||
{
|
||||
[Required]
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public string Name { get; set; }
|
||||
public IEnumerable<int> Numbers { get; set; }
|
||||
}
|
||||
|
||||
protected sealed class ComplexModel
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
|
|
|||
|
|
@ -1,40 +1,103 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// 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.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||
{
|
||||
public class SystemTextJsonInputFormatterTest : JsonInputFormatterTestBase
|
||||
{
|
||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8474")]
|
||||
[Fact]
|
||||
public override Task ReadAsync_AddsModelValidationErrorsToModelState()
|
||||
{
|
||||
return base.ReadAsync_AddsModelValidationErrorsToModelState();
|
||||
}
|
||||
|
||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8474")]
|
||||
[Fact]
|
||||
public override Task ReadAsync_InvalidArray_AddsOverflowErrorsToModelState()
|
||||
{
|
||||
return base.ReadAsync_InvalidArray_AddsOverflowErrorsToModelState();
|
||||
}
|
||||
|
||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8474")]
|
||||
[Fact]
|
||||
public override Task ReadAsync_InvalidComplexArray_AddsOverflowErrorsToModelState()
|
||||
{
|
||||
return base.ReadAsync_InvalidComplexArray_AddsOverflowErrorsToModelState();
|
||||
}
|
||||
|
||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8474")]
|
||||
[Fact]
|
||||
public override Task ReadAsync_UsesTryAddModelValidationErrorsToModelState()
|
||||
{
|
||||
return base.ReadAsync_UsesTryAddModelValidationErrorsToModelState();
|
||||
}
|
||||
|
||||
[Fact(Skip = "https://github.com/dotnet/corefx/issues/38492")]
|
||||
public override Task ReadAsync_RequiredAttribute()
|
||||
{
|
||||
// System.Text.Json does not yet support an equivalent of Required.
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public override Task JsonFormatter_EscapedKeys()
|
||||
{
|
||||
return base.JsonFormatter_EscapedKeys();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public override Task JsonFormatter_EscapedKeys_Bracket()
|
||||
{
|
||||
return base.JsonFormatter_EscapedKeys_Bracket();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadAsync_SingleError()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = GetInputFormatter();
|
||||
|
||||
var content = "[5, 'seven', 3, notnum ]";
|
||||
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||
var httpContext = GetHttpContext(contentBytes);
|
||||
|
||||
var formatterContext = CreateInputFormatterContext(typeof(List<int>), httpContext);
|
||||
|
||||
// Act
|
||||
await formatter.ReadAsync(formatterContext);
|
||||
|
||||
Assert.Collection(
|
||||
formatterContext.ModelState.OrderBy(k => k),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("$[1]", kvp.Key);
|
||||
var error = Assert.Single(kvp.Value.Errors);
|
||||
Assert.StartsWith("''' is an invalid start of a value", error.ErrorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
protected override TextInputFormatter GetInputFormatter()
|
||||
{
|
||||
return new SystemTextJsonInputFormatter(new JsonOptions());
|
||||
return new SystemTextJsonInputFormatter(new JsonOptions(), LoggerFactory.CreateLogger<SystemTextJsonInputFormatter>());
|
||||
}
|
||||
|
||||
internal override string ReadAsync_AddsModelValidationErrorsToModelState_Expected => "$.Age";
|
||||
|
||||
internal override string JsonFormatter_EscapedKeys_Expected => "$[0]['It\\u0022s a key']";
|
||||
|
||||
internal override string JsonFormatter_EscapedKeys_Bracket_Expected => "$[0]['It[s a key']";
|
||||
|
||||
internal override string ReadAsync_ArrayOfObjects_HasCorrectKey_Expected => "$[2].Age";
|
||||
|
||||
internal override string ReadAsync_InvalidArray_AddsOverflowErrorsToModelState_Expected => "$[2]";
|
||||
|
||||
internal override string ReadAsync_InvalidComplexArray_AddsOverflowErrorsToModelState_Expected => "$[1].Small";
|
||||
|
||||
internal override string ReadAsync_ComplexPoco_Expected => "$.Person.Numbers[2]";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -226,7 +226,9 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
}
|
||||
else
|
||||
{
|
||||
addMember = !path.EndsWith("." + member, StringComparison.Ordinal);
|
||||
addMember = !path.EndsWith("." + member, StringComparison.Ordinal)
|
||||
&& !path.EndsWith("['" + member + "']", StringComparison.Ordinal)
|
||||
&& !path.EndsWith("[" + member + "]", StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson
|
|||
{
|
||||
internal static class NewtonsoftJsonLoggerExtensions
|
||||
{
|
||||
private static readonly Action<ILogger, Exception> _jsonInputFormatterCrashed;
|
||||
private static readonly Action<ILogger, Exception> _jsonInputFormatterException;
|
||||
|
||||
private static readonly Action<ILogger, string, Exception> _jsonResultExecuting;
|
||||
|
||||
static NewtonsoftJsonLoggerExtensions()
|
||||
{
|
||||
_jsonInputFormatterCrashed = LoggerMessage.Define(
|
||||
_jsonInputFormatterException = LoggerMessage.Define(
|
||||
LogLevel.Debug,
|
||||
new EventId(1, "JsonInputException"),
|
||||
"JSON input formatter threw an exception.");
|
||||
|
|
@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson
|
|||
|
||||
public static void JsonInputException(this ILogger logger, Exception exception)
|
||||
{
|
||||
_jsonInputFormatterCrashed(logger, exception);
|
||||
_jsonInputFormatterException(logger, exception);
|
||||
}
|
||||
|
||||
public static void JsonResultExecuting(this ILogger logger, object value)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -196,6 +196,18 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
Assert.Equal(settings.DateTimeZoneHandling, actual.DateTimeZoneHandling);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public override Task JsonFormatter_EscapedKeys()
|
||||
{
|
||||
return base.JsonFormatter_EscapedKeys();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public override Task JsonFormatter_EscapedKeys_Bracket()
|
||||
{
|
||||
return base.JsonFormatter_EscapedKeys_Bracket();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(" ", true, true)]
|
||||
[InlineData(" ", false, false)]
|
||||
|
|
@ -235,6 +247,39 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
Assert.Equal(expectedMessage, modelError.ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadAsync_AllowMultipleErrors()
|
||||
{
|
||||
// Arrange
|
||||
var content = "[5, 'seven', 3, 'notnum']";
|
||||
|
||||
var formatter = CreateFormatter(allowInputFormatterExceptionMessages: true);
|
||||
|
||||
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||
var httpContext = GetHttpContext(contentBytes);
|
||||
|
||||
var formatterContext = CreateInputFormatterContext(typeof(List<int>), httpContext);
|
||||
|
||||
// Act
|
||||
var result = await formatter.ReadAsync(formatterContext);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
formatterContext.ModelState.OrderBy(k => k.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("[1]", kvp.Key);
|
||||
var error = Assert.Single(kvp.Value.Errors);
|
||||
Assert.StartsWith("Could not convert string to integer:", error.ErrorMessage);
|
||||
},
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("[3]", kvp.Key);
|
||||
var error = Assert.Single(kvp.Value.Errors);
|
||||
Assert.StartsWith("Could not convert string to integer:", error.ErrorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadAsync_DoNotAllowInputFormatterExceptionMessages_DoesNotWrapJsonInputExceptions()
|
||||
{
|
||||
|
|
@ -325,6 +370,20 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
});
|
||||
}
|
||||
|
||||
internal override string JsonFormatter_EscapedKeys_Expected => "[0]['It\"s a key']";
|
||||
|
||||
internal override string JsonFormatter_EscapedKeys_Bracket_Expected => "[0][\'It[s a key\']";
|
||||
|
||||
internal override string ReadAsync_AddsModelValidationErrorsToModelState_Expected => "Age";
|
||||
|
||||
internal override string ReadAsync_ArrayOfObjects_HasCorrectKey_Expected => "[2].Age";
|
||||
|
||||
internal override string ReadAsync_ComplexPoco_Expected => "Person.Numbers[2]";
|
||||
|
||||
internal override string ReadAsync_InvalidComplexArray_AddsOverflowErrorsToModelState_Expected => "names[1].Small";
|
||||
|
||||
internal override string ReadAsync_InvalidArray_AddsOverflowErrorsToModelState_Expected => "[2]";
|
||||
|
||||
private class Location
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
|
|
|||
|
|
@ -325,6 +325,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions.ApiDescription.S
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ApiDescription.Server", "Extensions.ApiDescription.Server\src\Microsoft.Extensions.ApiDescription.Server.csproj", "{D7CF2A1E-A29E-45AB-9C2A-CD6C3BAE54F2}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Metadata", "..\Http\Metadata\src\Microsoft.AspNetCore.Metadata.csproj", "{464195B3-022A-4D19-9104-8C66CC882D67}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -1841,6 +1843,18 @@ Global
|
|||
{D7CF2A1E-A29E-45AB-9C2A-CD6C3BAE54F2}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{D7CF2A1E-A29E-45AB-9C2A-CD6C3BAE54F2}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{D7CF2A1E-A29E-45AB-9C2A-CD6C3BAE54F2}.Release|x86.Build.0 = Release|Any CPU
|
||||
{464195B3-022A-4D19-9104-8C66CC882D67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{464195B3-022A-4D19-9104-8C66CC882D67}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{464195B3-022A-4D19-9104-8C66CC882D67}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{464195B3-022A-4D19-9104-8C66CC882D67}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{464195B3-022A-4D19-9104-8C66CC882D67}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{464195B3-022A-4D19-9104-8C66CC882D67}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{464195B3-022A-4D19-9104-8C66CC882D67}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{464195B3-022A-4D19-9104-8C66CC882D67}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{464195B3-022A-4D19-9104-8C66CC882D67}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{464195B3-022A-4D19-9104-8C66CC882D67}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{464195B3-022A-4D19-9104-8C66CC882D67}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{464195B3-022A-4D19-9104-8C66CC882D67}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -1972,6 +1986,7 @@ Global
|
|||
{C6F3BCE6-1EFD-4360-932B-B98573E78926} = {45CE788D-4B69-4F83-981C-F43D8F15B0F1}
|
||||
{637119E8-5BBB-4FC7-A372-DAF38FF5EBD9} = {5FE3048A-E96B-44F8-A7C4-FC590D7E04B4}
|
||||
{D7CF2A1E-A29E-45AB-9C2A-CD6C3BAE54F2} = {C15AA245-9E54-4FD6-90FF-B46F47779C46}
|
||||
{464195B3-022A-4D19-9104-8C66CC882D67} = {5FE3048A-E96B-44F8-A7C4-FC590D7E04B4}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {63D344F6-F86D-40E6-85B9-0AABBE338C4A}
|
||||
|
|
|
|||
Loading…
Reference in New Issue