diff --git a/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonInputFormatter.cs b/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonInputFormatter.cs index e4f24e2b4ae0..9699a4036525 100644 --- a/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonInputFormatter.cs +++ b/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonInputFormatter.cs @@ -232,6 +232,13 @@ public override async Task ReadRequestBodyAsync( void ErrorHandler(object? sender, Newtonsoft.Json.Serialization.ErrorEventArgs eventArgs) { + // Skipping error, if it's already marked as handled + // This allows user code to implement its own error handling + if (eventArgs.ErrorContext.Handled) + { + return; + } + successful = false; // When ErrorContext.Path does not include ErrorContext.Member, add Member to form full path. diff --git a/src/Mvc/Mvc.NewtonsoftJson/test/NewtonsoftJsonInputFormatterTest.cs b/src/Mvc/Mvc.NewtonsoftJson/test/NewtonsoftJsonInputFormatterTest.cs index a180a4e07532..e4d6738d561c 100644 --- a/src/Mvc/Mvc.NewtonsoftJson/test/NewtonsoftJsonInputFormatterTest.cs +++ b/src/Mvc/Mvc.NewtonsoftJson/test/NewtonsoftJsonInputFormatterTest.cs @@ -487,6 +487,44 @@ public async Task ReadAsync_WithReadJsonWithRequestCulture_DeserializesUsingRequ } } + [Fact] + public async Task ReadAsync_AllowUserCodeToHandleDeserializationErrors() + { + // Arrange + var serializerSettings = new JsonSerializerSettings + { + Error = (sender, eventArgs) => + { + eventArgs.ErrorContext.Handled = true; + } + }; + var formatter = new NewtonsoftJsonInputFormatter( + GetLogger(), + serializerSettings, + ArrayPool.Shared, + _objectPoolProvider, + new MvcOptions(), + new MvcNewtonsoftJsonOptions()); + + var content = $"{{'id': 'should be integer', 'name': 'test location'}}"; + var contentBytes = Encoding.UTF8.GetBytes(content); + var httpContext = new DefaultHttpContext(); + httpContext.Features.Set(new TestResponseFeature()); + httpContext.Request.Body = new NonSeekableReadStream(contentBytes, allowSyncReads: false); + httpContext.Request.ContentType = "application/json"; + + var formatterContext = CreateInputFormatterContext(typeof(Location), httpContext); + + // Act + var result = await formatter.ReadAsync(formatterContext); + + // Assert + Assert.False(result.HasError); + var location = (Location)result.Model; + Assert.Equal(0, location?.Id); + Assert.Equal("test location", location?.Name); + } + private class TestableJsonInputFormatter : NewtonsoftJsonInputFormatter { public TestableJsonInputFormatter(JsonSerializerSettings settings, ObjectPoolProvider objectPoolProvider)