diff --git a/src/Mvc/Mvc.Core/ref/Microsoft.AspNetCore.Mvc.Core.netcoreapp3.0.cs b/src/Mvc/Mvc.Core/ref/Microsoft.AspNetCore.Mvc.Core.netcoreapp3.0.cs
index 07646edce5..9f3472518d 100644
--- a/src/Mvc/Mvc.Core/ref/Microsoft.AspNetCore.Mvc.Core.netcoreapp3.0.cs
+++ b/src/Mvc/Mvc.Core/ref/Microsoft.AspNetCore.Mvc.Core.netcoreapp3.0.cs
@@ -2210,7 +2210,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
}
public partial class SystemTextJsonOutputFormatter : Microsoft.AspNetCore.Mvc.Formatters.TextOutputFormatter
{
- public SystemTextJsonOutputFormatter(Microsoft.AspNetCore.Mvc.JsonOptions options) { }
+ public SystemTextJsonOutputFormatter(System.Text.Json.JsonSerializerOptions jsonSerializerOptions) { }
public System.Text.Json.JsonSerializerOptions SerializerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
[System.Diagnostics.DebuggerStepThroughAttribute]
public sealed override System.Threading.Tasks.Task WriteResponseBodyAsync(Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterWriteContext context, System.Text.Encoding selectedEncoding) { throw null; }
diff --git a/src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonOutputFormatter.cs b/src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonOutputFormatter.cs
index 861f277e48..664615fa8d 100644
--- a/src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonOutputFormatter.cs
+++ b/src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonOutputFormatter.cs
@@ -4,6 +4,7 @@
using System;
using System.IO;
using System.Text;
+using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@@ -17,14 +18,13 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
///
public class SystemTextJsonOutputFormatter : TextOutputFormatter
{
-
///
/// Initializes a new instance.
///
- /// The .
- public SystemTextJsonOutputFormatter(JsonOptions options)
+ /// The .
+ public SystemTextJsonOutputFormatter(JsonSerializerOptions jsonSerializerOptions)
{
- SerializerOptions = options.JsonSerializerOptions;
+ SerializerOptions = jsonSerializerOptions;
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
@@ -33,6 +33,19 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyJsonSyntax);
}
+ internal static SystemTextJsonOutputFormatter CreateFormatter(JsonOptions jsonOptions)
+ {
+ var jsonSerializerOptions = jsonOptions.JsonSerializerOptions;
+
+ if (jsonSerializerOptions.Encoder is null)
+ {
+ // If the user hasn't explicitly configured the encoder, use the less strict encoder that does not encode all non-ASCII characters.
+ jsonSerializerOptions = jsonSerializerOptions.Copy(JavaScriptEncoder.UnsafeRelaxedJsonEscaping);
+ }
+
+ return new SystemTextJsonOutputFormatter(jsonSerializerOptions);
+ }
+
///
/// Gets the used to configure the .
///
diff --git a/src/Mvc/Mvc.Core/src/Infrastructure/JsonSerializerOptionsCopyConstructor.cs b/src/Mvc/Mvc.Core/src/Infrastructure/JsonSerializerOptionsCopyConstructor.cs
new file mode 100644
index 0000000000..f378aec55e
--- /dev/null
+++ b/src/Mvc/Mvc.Core/src/Infrastructure/JsonSerializerOptionsCopyConstructor.cs
@@ -0,0 +1,36 @@
+// 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.Text.Encodings.Web;
+
+namespace System.Text.Json
+{
+ internal static class JsonSerializerOptionsCopyConstructor
+ {
+ public static JsonSerializerOptions Copy(this JsonSerializerOptions serializerOptions, JavaScriptEncoder encoder)
+ {
+ var copiedOptions = new JsonSerializerOptions
+ {
+ AllowTrailingCommas = serializerOptions.AllowTrailingCommas,
+ DefaultBufferSize = serializerOptions.DefaultBufferSize,
+ DictionaryKeyPolicy = serializerOptions.DictionaryKeyPolicy,
+ IgnoreNullValues = serializerOptions.IgnoreNullValues,
+ IgnoreReadOnlyProperties = serializerOptions.IgnoreReadOnlyProperties,
+ MaxDepth = serializerOptions.MaxDepth,
+ PropertyNameCaseInsensitive = serializerOptions.PropertyNameCaseInsensitive,
+ PropertyNamingPolicy = serializerOptions.PropertyNamingPolicy,
+ ReadCommentHandling = serializerOptions.ReadCommentHandling,
+ WriteIndented = serializerOptions.WriteIndented
+ };
+
+ for (var i = 0; i < serializerOptions.Converters.Count; i++)
+ {
+ copiedOptions.Converters.Add(serializerOptions.Converters[i]);
+ }
+
+ copiedOptions.Encoder = encoder;
+
+ return copiedOptions;
+ }
+ }
+}
diff --git a/src/Mvc/Mvc.Core/src/Infrastructure/MvcCoreMvcOptionsSetup.cs b/src/Mvc/Mvc.Core/src/Infrastructure/MvcCoreMvcOptionsSetup.cs
index cbc141fe76..1f246bdbd5 100644
--- a/src/Mvc/Mvc.Core/src/Infrastructure/MvcCoreMvcOptionsSetup.cs
+++ b/src/Mvc/Mvc.Core/src/Infrastructure/MvcCoreMvcOptionsSetup.cs
@@ -87,7 +87,9 @@ namespace Microsoft.AspNetCore.Mvc
options.OutputFormatters.Add(new HttpNoContentOutputFormatter());
options.OutputFormatters.Add(new StringOutputFormatter());
options.OutputFormatters.Add(new StreamOutputFormatter());
- options.OutputFormatters.Add(new SystemTextJsonOutputFormatter(_jsonOptions.Value));
+
+ var jsonOutputFormatter = SystemTextJsonOutputFormatter.CreateFormatter(_jsonOptions.Value);
+ options.OutputFormatters.Add(jsonOutputFormatter);
// Set up ValueProviders
options.ValueProviderFactories.Add(new FormValueProviderFactory());
diff --git a/src/Mvc/Mvc.Core/test/CreatedAtActionResultTests.cs b/src/Mvc/Mvc.Core/test/CreatedAtActionResultTests.cs
index be493c9eda..3b559992e5 100644
--- a/src/Mvc/Mvc.Core/test/CreatedAtActionResultTests.cs
+++ b/src/Mvc/Mvc.Core/test/CreatedAtActionResultTests.cs
@@ -91,7 +91,7 @@ namespace Microsoft.AspNetCore.Mvc
{
var options = Options.Create(new MvcOptions());
options.Value.OutputFormatters.Add(new StringOutputFormatter());
- options.Value.OutputFormatters.Add(new SystemTextJsonOutputFormatter(new JsonOptions()));
+ options.Value.OutputFormatters.Add(SystemTextJsonOutputFormatter.CreateFormatter(new JsonOptions()));
var services = new ServiceCollection();
services.AddSingleton>(new ObjectResultExecutor(
diff --git a/src/Mvc/Mvc.Core/test/CreatedAtRouteResultTests.cs b/src/Mvc/Mvc.Core/test/CreatedAtRouteResultTests.cs
index 50eb7ba12b..f6cf7d583a 100644
--- a/src/Mvc/Mvc.Core/test/CreatedAtRouteResultTests.cs
+++ b/src/Mvc/Mvc.Core/test/CreatedAtRouteResultTests.cs
@@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.Mvc
{
var options = Options.Create(new MvcOptions());
options.Value.OutputFormatters.Add(new StringOutputFormatter());
- options.Value.OutputFormatters.Add(new SystemTextJsonOutputFormatter(new JsonOptions()));
+ options.Value.OutputFormatters.Add(SystemTextJsonOutputFormatter.CreateFormatter(new JsonOptions()));
var services = new ServiceCollection();
services.AddSingleton>(new ObjectResultExecutor(
diff --git a/src/Mvc/Mvc.Core/test/CreatedResultTests.cs b/src/Mvc/Mvc.Core/test/CreatedResultTests.cs
index 22e6b13637..56db1771c7 100644
--- a/src/Mvc/Mvc.Core/test/CreatedResultTests.cs
+++ b/src/Mvc/Mvc.Core/test/CreatedResultTests.cs
@@ -92,7 +92,7 @@ namespace Microsoft.AspNetCore.Mvc
{
var options = Options.Create(new MvcOptions());
options.Value.OutputFormatters.Add(new StringOutputFormatter());
- options.Value.OutputFormatters.Add(new SystemTextJsonOutputFormatter(new JsonOptions()));
+ options.Value.OutputFormatters.Add(SystemTextJsonOutputFormatter.CreateFormatter(new JsonOptions()));
var services = new ServiceCollection();
services.AddSingleton>(new ObjectResultExecutor(
diff --git a/src/Mvc/Mvc.Core/test/Formatters/FormatFilterTest.cs b/src/Mvc/Mvc.Core/test/Formatters/FormatFilterTest.cs
index fea2967990..3dd386d314 100644
--- a/src/Mvc/Mvc.Core/test/Formatters/FormatFilterTest.cs
+++ b/src/Mvc/Mvc.Core/test/Formatters/FormatFilterTest.cs
@@ -465,7 +465,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
// Set up default output formatters.
MvcOptions.OutputFormatters.Add(new HttpNoContentOutputFormatter());
MvcOptions.OutputFormatters.Add(new StringOutputFormatter());
- MvcOptions.OutputFormatters.Add(new SystemTextJsonOutputFormatter(new JsonOptions()));
+ MvcOptions.OutputFormatters.Add(SystemTextJsonOutputFormatter.CreateFormatter(new JsonOptions()));
// Set up default mapping for json extensions to content type
MvcOptions.FormatterMappings.SetMediaTypeMappingForFormat(
diff --git a/src/Mvc/Mvc.Core/test/Formatters/JsonOutputFormatterTestBase.cs b/src/Mvc/Mvc.Core/test/Formatters/JsonOutputFormatterTestBase.cs
index 0467d0e51c..8990b1cb9c 100644
--- a/src/Mvc/Mvc.Core/test/Formatters/JsonOutputFormatterTestBase.cs
+++ b/src/Mvc/Mvc.Core/test/Formatters/JsonOutputFormatterTestBase.cs
@@ -76,6 +76,71 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
}
}
+ [Theory]
+ [MemberData(nameof(WriteCorrectCharacterEncoding))]
+ public async Task WriteToStreamAsync_UsesCorrectCharacterEncoding(
+ string content,
+ string encodingAsString,
+ bool isDefaultEncoding)
+ {
+ // Arrange
+ var formatter = GetOutputFormatter();
+ var expectedContent = "\"" + content + "\"";
+ var mediaType = MediaTypeHeaderValue.Parse(string.Format("application/json; charset={0}", encodingAsString));
+ var encoding = CreateOrGetSupportedEncoding(formatter, encodingAsString, isDefaultEncoding);
+
+
+ var body = new MemoryStream();
+ var actionContext = GetActionContext(mediaType, body);
+
+ var outputFormatterContext = new OutputFormatterWriteContext(
+ actionContext.HttpContext,
+ new TestHttpResponseStreamWriterFactory().CreateWriter,
+ typeof(string),
+ content)
+ {
+ ContentType = new StringSegment(mediaType.ToString()),
+ };
+
+ // Act
+ await formatter.WriteResponseBodyAsync(outputFormatterContext, Encoding.GetEncoding(encodingAsString));
+
+ // Assert
+ var actualContent = encoding.GetString(body.ToArray());
+ Assert.Equal(expectedContent, actualContent, StringComparer.OrdinalIgnoreCase);
+ }
+
+ [Fact]
+ public async Task WriteResponseBodyAsync_Encodes()
+ {
+ // Arrange
+ var formatter = GetOutputFormatter();
+ var expectedContent = "{\"key\":\"Hello \\n Wörld\"}";
+ var content = new { key = "Hello \n Wörld" };
+
+ var mediaType = MediaTypeHeaderValue.Parse("application/json; charset=utf-8");
+ var encoding = CreateOrGetSupportedEncoding(formatter, "utf-8", isDefaultEncoding: true);
+
+ var body = new MemoryStream();
+ var actionContext = GetActionContext(mediaType, body);
+
+ var outputFormatterContext = new OutputFormatterWriteContext(
+ actionContext.HttpContext,
+ new TestHttpResponseStreamWriterFactory().CreateWriter,
+ typeof(string),
+ content)
+ {
+ ContentType = new StringSegment(mediaType.ToString()),
+ };
+
+ // Act
+ await formatter.WriteResponseBodyAsync(outputFormatterContext, Encoding.GetEncoding("utf-8"));
+
+ // Assert
+ var actualContent = encoding.GetString(body.ToArray());
+ Assert.Equal(expectedContent, actualContent);
+ }
+
[Fact]
public async Task ErrorDuringSerialization_DoesNotCloseTheBrackets()
{
diff --git a/src/Mvc/Mvc.Core/test/Formatters/SystemTextJsonOutputFormatterTest.cs b/src/Mvc/Mvc.Core/test/Formatters/SystemTextJsonOutputFormatterTest.cs
index 0a3d90e18b..cd245872ef 100644
--- a/src/Mvc/Mvc.Core/test/Formatters/SystemTextJsonOutputFormatterTest.cs
+++ b/src/Mvc/Mvc.Core/test/Formatters/SystemTextJsonOutputFormatterTest.cs
@@ -1,56 +1,13 @@
// 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.IO;
-using System.Text;
-using System.Text.Encodings.Web;
-using System.Threading.Tasks;
-using Microsoft.Extensions.Primitives;
-using Microsoft.Net.Http.Headers;
-using Xunit;
-
namespace Microsoft.AspNetCore.Mvc.Formatters
{
public class SystemTextJsonOutputFormatterTest : JsonOutputFormatterTestBase
{
protected override TextOutputFormatter GetOutputFormatter()
{
- return new SystemTextJsonOutputFormatter(new JsonOptions());
- }
-
- [Theory]
- [MemberData(nameof(WriteCorrectCharacterEncoding))]
- public async Task WriteToStreamAsync_UsesCorrectCharacterEncoding(
- string content,
- string encodingAsString,
- bool isDefaultEncoding)
- {
- // Arrange
- var formatter = GetOutputFormatter();
- var expectedContent = "\"" + JavaScriptEncoder.Default.Encode(content) + "\"";
- var mediaType = MediaTypeHeaderValue.Parse(string.Format("application/json; charset={0}", encodingAsString));
- var encoding = CreateOrGetSupportedEncoding(formatter, encodingAsString, isDefaultEncoding);
-
-
- var body = new MemoryStream();
- var actionContext = GetActionContext(mediaType, body);
-
- var outputFormatterContext = new OutputFormatterWriteContext(
- actionContext.HttpContext,
- new TestHttpResponseStreamWriterFactory().CreateWriter,
- typeof(string),
- content)
- {
- ContentType = new StringSegment(mediaType.ToString()),
- };
-
- // Act
- await formatter.WriteResponseBodyAsync(outputFormatterContext, Encoding.GetEncoding(encodingAsString));
-
- // Assert
- var actualContent = encoding.GetString(body.ToArray());
- Assert.Equal(expectedContent, actualContent, StringComparer.OrdinalIgnoreCase);
+ return SystemTextJsonOutputFormatter.CreateFormatter(new JsonOptions());
}
}
}
diff --git a/src/Mvc/Mvc.Core/test/HttpNotFoundObjectResultTest.cs b/src/Mvc/Mvc.Core/test/HttpNotFoundObjectResultTest.cs
index 42ef48b2f0..7956120d2a 100644
--- a/src/Mvc/Mvc.Core/test/HttpNotFoundObjectResultTest.cs
+++ b/src/Mvc/Mvc.Core/test/HttpNotFoundObjectResultTest.cs
@@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.Mvc
{
var options = Options.Create(new MvcOptions());
options.Value.OutputFormatters.Add(new StringOutputFormatter());
- options.Value.OutputFormatters.Add(new SystemTextJsonOutputFormatter(new JsonOptions()));
+ options.Value.OutputFormatters.Add(SystemTextJsonOutputFormatter.CreateFormatter(new JsonOptions()));
var services = new ServiceCollection();
services.AddSingleton>(new ObjectResultExecutor(
diff --git a/src/Mvc/Mvc.Core/test/HttpOkObjectResultTest.cs b/src/Mvc/Mvc.Core/test/HttpOkObjectResultTest.cs
index 49fac14fff..8735a8c89f 100644
--- a/src/Mvc/Mvc.Core/test/HttpOkObjectResultTest.cs
+++ b/src/Mvc/Mvc.Core/test/HttpOkObjectResultTest.cs
@@ -70,7 +70,7 @@ namespace Microsoft.AspNetCore.Mvc
{
var options = Options.Create(new MvcOptions());
options.Value.OutputFormatters.Add(new StringOutputFormatter());
- options.Value.OutputFormatters.Add(new SystemTextJsonOutputFormatter(new JsonOptions()));
+ options.Value.OutputFormatters.Add(SystemTextJsonOutputFormatter.CreateFormatter(new JsonOptions()));
var services = new ServiceCollection();
services.AddSingleton>(new ObjectResultExecutor(
diff --git a/src/Mvc/Mvc.ViewFeatures/src/Rendering/SystemTextJsonHelper.cs b/src/Mvc/Mvc.ViewFeatures/src/Rendering/SystemTextJsonHelper.cs
index df59a73215..a615d92675 100644
--- a/src/Mvc/Mvc.ViewFeatures/src/Rendering/SystemTextJsonHelper.cs
+++ b/src/Mvc/Mvc.ViewFeatures/src/Rendering/SystemTextJsonHelper.cs
@@ -2,6 +2,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.Text.Encodings.Web;
using System.Text.Json;
using Microsoft.AspNetCore.Html;
using Microsoft.Extensions.Options;
@@ -10,11 +11,11 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
{
internal class SystemTextJsonHelper : IJsonHelper
{
- private readonly JsonOptions _options;
+ private readonly JsonSerializerOptions _htmlSafeJsonSerializerOptions;
public SystemTextJsonHelper(IOptions options)
{
- _options = options.Value;
+ _htmlSafeJsonSerializerOptions = GetHtmlSafeSerializerOptions(options.Value.JsonSerializerOptions);
}
///
@@ -22,8 +23,18 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
{
// JsonSerializer always encodes non-ASCII chars, so we do not need
// to do anything special with the SerializerOptions
- var json = JsonSerializer.Serialize(value, _options.JsonSerializerOptions);
+ var json = JsonSerializer.Serialize(value, _htmlSafeJsonSerializerOptions);
return new HtmlString(json);
}
+
+ private static JsonSerializerOptions GetHtmlSafeSerializerOptions(JsonSerializerOptions serializerOptions)
+ {
+ if (serializerOptions.Encoder is null || serializerOptions.Encoder == JavaScriptEncoder.Default)
+ {
+ return serializerOptions;
+ }
+
+ return serializerOptions.Copy(JavaScriptEncoder.Default);
+ }
}
}
diff --git a/src/Mvc/Mvc.ViewFeatures/test/Rendering/JsonHelperTestBase.cs b/src/Mvc/Mvc.ViewFeatures/test/Rendering/JsonHelperTestBase.cs
index cfbabe735b..205a3479b6 100644
--- a/src/Mvc/Mvc.ViewFeatures/test/Rendering/JsonHelperTestBase.cs
+++ b/src/Mvc/Mvc.ViewFeatures/test/Rendering/JsonHelperTestBase.cs
@@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
// Assert
var htmlString = Assert.IsType(result);
- Assert.Equal(expectedOutput, htmlString.ToString());
+ Assert.Equal(expectedOutput, htmlString.ToString(), ignoreCase: true);
}
[Fact]
@@ -71,14 +71,14 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
{
HTML = $"Hello pingüino"
};
- var expectedOutput = "{\"html\":\"Hello ping\\u00fcino\"}";
+ var expectedOutput = "{\"html\":\"Hello pingüino\"}";
// Act
var result = helper.Serialize(obj);
// Assert
var htmlString = Assert.IsType(result);
- Assert.Equal(expectedOutput, htmlString.ToString());
+ Assert.Equal(expectedOutput, htmlString.ToString(), ignoreCase: true);
}
[Fact]
@@ -99,5 +99,25 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
var htmlString = Assert.IsType(result);
Assert.Equal(expectedOutput, htmlString.ToString());
}
+
+
+ [Fact]
+ public virtual void Serialize_WithHTMLNonAsciiAndControlChars()
+ {
+ // Arrange
+ var helper = GetJsonHelper();
+ var obj = new
+ {
+ HTML = "Hello \n pingüino"
+ };
+ var expectedOutput = "{\"html\":\"\\u003cb\\u003eHello \\n pingüino\\u003c/b\\u003e\"}";
+
+ // Act
+ var result = helper.Serialize(obj);
+
+ // Assert
+ var htmlString = Assert.IsType(result);
+ Assert.Equal(expectedOutput, htmlString.ToString());
+ }
}
}
diff --git a/src/Mvc/Mvc.ViewFeatures/test/Rendering/SystemTextJsonHelperTest.cs b/src/Mvc/Mvc.ViewFeatures/test/Rendering/SystemTextJsonHelperTest.cs
index ce03e5f633..e1174f360b 100644
--- a/src/Mvc/Mvc.ViewFeatures/test/Rendering/SystemTextJsonHelperTest.cs
+++ b/src/Mvc/Mvc.ViewFeatures/test/Rendering/SystemTextJsonHelperTest.cs
@@ -1,7 +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.Text.Json;
+using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Html;
using Microsoft.Extensions.Options;
using Xunit;
@@ -10,31 +10,13 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
{
public class SystemTextJsonHelperTest : JsonHelperTestBase
{
- protected override IJsonHelper GetJsonHelper()
+ protected override IJsonHelper GetJsonHelper() => GetJsonHelper(new JsonOptions());
+
+ private static IJsonHelper GetJsonHelper(JsonOptions options)
{
- var options = new JsonOptions() { JsonSerializerOptions = { PropertyNamingPolicy = JsonNamingPolicy.CamelCase } };
return new SystemTextJsonHelper(Options.Create(options));
}
- [Fact]
- public override void Serialize_EscapesHtmlByDefault()
- {
- // Arrange
- var helper = GetJsonHelper();
- var obj = new
- {
- HTML = "John Doe"
- };
- var expectedOutput = "{\"html\":\"\\u003Cb\\u003EJohn Doe\\u003C/b\\u003E\"}";
-
- // Act
- var result = helper.Serialize(obj);
-
- // Assert
- var htmlString = Assert.IsType(result);
- Assert.Equal(expectedOutput, htmlString.ToString());
- }
-
[Fact]
public override void Serialize_WithNonAsciiChars()
{
@@ -53,5 +35,56 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
var htmlString = Assert.IsType(result);
Assert.Equal(expectedOutput, htmlString.ToString());
}
+
+ [Fact]
+ public override void Serialize_WithHTMLNonAsciiAndControlChars()
+ {
+ // Arrange
+ var helper = GetJsonHelper();
+ var obj = new
+ {
+ HTML = "Hello \n pingüino"
+ };
+ var expectedOutput = "{\"html\":\"\\u003Cb\\u003EHello \\n ping\\u00FCino\\u003C/b\\u003E\"}";
+
+ // Act
+ var result = helper.Serialize(obj);
+
+ // Assert
+ var htmlString = Assert.IsType(result);
+ Assert.Equal(expectedOutput, htmlString.ToString());
+ }
+
+ [Fact]
+ public void Serialize_UsesOptionsConfiguredInTheProvider()
+ {
+ // Arrange
+ // This should use property-casing and indentation, but the result should be HTML-safe
+ var options = new JsonOptions
+ {
+ JsonSerializerOptions =
+ {
+ Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
+ PropertyNamingPolicy = null,
+ WriteIndented = true,
+ }
+ };
+ var helper = GetJsonHelper(options);
+ var obj = new
+ {
+ HTML = "John"
+ };
+ var expectedOutput =
+@"{
+ ""HTML"": ""\u003Cb\u003EJohn\u003C/b\u003E""
+}";
+
+ // Act
+ var result = helper.Serialize(obj);
+
+ // Assert
+ var htmlString = Assert.IsType(result);
+ Assert.Equal(expectedOutput, htmlString.ToString());
+ }
}
}
diff --git a/src/Mvc/test/Mvc.FunctionalTests/JsonOutputFormatterTestBase.cs b/src/Mvc/test/Mvc.FunctionalTests/JsonOutputFormatterTestBase.cs
index c4f7ee9dcc..c5c4ae51fd 100644
--- a/src/Mvc/test/Mvc.FunctionalTests/JsonOutputFormatterTestBase.cs
+++ b/src/Mvc/test/Mvc.FunctionalTests/JsonOutputFormatterTestBase.cs
@@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
-using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Http;
@@ -11,6 +10,7 @@ using System.Text;
using System.Threading.Tasks;
using FormatterWebSite.Controllers;
using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc.Testing;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Xunit;
@@ -21,13 +21,14 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{
protected JsonOutputFormatterTestBase(MvcTestFixture fixture)
{
- var factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(ConfigureWebHostBuilder);
- Client = factory.CreateDefaultClient();
+ Factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(ConfigureWebHostBuilder);
+ Client = Factory.CreateDefaultClient();
}
private static void ConfigureWebHostBuilder(IWebHostBuilder builder) =>
builder.UseStartup();
+ public WebApplicationFactory Factory { get; }
public HttpClient Client { get; }
[Fact]
@@ -100,6 +101,17 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal("\"Hello Mr. 🦊\"", await response.Content.ReadAsStringAsync());
}
+ [Fact]
+ public virtual async Task Formatting_StringValueWithNonAsciiCharacters()
+ {
+ // Act
+ var response = await Client.GetAsync($"/JsonOutputFormatter/{nameof(JsonOutputFormatterController.StringWithNonAsciiContent)}");
+
+ // Assert
+ await response.AssertStatusCodeAsync(HttpStatusCode.OK);
+ Assert.Equal("\"Une bête de cirque\"", await response.Content.ReadAsStringAsync());
+ }
+
[Fact]
public virtual async Task Formatting_SimpleModel()
{
diff --git a/src/Mvc/test/Mvc.FunctionalTests/SystemTextJsonOutputFormatterTest.cs b/src/Mvc/test/Mvc.FunctionalTests/SystemTextJsonOutputFormatterTest.cs
index d508bfe731..d0af74ada1 100644
--- a/src/Mvc/test/Mvc.FunctionalTests/SystemTextJsonOutputFormatterTest.cs
+++ b/src/Mvc/test/Mvc.FunctionalTests/SystemTextJsonOutputFormatterTest.cs
@@ -2,8 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Net;
+using System.Text.Encodings.Web;
using System.Threading.Tasks;
using FormatterWebSite.Controllers;
+using Microsoft.Extensions.DependencyInjection;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
@@ -15,7 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{
}
- [Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/11459")]
+ [Fact]
public override Task SerializableErrorIsReturnedInExpectedFormat() => base.SerializableErrorIsReturnedInExpectedFormat();
[Fact]
@@ -29,6 +31,25 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal("\"Hello Mr. \\uD83E\\uDD8A\"", await response.Content.ReadAsStringAsync());
}
+ [Fact]
+ public async Task Formatting_WithCustomEncoder()
+ {
+ // Arrange
+ static void ConfigureServices(IServiceCollection serviceCollection)
+ {
+ serviceCollection.AddControllers()
+ .AddJsonOptions(o => o.JsonSerializerOptions.Encoder = JavaScriptEncoder.Default);
+ }
+ var client = Factory.WithWebHostBuilder(c => c.ConfigureServices(ConfigureServices)).CreateClient();
+
+ // Act
+ var response = await client.GetAsync($"/JsonOutputFormatter/{nameof(JsonOutputFormatterController.StringWithNonAsciiContent)}");
+
+ // Assert
+ await response.AssertStatusCodeAsync(HttpStatusCode.OK);
+ Assert.Equal("\"Une b\\u00EAte de cirque\"", await response.Content.ReadAsStringAsync());
+ }
+
[Fact]
public override Task Formatting_DictionaryType() => base.Formatting_DictionaryType();
diff --git a/src/Mvc/test/WebSites/FormatterWebSite/Controllers/JsonOutputFormatterController.cs b/src/Mvc/test/WebSites/FormatterWebSite/Controllers/JsonOutputFormatterController.cs
index bf8b004553..67ae87d482 100644
--- a/src/Mvc/test/WebSites/FormatterWebSite/Controllers/JsonOutputFormatterController.cs
+++ b/src/Mvc/test/WebSites/FormatterWebSite/Controllers/JsonOutputFormatterController.cs
@@ -20,6 +20,9 @@ namespace FormatterWebSite.Controllers
[HttpGet]
public ActionResult StringWithUnicodeResult() => "Hello Mr. 🦊";
+ [HttpGet]
+ public ActionResult StringWithNonAsciiContent() => "Une bête de cirque";
+
[HttpGet]
public ActionResult SimpleModelResult() =>
new SimpleModel { Id = 10, Name = "Test", StreetName = "Some street" };