Exception handling with SystemTextJsonInputFormatter
This commit is contained in:
parent
0648edf7c2
commit
f575677c95
|
|
@ -8,6 +8,8 @@ using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc.Formatters.Json;
|
using Microsoft.AspNetCore.Mvc.Formatters.Json;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Formatters
|
namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
{
|
{
|
||||||
|
|
@ -16,13 +18,19 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SystemTextJsonInputFormatter : TextInputFormatter, IInputFormatterExceptionPolicy
|
public class SystemTextJsonInputFormatter : TextInputFormatter, IInputFormatterExceptionPolicy
|
||||||
{
|
{
|
||||||
|
private readonly ILogger<SystemTextJsonInputFormatter> _logger;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of <see cref="SystemTextJsonInputFormatter"/>.
|
/// Initializes a new instance of <see cref="SystemTextJsonInputFormatter"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="options">The <see cref="JsonOptions"/>.</param>
|
/// <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;
|
SerializerOptions = options.JsonSerializerOptions;
|
||||||
|
_logger = logger;
|
||||||
|
|
||||||
SupportedEncodings.Add(UTF8EncodingWithoutBOM);
|
SupportedEncodings.Add(UTF8EncodingWithoutBOM);
|
||||||
SupportedEncodings.Add(UTF16EncodingLittleEndian);
|
SupportedEncodings.Add(UTF16EncodingLittleEndian);
|
||||||
|
|
@ -67,6 +75,26 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
{
|
{
|
||||||
model = await JsonSerializer.ReadAsync(inputStream, context.ModelType, SerializerOptions);
|
model = await JsonSerializer.ReadAsync(inputStream, context.ModelType, SerializerOptions);
|
||||||
}
|
}
|
||||||
|
catch (JsonException jsonException)
|
||||||
|
{
|
||||||
|
var path = jsonException.Path;
|
||||||
|
if (path.StartsWith("$.", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
path = path.Substring(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle path combinations such as ""+"Property", "Parent"+"Property", or "Parent"+"[12]".
|
||||||
|
var key = ModelNames.CreatePropertyModelName(context.ModelName, path);
|
||||||
|
|
||||||
|
var formatterException = new InputFormatterException(jsonException.Message, jsonException);
|
||||||
|
|
||||||
|
var metadata = GetPathMetadata(context.Metadata, path);
|
||||||
|
context.ModelState.TryAddModelError(key, formatterException, metadata);
|
||||||
|
|
||||||
|
Log.JsonInputException(_logger, jsonException);
|
||||||
|
|
||||||
|
return InputFormatterResult.Failure();
|
||||||
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (inputStream is TranscodingReadStream transcoding)
|
if (inputStream is TranscodingReadStream transcoding)
|
||||||
|
|
@ -98,5 +126,68 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
|
|
||||||
return new TranscodingReadStream(httpContext.Request.Body, encoding);
|
return new TranscodingReadStream(httpContext.Request.Body, encoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keep in sync with NewtonsoftJsonInputFormatter.GetPatMetadata
|
||||||
|
private ModelMetadata GetPathMetadata(ModelMetadata metadata, string path)
|
||||||
|
{
|
||||||
|
var index = 0;
|
||||||
|
while (index >= 0 && index < path.Length)
|
||||||
|
{
|
||||||
|
if (path[index] == '[')
|
||||||
|
{
|
||||||
|
// At start of "[0]".
|
||||||
|
if (metadata.ElementMetadata == null)
|
||||||
|
{
|
||||||
|
// Odd case but don't throw just because ErrorContext had an odd-looking path.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata = metadata.ElementMetadata;
|
||||||
|
index = path.IndexOf(']', index);
|
||||||
|
}
|
||||||
|
else if (path[index] == '.' || path[index] == ']')
|
||||||
|
{
|
||||||
|
// Skip '.' in "prefix.property" or "[0].property" or ']' in "[0]".
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// At start of "property", "property." or "property[0]".
|
||||||
|
var endIndex = path.IndexOfAny(new[] { '.', '[' }, index);
|
||||||
|
if (endIndex == -1)
|
||||||
|
{
|
||||||
|
endIndex = path.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
var propertyName = path.Substring(index, endIndex - index);
|
||||||
|
if (metadata.Properties[propertyName] == null)
|
||||||
|
{
|
||||||
|
// Odd case but don't throw just because ErrorContext had an odd-looking path.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata = metadata.Properties[propertyName];
|
||||||
|
index = endIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Log
|
||||||
|
{
|
||||||
|
private static readonly Action<ILogger, Exception> _jsonInputFormatterException;
|
||||||
|
|
||||||
|
static Log()
|
||||||
|
{
|
||||||
|
_jsonInputFormatterException = LoggerMessage.Define(
|
||||||
|
LogLevel.Debug,
|
||||||
|
new EventId(1, "SystemTextJsonInputException"),
|
||||||
|
"JSON input formatter threw an exception.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void JsonInputException(ILogger logger, Exception exception)
|
||||||
|
=> _jsonInputFormatterException(logger, exception);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
options.Filters.Add(new UnsupportedContentTypeFilter());
|
options.Filters.Add(new UnsupportedContentTypeFilter());
|
||||||
|
|
||||||
// Set up default input formatters.
|
// 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.
|
// Set up default output formatters.
|
||||||
options.OutputFormatters.Add(new HttpNoContentOutputFormatter());
|
options.OutputFormatters.Add(new HttpNoContentOutputFormatter());
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,13 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson
|
||||||
{
|
{
|
||||||
internal static class NewtonsoftJsonLoggerExtensions
|
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;
|
private static readonly Action<ILogger, string, Exception> _jsonResultExecuting;
|
||||||
|
|
||||||
static NewtonsoftJsonLoggerExtensions()
|
static NewtonsoftJsonLoggerExtensions()
|
||||||
{
|
{
|
||||||
_jsonInputFormatterCrashed = LoggerMessage.Define(
|
_jsonInputFormatterException = LoggerMessage.Define(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
new EventId(1, "JsonInputException"),
|
new EventId(1, "JsonInputException"),
|
||||||
"JSON input formatter threw an exception.");
|
"JSON input formatter threw an exception.");
|
||||||
|
|
@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson
|
||||||
|
|
||||||
public static void JsonInputException(this ILogger logger, Exception exception)
|
public static void JsonInputException(this ILogger logger, Exception exception)
|
||||||
{
|
{
|
||||||
_jsonInputFormatterCrashed(logger, exception);
|
_jsonInputFormatterException(logger, exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void JsonResultExecuting(this ILogger logger, object value)
|
public static void JsonResultExecuting(this ILogger logger, object value)
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,19 @@ using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace MvcSandbox.Controllers
|
namespace MvcSandbox.Controllers
|
||||||
{
|
{
|
||||||
|
[ApiController]
|
||||||
public class HomeController : Controller
|
public class HomeController : Controller
|
||||||
{
|
{
|
||||||
[ModelBinder]
|
[HttpPost("/")]
|
||||||
public string Id { get; set; }
|
public IActionResult Index(Person person)
|
||||||
|
|
||||||
public IActionResult Index()
|
|
||||||
{
|
{
|
||||||
return View();
|
return Ok(person);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class Person
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue