From e42f979ccab61f1b5d14b585439071e244c1b644 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Sun, 21 Apr 2019 12:10:47 -0700 Subject: [PATCH] Add a System.Text.Json based IJsonHelper (#9566) * Add a System.Text.Json based IJsonHelper Fixes https://github.com/aspnet/AspNetCore/issues/9564 --- ...tonsoftJsonMvcCoreBuilderExtensionsTest.cs | 2 +- ....AspNetCore.Mvc.NewtonsoftJson.Test.csproj | 1 + .../test/NewtonsoftJsonHelperTest.cs | 59 +++++----- ...MvcViewFeaturesMvcCoreBuilderExtensions.cs | 2 +- .../src/Rendering/DefaultJsonHelper.cs | 24 ---- .../src/Rendering/SystemTextJsonHelper.cs | 29 +++++ .../test/Rendering/JsonHelperTestBase.cs | 103 ++++++++++++++++++ .../Rendering/SystemTextJsonHelperTest.cs | 19 ++++ 8 files changed, 185 insertions(+), 54 deletions(-) delete mode 100644 src/Mvc/Mvc.ViewFeatures/src/Rendering/DefaultJsonHelper.cs create mode 100644 src/Mvc/Mvc.ViewFeatures/src/Rendering/SystemTextJsonHelper.cs create mode 100644 src/Mvc/Mvc.ViewFeatures/test/Rendering/JsonHelperTestBase.cs create mode 100644 src/Mvc/Mvc.ViewFeatures/test/Rendering/SystemTextJsonHelperTest.cs diff --git a/src/Mvc/Mvc.NewtonsoftJson/test/DependencyInjection/NewtonsoftJsonMvcCoreBuilderExtensionsTest.cs b/src/Mvc/Mvc.NewtonsoftJson/test/DependencyInjection/NewtonsoftJsonMvcCoreBuilderExtensionsTest.cs index a1bb5cf55f..a041352415 100644 --- a/src/Mvc/Mvc.NewtonsoftJson/test/DependencyInjection/NewtonsoftJsonMvcCoreBuilderExtensionsTest.cs +++ b/src/Mvc/Mvc.NewtonsoftJson/test/DependencyInjection/NewtonsoftJsonMvcCoreBuilderExtensionsTest.cs @@ -36,7 +36,7 @@ namespace Microsoft.Extensions.DependencyInjection { // Arrange var services = new ServiceCollection() - .AddSingleton(); + .AddSingleton(); // Act NewtonsoftJsonMvcCoreBuilderExtensions.AddServicesCore(services); diff --git a/src/Mvc/Mvc.NewtonsoftJson/test/Microsoft.AspNetCore.Mvc.NewtonsoftJson.Test.csproj b/src/Mvc/Mvc.NewtonsoftJson/test/Microsoft.AspNetCore.Mvc.NewtonsoftJson.Test.csproj index b97b8afbe3..734fc8d429 100644 --- a/src/Mvc/Mvc.NewtonsoftJson/test/Microsoft.AspNetCore.Mvc.NewtonsoftJson.Test.csproj +++ b/src/Mvc/Mvc.NewtonsoftJson/test/Microsoft.AspNetCore.Mvc.NewtonsoftJson.Test.csproj @@ -16,6 +16,7 @@ + diff --git a/src/Mvc/Mvc.NewtonsoftJson/test/NewtonsoftJsonHelperTest.cs b/src/Mvc/Mvc.NewtonsoftJson/test/NewtonsoftJsonHelperTest.cs index 8a7963460e..a4ad04b9e8 100644 --- a/src/Mvc/Mvc.NewtonsoftJson/test/NewtonsoftJsonHelperTest.cs +++ b/src/Mvc/Mvc.NewtonsoftJson/test/NewtonsoftJsonHelperTest.cs @@ -3,6 +3,7 @@ using System.Buffers; using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.Extensions.Options; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; @@ -10,31 +11,8 @@ using Xunit; namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson { - public class NewtonsoftJsonHelperTest + public class NewtonsoftJsonHelperTest : JsonHelperTestBase { - [Fact] - public void Serialize_EscapesHtmlByDefault() - { - // Arrange - var options = new MvcNewtonsoftJsonOptions(); - options.SerializerSettings.StringEscapeHandling = StringEscapeHandling.EscapeNonAscii; - var helper = new NewtonsoftJsonHelper( - Options.Create(options), - 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() { @@ -65,10 +43,7 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson public void Serialize_UsesHtmlSafeVersionOfPassedInSettings() { // Arrange - var options = new MvcNewtonsoftJsonOptions(); - var helper = new NewtonsoftJsonHelper( - Options.Create(options), - ArrayPool.Shared); + var helper = GetJsonHelper(); var obj = new { FullHtml = "John Doe" @@ -91,5 +66,33 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson var htmlString = Assert.IsType(result); Assert.Equal(expectedOutput, htmlString.ToString()); } + + [Fact] + public override void Serialize_WithNonAsciiChars() + { + // Arrange + var helper = GetJsonHelper(); + var obj = new + { + HTML = $"Hello pingüino" + }; + var expectedOutput = $"{{\"html\":\"{obj.HTML}\"}}"; + + // Act + var result = helper.Serialize(obj); + + // Assert + var htmlString = Assert.IsType(result); + Assert.Equal(expectedOutput, htmlString.ToString()); + } + + protected override IJsonHelper GetJsonHelper() + { + var options = new MvcNewtonsoftJsonOptions(); + var helper = new NewtonsoftJsonHelper( + Options.Create(options), + ArrayPool.Shared); + return helper; + } } } diff --git a/src/Mvc/Mvc.ViewFeatures/src/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs b/src/Mvc/Mvc.ViewFeatures/src/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs index fbef47c4c4..4b21b8ea65 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs @@ -172,7 +172,7 @@ namespace Microsoft.Extensions.DependencyInjection services.TryAddSingleton(s => s.GetRequiredService()); services.TryAddSingleton(); - services.TryAddSingleton(); + services.TryAddSingleton(); // // View Components diff --git a/src/Mvc/Mvc.ViewFeatures/src/Rendering/DefaultJsonHelper.cs b/src/Mvc/Mvc.ViewFeatures/src/Rendering/DefaultJsonHelper.cs deleted file mode 100644 index df0646a686..0000000000 --- a/src/Mvc/Mvc.ViewFeatures/src/Rendering/DefaultJsonHelper.cs +++ /dev/null @@ -1,24 +0,0 @@ - -// 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 Microsoft.AspNetCore.Html; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.AspNetCore.Mvc.Rendering -{ - internal class DefaultJsonHelper : IJsonHelper - { - /// - public IHtmlContent Serialize(object value) - { - throw new InvalidOperationException(Core.Resources.FormatReferenceToNewtonsoftJsonRequired( - $"{nameof(IJsonHelper)}.{nameof(Serialize)}", - "Microsoft.AspNetCore.Mvc.NewtonsoftJson", - nameof(IMvcBuilder), - "AddNewtonsoftJson", - "ConfigureServices(...)")); - } - } -} diff --git a/src/Mvc/Mvc.ViewFeatures/src/Rendering/SystemTextJsonHelper.cs b/src/Mvc/Mvc.ViewFeatures/src/Rendering/SystemTextJsonHelper.cs new file mode 100644 index 0000000000..cff4844c77 --- /dev/null +++ b/src/Mvc/Mvc.ViewFeatures/src/Rendering/SystemTextJsonHelper.cs @@ -0,0 +1,29 @@ + +// 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.Serialization; +using Microsoft.AspNetCore.Html; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.Rendering +{ + internal class SystemTextJsonHelper : IJsonHelper + { + private readonly MvcOptions _mvcOptions; + + public SystemTextJsonHelper(IOptions mvcOptions) + { + _mvcOptions = mvcOptions.Value; + } + + /// + public IHtmlContent Serialize(object value) + { + // JsonSerializer always encodes non-ASCII chars, so we do not need + // to do anything special with the SerializerOptions + var json = JsonSerializer.ToString(value, _mvcOptions.SerializerOptions); + return new HtmlString(json); + } + } +} diff --git a/src/Mvc/Mvc.ViewFeatures/test/Rendering/JsonHelperTestBase.cs b/src/Mvc/Mvc.ViewFeatures/test/Rendering/JsonHelperTestBase.cs new file mode 100644 index 0000000000..90d994af41 --- /dev/null +++ b/src/Mvc/Mvc.ViewFeatures/test/Rendering/JsonHelperTestBase.cs @@ -0,0 +1,103 @@ +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Html; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Rendering +{ + public abstract class JsonHelperTestBase + { + protected abstract IJsonHelper GetJsonHelper(); + + [Fact] + public 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 void Serialize_WithNullValue() + { + // Arrange + var helper = GetJsonHelper(); + var expectedOutput = "null"; + + // Act + var result = helper.Serialize(value: null); + + // Assert + var htmlString = Assert.IsType(result); + Assert.Equal(expectedOutput, htmlString.ToString()); + } + + [Fact] + public void Serialize_WithControlCharacters() + { + // Arrange + var helper = GetJsonHelper(); + var obj = new + { + HTML = $"Hello \n \b \a world" + }; + var expectedOutput = "{\"html\":\"Hello \\n \\b \\u0007 world\"}"; + + // Act + var result = helper.Serialize(obj); + + // Assert + var htmlString = Assert.IsType(result); + Assert.Equal(expectedOutput, htmlString.ToString()); + } + + [Fact] + public virtual void Serialize_WithNonAsciiChars() + { + // Arrange + var helper = GetJsonHelper(); + var obj = new + { + HTML = $"Hello pingüino" + }; + var expectedOutput = "{\"html\":\"Hello ping\\u00fcino\"}"; + + // Act + var result = helper.Serialize(obj); + + // Assert + var htmlString = Assert.IsType(result); + Assert.Equal(expectedOutput, htmlString.ToString()); + } + + [Fact] + public void Serialize_WithHTMLEntities() + { + // Arrange + var helper = GetJsonHelper(); + var obj = new + { + HTML = $"Hello   <John>" + }; + var expectedOutput = "{\"html\":\"Hello \\u0026nbsp; \\u0026lt;John\\u0026gt;\"}"; + + // 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 new file mode 100644 index 0000000000..4c07e933fd --- /dev/null +++ b/src/Mvc/Mvc.ViewFeatures/test/Rendering/SystemTextJsonHelperTest.cs @@ -0,0 +1,19 @@ + +// 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.Serialization; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.Rendering +{ + public class SystemTextJsonHelperTest : JsonHelperTestBase + { + protected override IJsonHelper GetJsonHelper() + { + var mvcOptions = new MvcOptions { SerializerOptions = { PropertyNamingPolicy = JsonNamingPolicy.CamelCase } }; + return new SystemTextJsonHelper(Options.Create(mvcOptions)); + } + } +}