diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonOutputFormatter.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonOutputFormatter.cs
index 8ecdebb2e7..744d558680 100644
--- a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonOutputFormatter.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonOutputFormatter.cs
@@ -3,6 +3,7 @@
using System;
using System.Buffers;
+using System.ComponentModel;
using System.IO;
using System.Text;
using System.Threading.Tasks;
@@ -62,6 +63,16 @@ public JsonOutputFormatter(JsonSerializerSettings serializerSettings, ArrayPool<
///
protected JsonSerializerSettings SerializerSettings { get; }
+ ///
+ /// Gets the used to configure the .
+ ///
+ ///
+ /// Any modifications to the object after this
+ /// has been used will have no effect.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public JsonSerializerSettings PublicSerializerSettings => SerializerSettings;
+
///
/// Writes the given as JSON using the given
/// .
diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/JsonHelper.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/JsonHelper.cs
index 4fcdfc3579..bda3a50784 100644
--- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/JsonHelper.cs
+++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/JsonHelper.cs
@@ -17,6 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
///
public class JsonHelper : IJsonHelper
{
+ private const string AllowJsonHtml = "Switch.Microsoft.AspNetCore.Mvc.AllowJsonHtml";
private readonly JsonOutputFormatter _jsonOutputFormatter;
private readonly ArrayPool _charPool;
@@ -46,7 +47,15 @@ public JsonHelper(JsonOutputFormatter jsonOutputFormatter, ArrayPool charP
///
public IHtmlContent Serialize(object value)
{
- return SerializeInternal(_jsonOutputFormatter, value);
+ if (AppContext.TryGetSwitch(AllowJsonHtml, out var allowJsonHtml) && allowJsonHtml)
+ {
+ return SerializeInternal(_jsonOutputFormatter, value);
+ }
+
+ var settings = ShallowCopy(_jsonOutputFormatter.PublicSerializerSettings);
+ settings.StringEscapeHandling = StringEscapeHandling.EscapeHtml;
+
+ return Serialize(value, settings);
}
///
@@ -69,5 +78,42 @@ private IHtmlContent SerializeInternal(JsonOutputFormatter jsonOutputFormatter,
return new HtmlString(stringWriter.ToString());
}
+
+ private static JsonSerializerSettings ShallowCopy(JsonSerializerSettings settings)
+ {
+ var copiedSettings = new JsonSerializerSettings
+ {
+ FloatParseHandling = settings.FloatParseHandling,
+ FloatFormatHandling = settings.FloatFormatHandling,
+ DateParseHandling = settings.DateParseHandling,
+ DateTimeZoneHandling = settings.DateTimeZoneHandling,
+ DateFormatHandling = settings.DateFormatHandling,
+ Formatting = settings.Formatting,
+ MaxDepth = settings.MaxDepth,
+ DateFormatString = settings.DateFormatString,
+ Context = settings.Context,
+ Error = settings.Error,
+ SerializationBinder = settings.SerializationBinder,
+ TraceWriter = settings.TraceWriter,
+ Culture = settings.Culture,
+ ReferenceResolverProvider = settings.ReferenceResolverProvider,
+ EqualityComparer = settings.EqualityComparer,
+ ContractResolver = settings.ContractResolver,
+ ConstructorHandling = settings.ConstructorHandling,
+ TypeNameAssemblyFormatHandling = settings.TypeNameAssemblyFormatHandling,
+ MetadataPropertyHandling = settings.MetadataPropertyHandling,
+ TypeNameHandling = settings.TypeNameHandling,
+ PreserveReferencesHandling = settings.PreserveReferencesHandling,
+ Converters = settings.Converters,
+ DefaultValueHandling = settings.DefaultValueHandling,
+ NullValueHandling = settings.NullValueHandling,
+ ObjectCreationHandling = settings.ObjectCreationHandling,
+ MissingMemberHandling = settings.MissingMemberHandling,
+ ReferenceLoopHandling = settings.ReferenceLoopHandling,
+ CheckAdditionalContent = settings.CheckAdditionalContent,
+ };
+
+ return copiedSettings;
+ }
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs
index 60697caf63..21829b3937 100644
--- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs
+++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs
@@ -245,12 +245,10 @@ public async Task ActionWithRequireHttps_AllowsHttpsRequests(string method)
public async Task JsonHelper_RendersJson_WithCamelCaseNames()
{
// Arrange
- var json = "{\"id\":9000,\"fullName\":\"John Smith\"}";
- var expectedBody = string.Format(
- @"",
- json);
+ var expectedBody =
+@"";
// Act
var response = await Client.GetAsync("Home/JsonHelperInView");
diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/JsonHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/JsonHelperTest.cs
new file mode 100644
index 0000000000..bcc3e4b538
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/JsonHelperTest.cs
@@ -0,0 +1,68 @@
+// 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.Buffers;
+using Microsoft.AspNetCore.Html;
+using Microsoft.AspNetCore.Mvc.Formatters;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Mvc.ViewFeatures
+{
+ public class JsonHelperTest
+ {
+ [Fact]
+ public void Serialize_EscapesHtmlByDefault()
+ {
+ // Arrange
+ var settings = new JsonSerializerSettings()
+ {
+ StringEscapeHandling = StringEscapeHandling.EscapeNonAscii,
+ };
+ var helper = new JsonHelper(
+ new JsonOutputFormatter(settings, ArrayPool.Shared),
+ ArrayPool.Shared);
+ 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 void Serialize_MaintainsSettingsAndEscapesHtml()
+ {
+ // Arrange
+ var settings = new JsonSerializerSettings()
+ {
+ ContractResolver = new DefaultContractResolver
+ {
+ NamingStrategy = new CamelCaseNamingStrategy(),
+ },
+ };
+ var helper = new JsonHelper(
+ new JsonOutputFormatter(settings, ArrayPool.Shared),
+ ArrayPool.Shared);
+ var obj = new
+ {
+ FullHtml = "John Doe"
+ };
+ var expectedOutput = "{\"fullHtml\":\"\\u003cb\\u003eJohn Doe\\u003c/b\\u003e\"}";
+
+ // Act
+ var result = helper.Serialize(obj);
+
+ // Assert
+ var htmlString = Assert.IsType(result);
+ Assert.Equal(expectedOutput, htmlString.ToString());
+ }
+ }
+}