Add support for System.Text.Json based JsonResult executor (#9558)
Fixes https://github.com/aspnet/AspNetCore/issues/9554
This commit is contained in:
parent
b893777898
commit
5e953124e6
|
|
@ -24,6 +24,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json
|
||||||
private ArraySegment<byte> _byteBuffer;
|
private ArraySegment<byte> _byteBuffer;
|
||||||
private ArraySegment<char> _charBuffer;
|
private ArraySegment<char> _charBuffer;
|
||||||
private ArraySegment<byte> _overflowBuffer;
|
private ArraySegment<byte> _overflowBuffer;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
public TranscodingReadStream(Stream input, Encoding sourceEncoding)
|
public TranscodingReadStream(Stream input, Encoding sourceEncoding)
|
||||||
{
|
{
|
||||||
|
|
@ -150,9 +151,9 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json
|
||||||
readBuffer = readBuffer.Slice(bytesEncoded);
|
readBuffer = readBuffer.Slice(bytesEncoded);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to exit in one of the 2 conditions:
|
// We need to exit in one of the 2 conditions:
|
||||||
// * encoderCompleted will return false if "buffer" was too small for all the chars to be encoded.
|
// * encoderCompleted will return false if "buffer" was too small for all the chars to be encoded.
|
||||||
// * no bytes were converted in an iteration. This can occur if there wasn't any input.
|
// * no bytes were converted in an iteration. This can occur if there wasn't any input.
|
||||||
} while (encoderCompleted && bytesEncoded > 0);
|
} while (encoderCompleted && bytesEncoded > 0);
|
||||||
|
|
||||||
return totalBytes;
|
return totalBytes;
|
||||||
|
|
@ -224,11 +225,13 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
ArrayPool<char>.Shared.Return(_charBuffer.Array);
|
if (!_disposed)
|
||||||
ArrayPool<byte>.Shared.Return(_byteBuffer.Array);
|
{
|
||||||
ArrayPool<byte>.Shared.Return(_overflowBuffer.Array);
|
_disposed = true;
|
||||||
|
ArrayPool<char>.Shared.Return(_charBuffer.Array);
|
||||||
base.Dispose(disposing);
|
ArrayPool<byte>.Shared.Return(_byteBuffer.Array);
|
||||||
|
ArrayPool<byte>.Shared.Return(_overflowBuffer.Array);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json
|
||||||
private readonly Encoder _encoder;
|
private readonly Encoder _encoder;
|
||||||
private readonly char[] _charBuffer;
|
private readonly char[] _charBuffer;
|
||||||
private int _charsDecoded;
|
private int _charsDecoded;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
public TranscodingWriteStream(Stream stream, Encoding targetEncoding)
|
public TranscodingWriteStream(Stream stream, Encoding targetEncoding)
|
||||||
{
|
{
|
||||||
|
|
@ -154,13 +155,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (disposing)
|
if (!_disposed)
|
||||||
{
|
{
|
||||||
|
_disposed = true;
|
||||||
ArrayPool<char>.Shared.Return(_charBuffer);
|
ArrayPool<char>.Shared.Return(_charBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
base.Dispose(disposing);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,133 @@
|
||||||
|
// 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.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Core;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Formatters.Json;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
||||||
|
{
|
||||||
|
internal sealed class SystemTextJsonResultExecutor : IActionResultExecutor<JsonResult>
|
||||||
|
{
|
||||||
|
private static readonly string DefaultContentType = new MediaTypeHeaderValue("application/json")
|
||||||
|
{
|
||||||
|
Encoding = Encoding.UTF8
|
||||||
|
}.ToString();
|
||||||
|
|
||||||
|
private readonly MvcOptions _mvcOptions;
|
||||||
|
private readonly ILogger<SystemTextJsonResultExecutor> _logger;
|
||||||
|
|
||||||
|
public SystemTextJsonResultExecutor(
|
||||||
|
IOptions<MvcOptions> mvcOptions,
|
||||||
|
ILogger<SystemTextJsonResultExecutor> logger)
|
||||||
|
{
|
||||||
|
_mvcOptions = mvcOptions.Value;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ExecuteAsync(ActionContext context, JsonResult result)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
var jsonSerializerOptions = GetSerializerOptions(result);
|
||||||
|
|
||||||
|
var response = context.HttpContext.Response;
|
||||||
|
|
||||||
|
ResponseContentTypeHelper.ResolveContentTypeAndEncoding(
|
||||||
|
result.ContentType,
|
||||||
|
response.ContentType,
|
||||||
|
DefaultContentType,
|
||||||
|
out var resolvedContentType,
|
||||||
|
out var resolvedContentTypeEncoding);
|
||||||
|
|
||||||
|
response.ContentType = resolvedContentType;
|
||||||
|
|
||||||
|
if (result.StatusCode != null)
|
||||||
|
{
|
||||||
|
response.StatusCode = result.StatusCode.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.JsonResultExecuting(_logger, result.Value);
|
||||||
|
|
||||||
|
// Keep this code in sync with SystemTextJsonOutputFormatter
|
||||||
|
var writeStream = GetWriteStream(context.HttpContext, resolvedContentTypeEncoding);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var type = result.Value?.GetType() ?? typeof(object);
|
||||||
|
await JsonSerializer.WriteAsync(result.Value, type, writeStream, jsonSerializerOptions);
|
||||||
|
await writeStream.FlushAsync();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (writeStream is TranscodingWriteStream transcoding)
|
||||||
|
{
|
||||||
|
await transcoding.DisposeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stream GetWriteStream(HttpContext httpContext, Encoding selectedEncoding)
|
||||||
|
{
|
||||||
|
if (selectedEncoding.CodePage == Encoding.UTF8.CodePage)
|
||||||
|
{
|
||||||
|
// JsonSerializer does not write a BOM. Therefore we do not have to handle it
|
||||||
|
// in any special way.
|
||||||
|
return httpContext.Response.Body;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TranscodingWriteStream(httpContext.Response.Body, selectedEncoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonSerializerOptions GetSerializerOptions(JsonResult result)
|
||||||
|
{
|
||||||
|
var serializerSettings = result.SerializerSettings;
|
||||||
|
if (serializerSettings == null)
|
||||||
|
{
|
||||||
|
return _mvcOptions.SerializerOptions;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!(serializerSettings is JsonSerializerOptions settingsFromResult))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(Resources.FormatProperty_MustBeInstanceOfType(
|
||||||
|
nameof(JsonResult),
|
||||||
|
nameof(JsonResult.SerializerSettings),
|
||||||
|
typeof(JsonSerializerOptions)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return settingsFromResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Log
|
||||||
|
{
|
||||||
|
private static readonly Action<ILogger, string, Exception> _jsonResultExecuting = LoggerMessage.Define<string>(
|
||||||
|
LogLevel.Information,
|
||||||
|
new EventId(1, "JsonResultExecuting"),
|
||||||
|
"Executing JsonResult, writing value of type '{Type}'.");
|
||||||
|
|
||||||
|
public static void JsonResultExecuting(ILogger logger, object value)
|
||||||
|
{
|
||||||
|
var type = value == null ? "null" : value.GetType().FullName;
|
||||||
|
_jsonResultExecuting(logger, type, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -68,17 +68,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
}
|
}
|
||||||
|
|
||||||
var services = context.HttpContext.RequestServices;
|
var services = context.HttpContext.RequestServices;
|
||||||
var executor = services.GetService<IActionResultExecutor<JsonResult>>();
|
var executor = services.GetRequiredService<IActionResultExecutor<JsonResult>>();
|
||||||
if (executor == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(Resources.FormatReferenceToNewtonsoftJsonRequired(
|
|
||||||
$"{nameof(JsonResult)}.{nameof(ExecuteResultAsync)}",
|
|
||||||
"Microsoft.AspNetCore.Mvc.NewtonsoftJson",
|
|
||||||
nameof(IMvcBuilder),
|
|
||||||
"AddNewtonsoftJson",
|
|
||||||
"ConfigureServices(...)"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return executor.ExecuteAsync(context, this);
|
return executor.ExecuteAsync(context, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ using Microsoft.AspNetCore.Mvc.Formatters;
|
||||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Formatters.Xml.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Formatters.Xml.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.IntegrationTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.IntegrationTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Localization.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Localization.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.NewtonsoftJson.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.RazorPages.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.RazorPages.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.TagHelpers.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.TagHelpers.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
|
|
|
||||||
|
|
@ -1705,7 +1705,7 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
||||||
=> string.Format(CultureInfo.CurrentCulture, GetString("ReferenceToNewtonsoftJsonRequired"), p0, p1, p2, p3, p4);
|
=> string.Format(CultureInfo.CurrentCulture, GetString("ReferenceToNewtonsoftJsonRequired"), p0, p1, p2, p3, p4);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Collection bound to '{0}' exceeded {1}.{2} ({3}). This limit is a safeguard against incorrect model binders and models. Address issues in '{4}'. For example, this type may have a property with a model binder that always succeeds. See the {0}.{1} documentation for more information.
|
/// Collection bound to '{0}' exceeded {1}.{2} ({3}). This limit is a safeguard against incorrect model binders and models. Address issues in '{4}'. For example, this type may have a property with a model binder that always succeeds. See the {1}.{2} documentation for more information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string ModelBinding_ExceededMaxModelBindingCollectionSize
|
internal static string ModelBinding_ExceededMaxModelBindingCollectionSize
|
||||||
{
|
{
|
||||||
|
|
@ -1713,7 +1713,7 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Collection bound to '{0}' exceeded {1}.{2} ({3}). This limit is a safeguard against incorrect model binders and models. Address issues in '{4}'. For example, this type may have a property with a model binder that always succeeds. See the {0}.{1} documentation for more information.
|
/// Collection bound to '{0}' exceeded {1}.{2} ({3}). This limit is a safeguard against incorrect model binders and models. Address issues in '{4}'. For example, this type may have a property with a model binder that always succeeds. See the {1}.{2} documentation for more information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatModelBinding_ExceededMaxModelBindingCollectionSize(object p0, object p1, object p2, object p3, object p4)
|
internal static string FormatModelBinding_ExceededMaxModelBindingCollectionSize(object p0, object p1, object p2, object p3, object p4)
|
||||||
=> string.Format(CultureInfo.CurrentCulture, GetString("ModelBinding_ExceededMaxModelBindingCollectionSize"), p0, p1, p2, p3, p4);
|
=> string.Format(CultureInfo.CurrentCulture, GetString("ModelBinding_ExceededMaxModelBindingCollectionSize"), p0, p1, p2, p3, p4);
|
||||||
|
|
@ -1732,6 +1732,20 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
||||||
internal static string FormatModelBinding_ExceededMaxModelBindingRecursionDepth(object p0, object p1, object p2, object p3)
|
internal static string FormatModelBinding_ExceededMaxModelBindingRecursionDepth(object p0, object p1, object p2, object p3)
|
||||||
=> string.Format(CultureInfo.CurrentCulture, GetString("ModelBinding_ExceededMaxModelBindingRecursionDepth"), p0, p1, p2, p3);
|
=> string.Format(CultureInfo.CurrentCulture, GetString("ModelBinding_ExceededMaxModelBindingRecursionDepth"), p0, p1, p2, p3);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Property '{0}.{1}' must be an instance of type '{2}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string Property_MustBeInstanceOfType
|
||||||
|
{
|
||||||
|
get => GetString("Property_MustBeInstanceOfType");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Property '{0}.{1}' must be an instance of type '{2}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatProperty_MustBeInstanceOfType(object p0, object p1, object p2)
|
||||||
|
=> string.Format(CultureInfo.CurrentCulture, GetString("Property_MustBeInstanceOfType"), p0, p1, p2);
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -501,4 +501,7 @@
|
||||||
<value>Model binding system exceeded {0}.{1} ({2}). Reduce the potential nesting of '{3}'. For example, this type may have a property with a model binder that always succeeds. See the {0}.{1} documentation for more information.</value>
|
<value>Model binding system exceeded {0}.{1} ({2}). Reduce the potential nesting of '{3}'. For example, this type may have a property with a model binder that always succeeds. See the {0}.{1} documentation for more information.</value>
|
||||||
<comment>{0} is MvcOptions, {1} is MaxModelBindingRecursionDepth, {2} is option value, {3} is (loopy or deeply nested) top-level model type.</comment>
|
<comment>{0} is MvcOptions, {1} is MaxModelBindingRecursionDepth, {2} is option value, {3} is (loopy or deeply nested) top-level model type.</comment>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Property_MustBeInstanceOfType" xml:space="preserve">
|
||||||
|
<value>Property '{0}.{1}' must be an instance of type '{2}'.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -2,11 +2,10 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
@ -15,21 +14,20 @@ using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Logging.Testing;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson
|
namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
||||||
{
|
{
|
||||||
public class JsonResultExecutorTest
|
public abstract class JsonResultExecutorTestBase
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ExecuteAsync_UsesDefaultContentType_IfNoContentTypeSpecified()
|
public async Task ExecuteAsync_UsesDefaultContentType_IfNoContentTypeSpecified()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new { foo = "abcd" }));
|
var expected = Encoding.UTF8.GetBytes(JsonSerializer.ToString(new { foo = "abcd" }));
|
||||||
|
|
||||||
var context = GetActionContext();
|
var context = GetActionContext();
|
||||||
|
|
||||||
|
|
@ -49,7 +47,7 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson
|
||||||
public async Task ExecuteAsync_NullEncoding_DoesNotSetCharsetOnContentType()
|
public async Task ExecuteAsync_NullEncoding_DoesNotSetCharsetOnContentType()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new { foo = "abcd" }));
|
var expected = Encoding.UTF8.GetBytes(JsonSerializer.ToString(new { foo = "abcd" }));
|
||||||
|
|
||||||
var context = GetActionContext();
|
var context = GetActionContext();
|
||||||
|
|
||||||
|
|
@ -66,11 +64,55 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson
|
||||||
Assert.Equal("text/json", context.HttpContext.Response.ContentType);
|
Assert.Equal("text/json", context.HttpContext.Response.ContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExecuteAsync_UsesEncodingSpecifiedInContentType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = Encoding.Unicode.GetBytes(JsonSerializer.ToString(new { foo = "abcd" }));
|
||||||
|
|
||||||
|
var context = GetActionContext();
|
||||||
|
context.HttpContext.Response.ContentType = "text/json; charset=utf-8";
|
||||||
|
|
||||||
|
var result = new JsonResult(new { foo = "abcd" })
|
||||||
|
{
|
||||||
|
ContentType = "text/json; charset=utf-16",
|
||||||
|
};
|
||||||
|
var executor = CreateExecutor();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await executor.ExecuteAsync(context, result);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var written = GetWrittenBytes(context.HttpContext);
|
||||||
|
Assert.Equal(expected, written);
|
||||||
|
Assert.Equal("text/json; charset=utf-16", context.HttpContext.Response.ContentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExecuteAsync_UsesEncodingSpecifiedInResponseContentType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = Encoding.Unicode.GetBytes(JsonSerializer.ToString(new { foo = "abcd" }));
|
||||||
|
|
||||||
|
var context = GetActionContext();
|
||||||
|
context.HttpContext.Response.ContentType = "text/json; charset=utf-16";
|
||||||
|
var result = new JsonResult(new { foo = "abcd" });
|
||||||
|
var executor = CreateExecutor();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await executor.ExecuteAsync(context, result);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var written = GetWrittenBytes(context.HttpContext);
|
||||||
|
Assert.Equal(expected, written);
|
||||||
|
Assert.Equal("text/json; charset=utf-16", context.HttpContext.Response.ContentType);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ExecuteAsync_SetsContentTypeAndEncoding()
|
public async Task ExecuteAsync_SetsContentTypeAndEncoding()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new { foo = "abcd" }));
|
var expected = Encoding.UTF8.GetBytes(JsonSerializer.ToString(new { foo = "abcd" }));
|
||||||
|
|
||||||
var context = GetActionContext();
|
var context = GetActionContext();
|
||||||
|
|
||||||
|
|
@ -94,7 +136,7 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson
|
||||||
public async Task ExecuteAsync_NoResultContentTypeSet_UsesResponseContentType_AndSuppliedEncoding()
|
public async Task ExecuteAsync_NoResultContentTypeSet_UsesResponseContentType_AndSuppliedEncoding()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new { foo = "abcd" }));
|
var expected = Encoding.UTF8.GetBytes(JsonSerializer.ToString(new { foo = "abcd" }));
|
||||||
var expectedContentType = "text/foo; p1=p1-value; charset=us-ascii";
|
var expectedContentType = "text/foo; p1=p1-value; charset=us-ascii";
|
||||||
|
|
||||||
var context = GetActionContext();
|
var context = GetActionContext();
|
||||||
|
|
@ -120,7 +162,7 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson
|
||||||
string expectedContentType)
|
string expectedContentType)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new { foo = "abcd" }));
|
var expected = Encoding.UTF8.GetBytes(JsonSerializer.ToString(new { foo = "abcd" }));
|
||||||
|
|
||||||
var context = GetActionContext();
|
var context = GetActionContext();
|
||||||
context.HttpContext.Response.ContentType = responseContentType;
|
context.HttpContext.Response.ContentType = responseContentType;
|
||||||
|
|
@ -141,14 +183,13 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson
|
||||||
public async Task ExecuteAsync_UsesPassedInSerializerSettings()
|
public async Task ExecuteAsync_UsesPassedInSerializerSettings()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(
|
var expected = Encoding.UTF8.GetBytes(JsonSerializer.ToString(
|
||||||
new { foo = "abcd" },
|
new { foo = "abcd" },
|
||||||
Formatting.Indented));
|
new JsonSerializerOptions { WriteIndented = true }));
|
||||||
|
|
||||||
var context = GetActionContext();
|
var context = GetActionContext();
|
||||||
|
|
||||||
var serializerSettings = new JsonSerializerSettings();
|
var serializerSettings = GetIndentedSettings();
|
||||||
serializerSettings.Formatting = Formatting.Indented;
|
|
||||||
|
|
||||||
var result = new JsonResult(new { foo = "abcd" }, serializerSettings);
|
var result = new JsonResult(new { foo = "abcd" }, serializerSettings);
|
||||||
var executor = CreateExecutor();
|
var executor = CreateExecutor();
|
||||||
|
|
@ -162,6 +203,8 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson
|
||||||
Assert.Equal("application/json; charset=utf-8", context.HttpContext.Response.ContentType);
|
Assert.Equal("application/json; charset=utf-8", context.HttpContext.Response.ContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract object GetIndentedSettings();
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ExecuteAsync_ErrorDuringSerialization_DoesNotWriteContent()
|
public async Task ExecuteAsync_ErrorDuringSerialization_DoesNotWriteContent()
|
||||||
{
|
{
|
||||||
|
|
@ -175,7 +218,11 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson
|
||||||
{
|
{
|
||||||
await executor.ExecuteAsync(context, result);
|
await executor.ExecuteAsync(context, result);
|
||||||
}
|
}
|
||||||
catch (JsonSerializationException serializerException)
|
catch (NotImplementedException ex)
|
||||||
|
{
|
||||||
|
Assert.Equal("Property Age has not been implemented", ex.Message);
|
||||||
|
}
|
||||||
|
catch (Exception serializerException)
|
||||||
{
|
{
|
||||||
var expectedException = Assert.IsType<NotImplementedException>(serializerException.InnerException);
|
var expectedException = Assert.IsType<NotImplementedException>(serializerException.InnerException);
|
||||||
Assert.Equal("Property Age has not been implemented", expectedException.Message);
|
Assert.Equal("Property Age has not been implemented", expectedException.Message);
|
||||||
|
|
@ -192,15 +239,16 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = "Executing JsonResult, writing value of type 'System.String'.";
|
var expected = "Executing JsonResult, writing value of type 'System.String'.";
|
||||||
var context = GetActionContext();
|
var context = GetActionContext();
|
||||||
var logger = new StubLogger();
|
var sink = new TestSink();
|
||||||
var executer = CreateExecutor(logger);
|
var executor = CreateExecutor(new TestLoggerFactory(sink, enabled: true));
|
||||||
var result = new JsonResult("result_value");
|
var result = new JsonResult("result_value");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await executer.ExecuteAsync(context, result);
|
await executor.ExecuteAsync(context, result);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(expected, logger.MostRecentMessage);
|
var write = Assert.Single(sink.Writes);
|
||||||
|
Assert.Equal(expected, write.State.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -209,26 +257,28 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = "Executing JsonResult, writing value of type 'null'.";
|
var expected = "Executing JsonResult, writing value of type 'null'.";
|
||||||
var context = GetActionContext();
|
var context = GetActionContext();
|
||||||
var logger = new StubLogger();
|
var sink = new TestSink();
|
||||||
var executer = CreateExecutor(logger);
|
var executor = CreateExecutor(new TestLoggerFactory(sink, enabled: true));
|
||||||
var result = new JsonResult(null);
|
var result = new JsonResult(null);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await executer.ExecuteAsync(context, result);
|
await executor.ExecuteAsync(context, result);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(expected, logger.MostRecentMessage);
|
var write = Assert.Single(sink.Writes);
|
||||||
|
Assert.Equal(expected, write.State.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ExecuteAsync_LargePayload_DoesNotPerformSynchronousWrites()
|
public async Task ExecuteAsync_LargePayload_DoesNotPerformSynchronousWrites()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var model = Enumerable.Range(0, 1000).Select(p => new TestModel { Property = new string('a', 5000)});
|
var model = Enumerable.Range(0, 1000).Select(p => new TestModel { Property = new string('a', 5000) }).ToArray();
|
||||||
|
|
||||||
var stream = new Mock<Stream> { CallBase = true };
|
var stream = new Mock<Stream>();
|
||||||
stream.Setup(v => v.WriteAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
|
stream.Setup(v => v.WriteAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
|
||||||
.Returns(Task.CompletedTask);
|
.Returns(Task.CompletedTask)
|
||||||
|
.Verifiable();
|
||||||
stream.SetupGet(s => s.CanWrite).Returns(true);
|
stream.SetupGet(s => s.CanWrite).Returns(true);
|
||||||
var context = GetActionContext();
|
var context = GetActionContext();
|
||||||
context.HttpContext.Response.Body = stream.Object;
|
context.HttpContext.Response.Body = stream.Object;
|
||||||
|
|
@ -240,22 +290,30 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson
|
||||||
await executor.ExecuteAsync(context, result);
|
await executor.ExecuteAsync(context, result);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
stream.Verify(v => v.WriteAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.AtLeastOnce());
|
|
||||||
|
|
||||||
stream.Verify(v => v.Write(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>()), Times.Never());
|
stream.Verify(v => v.Write(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>()), Times.Never());
|
||||||
stream.Verify(v => v.Flush(), Times.Never());
|
stream.Verify(v => v.Flush(), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static JsonResultExecutor CreateExecutor(ILogger<JsonResultExecutor> logger = null)
|
[Fact]
|
||||||
|
public async Task ExecuteAsync_ThrowsIfSerializerSettingIsNotTheCorrectType()
|
||||||
{
|
{
|
||||||
return new JsonResultExecutor(
|
// Arrange
|
||||||
new TestHttpResponseStreamWriterFactory(),
|
var context = GetActionContext();
|
||||||
logger ?? NullLogger<JsonResultExecutor>.Instance,
|
|
||||||
Options.Create(new MvcOptions()),
|
var result = new JsonResult(new { foo = "abcd" }, new object());
|
||||||
Options.Create(new MvcNewtonsoftJsonOptions()),
|
var executor = CreateExecutor();
|
||||||
ArrayPool<char>.Shared);
|
|
||||||
|
// Act & Assert
|
||||||
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => executor.ExecuteAsync(context, result));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.StartsWith("Property 'JsonResult.SerializerSettings' must be an instance of type", ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected IActionResultExecutor<JsonResult> CreateExecutor() => CreateExecutor(NullLoggerFactory.Instance);
|
||||||
|
|
||||||
|
protected abstract IActionResultExecutor<JsonResult> CreateExecutor(ILoggerFactory loggerFactory);
|
||||||
|
|
||||||
private static HttpContext GetHttpContext()
|
private static HttpContext GetHttpContext()
|
||||||
{
|
{
|
||||||
var httpContext = new DefaultHttpContext();
|
var httpContext = new DefaultHttpContext();
|
||||||
|
|
@ -292,20 +350,6 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class StubLogger : ILogger<JsonResultExecutor>
|
|
||||||
{
|
|
||||||
public string MostRecentMessage { get; private set; }
|
|
||||||
|
|
||||||
public IDisposable BeginScope<TState>(TState state) => throw new NotImplementedException();
|
|
||||||
|
|
||||||
public bool IsEnabled(LogLevel logLevel) => true;
|
|
||||||
|
|
||||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
|
||||||
{
|
|
||||||
MostRecentMessage = formatter(state, exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TestModel
|
private class TestModel
|
||||||
{
|
{
|
||||||
public string Property { get; set; }
|
public string Property { get; set; }
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
// 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.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
||||||
|
{
|
||||||
|
public class SystemTextJsonResultExecutorTest : JsonResultExecutorTestBase
|
||||||
|
{
|
||||||
|
protected override IActionResultExecutor<JsonResult> CreateExecutor(ILoggerFactory loggerFactory)
|
||||||
|
{
|
||||||
|
return new SystemTextJsonResultExecutor(Options.Create(new MvcOptions()), loggerFactory.CreateLogger<SystemTextJsonResultExecutor>());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override object GetIndentedSettings()
|
||||||
|
{
|
||||||
|
return new JsonSerializerOptions { WriteIndented = true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,32 +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 System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Moq;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Core
|
|
||||||
{
|
|
||||||
public class JsonResultTest
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public async Task ExecuteResultAsync_ThrowsIfExecutorIsNotAvailableInServices()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var jsonResult = new JsonResult("Hello");
|
|
||||||
var message = "'JsonResult.ExecuteResultAsync' requires a reference to 'Microsoft.AspNetCore.Mvc.NewtonsoftJson'. " +
|
|
||||||
"Configure your application by adding a reference to the 'Microsoft.AspNetCore.Mvc.NewtonsoftJson' package and calling 'IMvcBuilder.AddNewtonsoftJson' " +
|
|
||||||
"inside the call to 'ConfigureServices(...)' in the application startup code.";
|
|
||||||
var actionContext = new ActionContext
|
|
||||||
{
|
|
||||||
HttpContext = new DefaultHttpContext { RequestServices = Mock.Of<IServiceProvider>() }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act & Assert
|
|
||||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => jsonResult.ExecuteResultAsync(actionContext));
|
|
||||||
Assert.Equal(message, ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,11 +2,9 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Mvc.NewtonsoftJson;
|
using Microsoft.AspNetCore.Mvc.NewtonsoftJson;
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
|
|
@ -73,14 +71,22 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, NewtonsoftJsonMvcOptionsSetup>());
|
ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, NewtonsoftJsonMvcOptionsSetup>());
|
||||||
services.TryAddEnumerable(
|
services.TryAddEnumerable(
|
||||||
ServiceDescriptor.Transient<IApiDescriptionProvider, JsonPatchOperationsArrayProvider>());
|
ServiceDescriptor.Transient<IApiDescriptionProvider, JsonPatchOperationsArrayProvider>());
|
||||||
services.TryAddSingleton<IActionResultExecutor<JsonResult>, JsonResultExecutor>();
|
|
||||||
|
|
||||||
|
var jsonResultExecutor = services.FirstOrDefault(f =>
|
||||||
|
f.ServiceType == typeof(IActionResultExecutor<JsonResult>) &&
|
||||||
|
f.ImplementationType?.Assembly == typeof(JsonResult).Assembly);
|
||||||
|
|
||||||
|
if (jsonResultExecutor != null)
|
||||||
|
{
|
||||||
|
services.Remove(jsonResultExecutor);
|
||||||
|
}
|
||||||
|
services.TryAddSingleton<IActionResultExecutor<JsonResult>, NewtonsoftJsonResultExecutor>();
|
||||||
|
|
||||||
var viewFeaturesAssembly = typeof(IHtmlHelper).Assembly;
|
var viewFeaturesAssembly = typeof(IHtmlHelper).Assembly;
|
||||||
|
|
||||||
var tempDataSerializer = services.FirstOrDefault(f =>
|
var tempDataSerializer = services.FirstOrDefault(f =>
|
||||||
f.ServiceType == typeof(TempDataSerializer) &&
|
f.ServiceType == typeof(TempDataSerializer) &&
|
||||||
f.ImplementationType?.Assembly == viewFeaturesAssembly &&
|
f.ImplementationType?.Assembly == viewFeaturesAssembly);
|
||||||
f.ImplementationType.FullName == "Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure.DefaultTempDataSerializer");
|
|
||||||
|
|
||||||
if (tempDataSerializer != null)
|
if (tempDataSerializer != null)
|
||||||
{
|
{
|
||||||
|
|
@ -94,8 +100,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
//
|
//
|
||||||
var jsonHelper = services.FirstOrDefault(
|
var jsonHelper = services.FirstOrDefault(
|
||||||
f => f.ServiceType == typeof(IJsonHelper) &&
|
f => f.ServiceType == typeof(IJsonHelper) &&
|
||||||
f.ImplementationType?.Assembly == viewFeaturesAssembly &&
|
f.ImplementationType?.Assembly == viewFeaturesAssembly);
|
||||||
f.ImplementationType.FullName == "Microsoft.AspNetCore.Mvc.Rendering.DefaultJsonHelper");
|
|
||||||
if (jsonHelper != null)
|
if (jsonHelper != null)
|
||||||
{
|
{
|
||||||
services.Remove(jsonHelper);
|
services.Remove(jsonHelper);
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Executes a <see cref="JsonResult"/> to write to the response.
|
/// Executes a <see cref="JsonResult"/> to write to the response.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class JsonResultExecutor : IActionResultExecutor<JsonResult>
|
internal class NewtonsoftJsonResultExecutor : IActionResultExecutor<JsonResult>
|
||||||
{
|
{
|
||||||
private static readonly string DefaultContentType = new MediaTypeHeaderValue("application/json")
|
private static readonly string DefaultContentType = new MediaTypeHeaderValue("application/json")
|
||||||
{
|
{
|
||||||
|
|
@ -34,16 +34,16 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson
|
||||||
private readonly IArrayPool<char> _charPool;
|
private readonly IArrayPool<char> _charPool;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="JsonResultExecutor"/>.
|
/// Creates a new <see cref="NewtonsoftJsonResultExecutor"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="writerFactory">The <see cref="IHttpResponseStreamWriterFactory"/>.</param>
|
/// <param name="writerFactory">The <see cref="IHttpResponseStreamWriterFactory"/>.</param>
|
||||||
/// <param name="logger">The <see cref="ILogger{JsonResultExecutor}"/>.</param>
|
/// <param name="logger">The <see cref="ILogger{NewtonsoftJsonResultExecutor}"/>.</param>
|
||||||
/// <param name="mvcOptions">Accessor to <see cref="MvcOptions"/>.</param>
|
/// <param name="mvcOptions">Accessor to <see cref="MvcOptions"/>.</param>
|
||||||
/// <param name="jsonOptions">Accessor to <see cref="MvcNewtonsoftJsonOptions"/>.</param>
|
/// <param name="jsonOptions">Accessor to <see cref="MvcNewtonsoftJsonOptions"/>.</param>
|
||||||
/// <param name="charPool">The <see cref="ArrayPool{Char}"/> for creating <see cref="T:char[]"/> buffers.</param>
|
/// <param name="charPool">The <see cref="ArrayPool{Char}"/> for creating <see cref="T:char[]"/> buffers.</param>
|
||||||
public JsonResultExecutor(
|
public NewtonsoftJsonResultExecutor(
|
||||||
IHttpResponseStreamWriterFactory writerFactory,
|
IHttpResponseStreamWriterFactory writerFactory,
|
||||||
ILogger<JsonResultExecutor> logger,
|
ILogger<NewtonsoftJsonResultExecutor> logger,
|
||||||
IOptions<MvcOptions> mvcOptions,
|
IOptions<MvcOptions> mvcOptions,
|
||||||
IOptions<MvcNewtonsoftJsonOptions> jsonOptions,
|
IOptions<MvcNewtonsoftJsonOptions> jsonOptions,
|
||||||
ArrayPool<char> charPool)
|
ArrayPool<char> charPool)
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Mvc.NewtonsoftJson;
|
using Microsoft.AspNetCore.Mvc.NewtonsoftJson;
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
using Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure;
|
using Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure;
|
||||||
|
|
@ -59,5 +60,20 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
var tempDataSerializer = Assert.Single(services, d => d.ServiceType == typeof(TempDataSerializer));
|
var tempDataSerializer = Assert.Single(services, d => d.ServiceType == typeof(TempDataSerializer));
|
||||||
Assert.Same(typeof(BsonTempDataSerializer), tempDataSerializer.ImplementationType);
|
Assert.Same(typeof(BsonTempDataSerializer), tempDataSerializer.ImplementationType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddServicesCore_ReplacesDefaultJsonResultExecutor()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var services = new ServiceCollection()
|
||||||
|
.AddSingleton<IActionResultExecutor<JsonResult>, SystemTextJsonResultExecutor>();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
NewtonsoftJsonMvcCoreBuilderExtensions.AddServicesCore(services);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var jsonResultExecutor = Assert.Single(services, d => d.ServiceType == typeof(IActionResultExecutor<JsonResult>));
|
||||||
|
Assert.Same(typeof(NewtonsoftJsonResultExecutor), jsonResultExecutor.ImplementationType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,9 +44,9 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson
|
||||||
var httpContext = new DefaultHttpContext();
|
var httpContext = new DefaultHttpContext();
|
||||||
httpContext.Response.Body = new MemoryStream();
|
httpContext.Response.Body = new MemoryStream();
|
||||||
|
|
||||||
var executor = new JsonResultExecutor(
|
var executor = new NewtonsoftJsonResultExecutor(
|
||||||
new TestHttpResponseStreamWriterFactory(),
|
new TestHttpResponseStreamWriterFactory(),
|
||||||
NullLogger<JsonResultExecutor>.Instance,
|
NullLogger<NewtonsoftJsonResultExecutor>.Instance,
|
||||||
Options.Create(new MvcOptions()),
|
Options.Create(new MvcOptions()),
|
||||||
Options.Create(new MvcNewtonsoftJsonOptions()),
|
Options.Create(new MvcNewtonsoftJsonOptions()),
|
||||||
ArrayPool<char>.Shared);
|
ArrayPool<char>.Shared);
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,13 @@
|
||||||
<Reference Include="Microsoft.AspNetCore.Mvc" />
|
<Reference Include="Microsoft.AspNetCore.Mvc" />
|
||||||
<Reference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" />
|
<Reference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" />
|
||||||
<ProjectReference Include="..\..\shared\Mvc.Core.TestCommon\Microsoft.AspNetCore.Mvc.Core.TestCommon.csproj" />
|
<ProjectReference Include="..\..\shared\Mvc.Core.TestCommon\Microsoft.AspNetCore.Mvc.Core.TestCommon.csproj" />
|
||||||
|
<Reference Include="Microsoft.Extensions.Logging.Testing" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="..\..\Mvc.Core\test\Formatters\JsonInputFormatterTestBase.cs" />
|
<Compile Include="..\..\Mvc.Core\test\Formatters\JsonInputFormatterTestBase.cs" />
|
||||||
<Compile Include="..\..\Mvc.Core\test\Formatters\JsonOutputFormatterTestBase.cs" />
|
<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\Infrastructure\TempDataSerializerTestBase.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.Buffers;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson
|
||||||
|
{
|
||||||
|
public class NewtonsoftJsonResultExecutorTest : JsonResultExecutorTestBase
|
||||||
|
{
|
||||||
|
protected override IActionResultExecutor<JsonResult> CreateExecutor(ILoggerFactory loggerFactory)
|
||||||
|
{
|
||||||
|
return new NewtonsoftJsonResultExecutor(
|
||||||
|
new TestHttpResponseStreamWriterFactory(),
|
||||||
|
loggerFactory.CreateLogger< NewtonsoftJsonResultExecutor>(),
|
||||||
|
Options.Create(new MvcOptions()),
|
||||||
|
Options.Create(new MvcNewtonsoftJsonOptions()),
|
||||||
|
ArrayPool<char>.Shared);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override object GetIndentedSettings()
|
||||||
|
{
|
||||||
|
return new JsonSerializerSettings { Formatting = Formatting.Indented };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue