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 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,26 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
{
|
||||
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
|
||||
{
|
||||
if (inputStream is TranscodingReadStream transcoding)
|
||||
|
|
@ -98,5 +126,68 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
|
||||
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());
|
||||
|
||||
// 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());
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -5,14 +5,19 @@ using Microsoft.AspNetCore.Mvc;
|
|||
|
||||
namespace MvcSandbox.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
public class HomeController : Controller
|
||||
{
|
||||
[ModelBinder]
|
||||
public string Id { get; set; }
|
||||
|
||||
public IActionResult Index()
|
||||
[HttpPost("/")]
|
||||
public IActionResult Index(Person person)
|
||||
{
|
||||
return View();
|
||||
return Ok(person);
|
||||
}
|
||||
}
|
||||
|
||||
public class Person
|
||||
{
|
||||
public int Id { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue