Add a System.Text.Json based IJsonHelper (#9566)

* Add a System.Text.Json based IJsonHelper

Fixes https://github.com/aspnet/AspNetCore/issues/9564
This commit is contained in:
Pranav K 2019-04-21 12:10:47 -07:00 committed by GitHub
parent c300c8c97c
commit e42f979cca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 185 additions and 54 deletions

View File

@ -36,7 +36,7 @@ namespace Microsoft.Extensions.DependencyInjection
{
// Arrange
var services = new ServiceCollection()
.AddSingleton<IJsonHelper, DefaultJsonHelper>();
.AddSingleton<IJsonHelper, SystemTextJsonHelper>();
// Act
NewtonsoftJsonMvcCoreBuilderExtensions.AddServicesCore(services);

View File

@ -16,6 +16,7 @@
<Compile Include="..\..\Mvc.Core\test\Formatters\JsonOutputFormatterTestBase.cs" />
<Compile Include="..\..\Mvc.Core\test\Infrastructure\JsonResultExecutorTestBase.cs" />
<Compile Include="..\..\Mvc.ViewFeatures\test\Infrastructure\TempDataSerializerTestBase.cs" />
<Compile Include="..\..\Mvc.ViewFeatures\test\Rendering\JsonHelperTestBase.cs" />
</ItemGroup>
</Project>

View File

@ -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<char>.Shared);
var obj = new
{
HTML = "<b>John Doe</b>"
};
var expectedOutput = "{\"html\":\"\\u003cb\\u003eJohn Doe\\u003c/b\\u003e\"}";
// Act
var result = helper.Serialize(obj);
// Assert
var htmlString = Assert.IsType<HtmlString>(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<char>.Shared);
var helper = GetJsonHelper();
var obj = new
{
FullHtml = "<b>John Doe</b>"
@ -91,5 +66,33 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson
var htmlString = Assert.IsType<HtmlString>(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<HtmlString>(result);
Assert.Equal(expectedOutput, htmlString.ToString());
}
protected override IJsonHelper GetJsonHelper()
{
var options = new MvcNewtonsoftJsonOptions();
var helper = new NewtonsoftJsonHelper(
Options.Create(options),
ArrayPool<char>.Shared);
return helper;
}
}
}

View File

@ -172,7 +172,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.TryAddSingleton<IModelExpressionProvider>(s => s.GetRequiredService<ModelExpressionProvider>());
services.TryAddSingleton<ValidationHtmlAttributeProvider, DefaultValidationHtmlAttributeProvider>();
services.TryAddSingleton<IJsonHelper, DefaultJsonHelper>();
services.TryAddSingleton<IJsonHelper, SystemTextJsonHelper>();
//
// View Components

View File

@ -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
{
/// <inheritdoc />
public IHtmlContent Serialize(object value)
{
throw new InvalidOperationException(Core.Resources.FormatReferenceToNewtonsoftJsonRequired(
$"{nameof(IJsonHelper)}.{nameof(Serialize)}",
"Microsoft.AspNetCore.Mvc.NewtonsoftJson",
nameof(IMvcBuilder),
"AddNewtonsoftJson",
"ConfigureServices(...)"));
}
}
}

View File

@ -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 = mvcOptions.Value;
}
/// <inheritdoc />
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);
}
}
}

View File

@ -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 = "<b>John Doe</b>"
};
var expectedOutput = "{\"html\":\"\\u003cb\\u003eJohn Doe\\u003c/b\\u003e\"}";
// Act
var result = helper.Serialize(obj);
// Assert
var htmlString = Assert.IsType<HtmlString>(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<HtmlString>(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<HtmlString>(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<HtmlString>(result);
Assert.Equal(expectedOutput, htmlString.ToString());
}
[Fact]
public void Serialize_WithHTMLEntities()
{
// Arrange
var helper = GetJsonHelper();
var obj = new
{
HTML = $"Hello &nbsp; &lt;John&gt;"
};
var expectedOutput = "{\"html\":\"Hello \\u0026nbsp; \\u0026lt;John\\u0026gt;\"}";
// Act
var result = helper.Serialize(obj);
// Assert
var htmlString = Assert.IsType<HtmlString>(result);
Assert.Equal(expectedOutput, htmlString.ToString());
}
}
}

View File

@ -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));
}
}
}