diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonResultExecutor.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonResultExecutor.cs index 79c9ee851d..f64082e9a5 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonResultExecutor.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonResultExecutor.cs @@ -33,8 +33,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal /// The . /// The for creating buffers. public JsonResultExecutor( - IHttpResponseStreamWriterFactory writerFactory, - ILogger logger, + IHttpResponseStreamWriterFactory writerFactory, + ILogger logger, IOptions options, ArrayPool charPool) { @@ -124,6 +124,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal { jsonWriter.ArrayPool = _charPool; jsonWriter.CloseOutput = false; + jsonWriter.AutoCompleteOnClose = false; var jsonSerializer = JsonSerializer.Create(serializerSettings); jsonSerializer.Serialize(jsonWriter, result.Value); diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonOutputFormatter.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonOutputFormatter.cs index 111a7df47f..98bbe2e0af 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonOutputFormatter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonOutputFormatter.cs @@ -97,6 +97,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters { ArrayPool = _charPool, CloseOutput = false, + AutoCompleteOnClose = false }; return jsonWriter; diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/Internal/JsonResultExecutorTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/Internal/JsonResultExecutorTest.cs index c48eaa4856..aa32faad36 100644 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/Internal/JsonResultExecutorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/Internal/JsonResultExecutorTest.cs @@ -1,6 +1,7 @@ // 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.Buffers; using System.IO; using System.Text; @@ -156,6 +157,31 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal Assert.Equal("application/json; charset=utf-8", context.HttpContext.Response.ContentType); } + [Fact] + public async Task ExecuteAsync_ErrorDuringSerialization_DoesNotCloseTheBrackets() + { + // Arrange + var expected = Encoding.UTF8.GetBytes("{\"name\":\"Robert\""); + var context = GetActionContext(); + var result = new JsonResult(new ModelWithSerializationError()); + var executor = CreateExcutor(); + + // Act + try + { + await executor.ExecuteAsync(context, result); + } + catch (JsonSerializationException serializerException) + { + var expectedException = Assert.IsType(serializerException.InnerException); + Assert.Equal("Property Age has not been implemented", expectedException.Message); + } + + // Assert + var written = GetWrittenBytes(context.HttpContext); + Assert.Equal(expected, written); + } + private static JsonResultExecutor CreateExcutor() { return new JsonResultExecutor( @@ -188,5 +214,17 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal context.Response.Body.Seek(0, SeekOrigin.Begin); return Assert.IsType(context.Response.Body).ToArray(); } + + private class ModelWithSerializationError + { + public string Name { get; } = "Robert"; + public int Age + { + get + { + throw new NotImplementedException($"Property {nameof(Age)} has not been implemented"); + } + } + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonOutputFormatterTests.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonOutputFormatterTests.cs index 84075cd15a..b71ed5a3a2 100644 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonOutputFormatterTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonOutputFormatterTests.cs @@ -313,7 +313,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters { "This is a test 激光這兩個字是甚麼意思 string written using shift_jis", "shift_jis", false }, #elif NETCOREAPP2_0 #else -#error target frameworks needs to be updated. +#error target frameworks needs to be updated. #endif { "This is a test æøå string written using iso-8859-1", "iso-8859-1", false }, }; @@ -327,7 +327,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters } #elif NETCOREAPP2_0 #else -#error target frameworks needs to be updated. +#error target frameworks needs to be updated. #endif return data; @@ -369,6 +369,38 @@ namespace Microsoft.AspNetCore.Mvc.Formatters Assert.Equal(expectedData, actualData); } + [Fact] + public async Task ErrorDuringSerialization_DoesNotCloseTheBrackets() + { + // Arrange + var expectedOutput = "{\"name\":\"Robert\""; + var outputFormatterContext = GetOutputFormatterContext( + new ModelWithSerializationError(), + typeof(ModelWithSerializationError)); + var serializerSettings = JsonSerializerSettingsProvider.CreateSerializerSettings(); + var jsonFormatter = new JsonOutputFormatter(serializerSettings, ArrayPool.Shared); + + // Act + try + { + await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext, Encoding.UTF8); + } + catch (JsonSerializationException serializerException) + { + var expectedException = Assert.IsType(serializerException.InnerException); + Assert.Equal("Property Age has not been implemented", expectedException.Message); + } + + // Assert + var body = outputFormatterContext.HttpContext.Response.Body; + + Assert.NotNull(body); + body.Position = 0; + + var content = new StreamReader(body, Encoding.UTF8).ReadToEnd(); + Assert.Equal(expectedOutput, content); + } + private static Encoding CreateOrGetSupportedEncoding( JsonOutputFormatter formatter, string encodingAsString, @@ -467,5 +499,17 @@ namespace Microsoft.AspNetCore.Mvc.Formatters public string FullName { get; set; } } + + private class ModelWithSerializationError + { + public string Name { get; } = "Robert"; + public int Age + { + get + { + throw new NotImplementedException($"Property {nameof(Age)} has not been implemented"); + } + } + } } } \ No newline at end of file