diff --git a/eng/Dependencies.props b/eng/Dependencies.props index 84b88d8fbb4d..663aca404a22 100644 --- a/eng/Dependencies.props +++ b/eng/Dependencies.props @@ -141,6 +141,7 @@ and are generated based on the last package release. + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 7910f49e68be..6953742073d3 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -319,5 +319,9 @@ https://github.com/dotnet/arcade 1b04d6de502c4108ada6ea8e5ccefdc2ddc3ee7b + + https://github.com/dotnet/arcade + 000000 + diff --git a/eng/Versions.props b/eng/Versions.props index 14ba44a615eb..351207a0f0b6 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -137,6 +137,7 @@ 8.0.0-beta.23063.7 8.0.0-beta.23063.7 + 8.0.0-beta.23063.7 8.0.0-alpha.1.23062.2 diff --git a/src/Http/Http.Abstractions/src/ProblemDetails/ProblemDetails.cs b/src/Http/Http.Abstractions/src/ProblemDetails/ProblemDetails.cs index 582154c971a8..2d01289cdf19 100644 --- a/src/Http/Http.Abstractions/src/ProblemDetails/ProblemDetails.cs +++ b/src/Http/Http.Abstractions/src/ProblemDetails/ProblemDetails.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; using Microsoft.AspNetCore.Http; @@ -13,8 +12,6 @@ namespace Microsoft.AspNetCore.Mvc; [JsonConverter(typeof(ProblemDetailsJsonConverter))] public class ProblemDetails { - private readonly IDictionary _extensions = new Dictionary(StringComparer.Ordinal); - /// /// A URI reference [RFC3986] that identifies the problem type. This specification encourages that, when /// dereferenced, it provide human-readable documentation for the problem type @@ -62,10 +59,5 @@ public class ProblemDetails /// In particular, complex types or collection types may not round-trip to the original type when using the built-in JSON or XML formatters. /// [JsonExtensionData] - public IDictionary Extensions - { - [RequiresUnreferencedCode("JSON serialization and deserialization of ProblemDetails.Extensions might require types that cannot be statically analyzed.")] - [RequiresDynamicCode("JSON serialization and deserialization of ProblemDetails.Extensions might require types that cannot be statically analyzed.")] - get => _extensions; - } + public IDictionary Extensions { get; } = new Dictionary(StringComparer.Ordinal); } diff --git a/src/Http/Http.Abstractions/test/ProblemDetailsJsonConverterTest.cs b/src/Http/Http.Abstractions/test/ProblemDetailsJsonConverterTest.cs index 81e4abee0635..666303b71df9 100644 --- a/src/Http/Http.Abstractions/test/ProblemDetailsJsonConverterTest.cs +++ b/src/Http/Http.Abstractions/test/ProblemDetailsJsonConverterTest.cs @@ -3,6 +3,7 @@ using System.Text; using System.Text.Json; +using System.Text.Json.Nodes; using Microsoft.AspNetCore.Http.Json; using Microsoft.AspNetCore.Mvc; @@ -92,6 +93,39 @@ public void Read_UsingJsonSerializerWorks() }); } + [Fact] + public void Read_WithUnknownTypeHandling_Works() + { + // Arrange + var type = "https://tools.ietf.org/html/rfc9110#section-15.5.5"; + var title = "Not found"; + var status = 404; + var detail = "Product not found"; + var instance = "http://example.com/products/14"; + var traceId = "|37dd3dd5-4a9619f953c40a16."; + var json = $"{{\"type\":\"{type}\",\"title\":\"{title}\",\"status\":{status},\"detail\":\"{detail}\", \"instance\":\"{instance}\",\"traceId\":\"{traceId}\"}}"; + var serializerOptions = new JsonSerializerOptions(JsonSerializerOptions) { UnknownTypeHandling = System.Text.Json.Serialization.JsonUnknownTypeHandling.JsonNode }; + + // Act + var problemDetails = JsonSerializer.Deserialize(json, serializerOptions); + + // Assert + Assert.NotNull(problemDetails); + Assert.Equal(type, problemDetails!.Type); + Assert.Equal(title, problemDetails.Title); + Assert.Equal(status, problemDetails.Status); + Assert.Equal(instance, problemDetails.Instance); + Assert.Equal(detail, problemDetails.Detail); + Assert.Collection( + problemDetails.Extensions, + kvp => + { + Assert.Equal("traceId", kvp.Key); + Assert.IsAssignableFrom(kvp.Value!); + Assert.Equal(traceId, kvp.Value?.ToString()); + }); + } + [Fact] public void Read_WithSomeMissingValues_Works() { @@ -178,4 +212,36 @@ public void Write_WithSomeMissingContent_Works() var actual = Encoding.UTF8.GetString(stream.ToArray()); Assert.Equal(expected, actual); } + + [Fact] + public void Write_WithNullExtensionValue_Works() + { + // Arrange + var value = new ProblemDetails + { + Title = "Not found", + Type = "https://tools.ietf.org/html/rfc9110#section-15.5.5", + Status = 404, + Detail = "Product not found", + Instance = "http://example.com/products/14", + Extensions = + { + { "traceId", null }, + { "some-data", new[] { "value1", "value2" } } + } + }; + var expected = $"{{\"type\":\"{JsonEncodedText.Encode(value.Type)}\",\"title\":\"{value.Title}\",\"status\":{value.Status},\"detail\":\"{value.Detail}\",\"instance\":\"{JsonEncodedText.Encode(value.Instance)}\",\"traceId\":null,\"some-data\":[\"value1\",\"value2\"]}}"; + var converter = new ProblemDetailsJsonConverter(); + var stream = new MemoryStream(); + + // Act + using (var writer = new Utf8JsonWriter(stream)) + { + converter.Write(writer, value, JsonSerializerOptions); + } + + // Assert + var actual = Encoding.UTF8.GetString(stream.ToArray()); + Assert.Equal(expected, actual); + } } diff --git a/src/Http/Http.Extensions/src/HttpRequestJsonExtensions.cs b/src/Http/Http.Extensions/src/HttpRequestJsonExtensions.cs index 09cc97339d0b..437283c330dd 100644 --- a/src/Http/Http.Extensions/src/HttpRequestJsonExtensions.cs +++ b/src/Http/Http.Extensions/src/HttpRequestJsonExtensions.cs @@ -32,13 +32,14 @@ public static class HttpRequestJsonExtensions /// The request to read from. /// A used to cancel the operation. /// The task object representing the asynchronous operation. - [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] - [RequiresDynamicCode(RequiresDynamicCodeMessage)] public static ValueTask ReadFromJsonAsync( this HttpRequest request, CancellationToken cancellationToken = default) { - return request.ReadFromJsonAsync(options: null, cancellationToken); + ArgumentNullException.ThrowIfNull(request); + + var options = ResolveSerializerOptions(request.HttpContext); + return request.ReadFromJsonAsync(jsonTypeInfo: (JsonTypeInfo)options.GetTypeInfo(typeof(TValue)), cancellationToken); } /// @@ -166,14 +167,15 @@ public static class HttpRequestJsonExtensions /// The type of object to read. /// A used to cancel the operation. /// The task object representing the asynchronous operation. - [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] - [RequiresDynamicCode(RequiresDynamicCodeMessage)] public static ValueTask ReadFromJsonAsync( this HttpRequest request, Type type, CancellationToken cancellationToken = default) { - return request.ReadFromJsonAsync(type, options: null, cancellationToken); + ArgumentNullException.ThrowIfNull(request); + + var options = ResolveSerializerOptions(request.HttpContext); + return request.ReadFromJsonAsync(jsonTypeInfo: options.GetTypeInfo(type), cancellationToken); } /// diff --git a/src/Http/Http.Extensions/src/HttpResponseJsonExtensions.cs b/src/Http/Http.Extensions/src/HttpResponseJsonExtensions.cs index 9d771adc2fb2..d2ec3b1e516e 100644 --- a/src/Http/Http.Extensions/src/HttpResponseJsonExtensions.cs +++ b/src/Http/Http.Extensions/src/HttpResponseJsonExtensions.cs @@ -30,14 +30,15 @@ public static partial class HttpResponseJsonExtensions /// The value to write as JSON. /// A used to cancel the operation. /// The task object representing the asynchronous operation. - [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] - [RequiresDynamicCode(RequiresDynamicCodeMessage)] public static Task WriteAsJsonAsync( this HttpResponse response, TValue value, CancellationToken cancellationToken = default) { - return response.WriteAsJsonAsync(value, options: null, contentType: null, cancellationToken); + ArgumentNullException.ThrowIfNull(response); + + var options = ResolveSerializerOptions(response.HttpContext); + return response.WriteAsJsonAsync(value, jsonTypeInfo: (JsonTypeInfo)options.GetTypeInfo(typeof(TValue)), contentType: null, cancellationToken); } /// @@ -203,15 +204,16 @@ private static async Task WriteAsJsonAsyncSlow( /// The type of object to write. /// A used to cancel the operation. /// The task object representing the asynchronous operation. - [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] - [RequiresDynamicCode(RequiresDynamicCodeMessage)] public static Task WriteAsJsonAsync( this HttpResponse response, object? value, Type type, CancellationToken cancellationToken = default) { - return response.WriteAsJsonAsync(value, type, options: null, contentType: null, cancellationToken); + ArgumentNullException.ThrowIfNull(response); + + var options = ResolveSerializerOptions(response.HttpContext); + return response.WriteAsJsonAsync(value, jsonTypeInfo: options.GetTypeInfo(type), contentType: null, cancellationToken); } /// diff --git a/src/Http/Http.Extensions/src/JsonOptions.cs b/src/Http/Http.Extensions/src/JsonOptions.cs index a47c77af8f96..5ca45dc9faff 100644 --- a/src/Http/Http.Extensions/src/JsonOptions.cs +++ b/src/Http/Http.Extensions/src/JsonOptions.cs @@ -4,6 +4,7 @@ using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Serialization.Metadata; +using Microsoft.AspNetCore.Internal; #nullable enable @@ -26,7 +27,7 @@ public class JsonOptions // The JsonSerializerOptions.GetTypeInfo method is called directly and needs a defined resolver // setting the default resolver (reflection-based) but the user can overwrite it directly or calling // .AddContext() - TypeInfoResolver = CreateDefaultTypeResolver() + TypeInfoResolver = TrimmingAppContextSwitches.EnsureJsonTrimmability ? null : CreateDefaultTypeResolver() }; // Use a copy so the defaults are not modified. diff --git a/src/Http/Http.Extensions/src/Microsoft.AspNetCore.Http.Extensions.csproj b/src/Http/Http.Extensions/src/Microsoft.AspNetCore.Http.Extensions.csproj index 65576652a378..9ac6d360d2b7 100644 --- a/src/Http/Http.Extensions/src/Microsoft.AspNetCore.Http.Extensions.csproj +++ b/src/Http/Http.Extensions/src/Microsoft.AspNetCore.Http.Extensions.csproj @@ -12,17 +12,18 @@ - - - - + + + + - + - - + + + @@ -36,4 +37,5 @@ + diff --git a/src/Http/Http.Extensions/src/ProblemDetailsJsonContext.cs b/src/Http/Http.Extensions/src/ProblemDetailsJsonContext.cs index b247e359e483..287b7d93d954 100644 --- a/src/Http/Http.Extensions/src/ProblemDetailsJsonContext.cs +++ b/src/Http/Http.Extensions/src/ProblemDetailsJsonContext.cs @@ -9,6 +9,8 @@ namespace Microsoft.AspNetCore.Http; [JsonSerializable(typeof(ProblemDetails))] [JsonSerializable(typeof(HttpValidationProblemDetails))] +// ExtensionData +[JsonSerializable(typeof(IDictionary))] // Additional values are specified on JsonSerializerContext to support some values for extensions. // For example, the DeveloperExceptionMiddleware serializes its complex type to JsonElement, which problem details then needs to serialize. [JsonSerializable(typeof(JsonElement))] diff --git a/src/Http/Http.Extensions/src/Properties/ILLink.Substitutions.xml b/src/Http/Http.Extensions/src/Properties/ILLink.Substitutions.xml new file mode 100644 index 000000000000..92c77d69a9e6 --- /dev/null +++ b/src/Http/Http.Extensions/src/Properties/ILLink.Substitutions.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/Http/Http.Extensions/test/JsonOptionsTests.cs b/src/Http/Http.Extensions/test/JsonOptionsTests.cs new file mode 100644 index 000000000000..2bae1a700e66 --- /dev/null +++ b/src/Http/Http.Extensions/test/JsonOptionsTests.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json.Serialization.Metadata; +using Microsoft.AspNetCore.Http.Json; +using Microsoft.AspNetCore.Testing; +using Microsoft.DotNet.RemoteExecutor; + +namespace Microsoft.AspNetCore.Http.Extensions; + +public class JsonOptionsTests +{ + [ConditionalFact] + [RemoteExecutionSupported] + public void DefaultSerializerOptions_SetsTypeInfoResolverNull_WhenEnsureJsonTrimmabilityTrue() + { + var options = new RemoteInvokeOptions(); + options.RuntimeConfigurationOptions.Add("Microsoft.AspNetCore.EnsureJsonTrimmability", true.ToString()); + + using var remoteHandle = RemoteExecutor.Invoke(static () => + { + // Arrange + var options = JsonOptions.DefaultSerializerOptions; + + // Assert + Assert.Null(options.TypeInfoResolver); + }, options); + } + + [ConditionalFact] + [RemoteExecutionSupported] + public void DefaultSerializerOptions_SetsTypeInfoResolverToDefault_WhenEnsureJsonTrimmabilityFalse() + { + var options = new RemoteInvokeOptions(); + options.RuntimeConfigurationOptions.Add("Microsoft.AspNetCore.EnsureJsonTrimmability", false.ToString()); + + using var remoteHandle = RemoteExecutor.Invoke(static () => + { + // Arrange + var options = JsonOptions.DefaultSerializerOptions; + + // Assert + Assert.NotNull(options.TypeInfoResolver); + Assert.IsType(options.TypeInfoResolver); + }, options); + } +} diff --git a/src/Middleware/Diagnostics/src/DeveloperExceptionPage/DeveloperExceptionPageMiddlewareImpl.cs b/src/Middleware/Diagnostics/src/DeveloperExceptionPage/DeveloperExceptionPageMiddlewareImpl.cs index 91e74ef529a6..bb34c6d64597 100644 --- a/src/Middleware/Diagnostics/src/DeveloperExceptionPage/DeveloperExceptionPageMiddlewareImpl.cs +++ b/src/Middleware/Diagnostics/src/DeveloperExceptionPage/DeveloperExceptionPageMiddlewareImpl.cs @@ -202,8 +202,6 @@ await _problemDetailsService.WriteAsync(new() } } - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Values set on ProblemDetails.Extensions are supported by the default writer.")] - [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Values set on ProblemDetails.Extensions are supported by the default writer.")] private ProblemDetails CreateProblemDetails(ErrorContext errorContext, HttpContext httpContext) { var problemDetails = new ProblemDetails diff --git a/src/Mvc/Mvc.Core/src/JsonOptions.cs b/src/Mvc/Mvc.Core/src/JsonOptions.cs index 834c758bb9dc..44c2522924f2 100644 --- a/src/Mvc/Mvc.Core/src/JsonOptions.cs +++ b/src/Mvc/Mvc.Core/src/JsonOptions.cs @@ -3,6 +3,7 @@ using System.Text.Json; using System.Text.Json.Serialization.Metadata; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.ModelBinding; @@ -42,7 +43,7 @@ public class JsonOptions // The JsonSerializerOptions.GetTypeInfo method is called directly and needs a defined resolver // setting the default resolver (reflection-based) but the user can overwrite it directly or calling // .AddContext() - TypeInfoResolver = CreateDefaultTypeResolver() + TypeInfoResolver = TrimmingAppContextSwitches.EnsureJsonTrimmability ? null : CreateDefaultTypeResolver() }; private static IJsonTypeInfoResolver CreateDefaultTypeResolver() diff --git a/src/Mvc/Mvc.Core/src/Microsoft.AspNetCore.Mvc.Core.csproj b/src/Mvc/Mvc.Core/src/Microsoft.AspNetCore.Mvc.Core.csproj index 477e4fb86aa1..161ce2ddcebf 100644 --- a/src/Mvc/Mvc.Core/src/Microsoft.AspNetCore.Mvc.Core.csproj +++ b/src/Mvc/Mvc.Core/src/Microsoft.AspNetCore.Mvc.Core.csproj @@ -33,6 +33,7 @@ Microsoft.AspNetCore.Mvc.RouteAttribute + @@ -61,6 +62,10 @@ Microsoft.AspNetCore.Mvc.RouteAttribute + + + + diff --git a/src/Mvc/Mvc.Core/src/Properties/ILLink.Substitutions.xml b/src/Mvc/Mvc.Core/src/Properties/ILLink.Substitutions.xml new file mode 100644 index 000000000000..9688f35dcd63 --- /dev/null +++ b/src/Mvc/Mvc.Core/src/Properties/ILLink.Substitutions.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/Mvc/Mvc.Core/test/JsonOptionsTest.cs b/src/Mvc/Mvc.Core/test/JsonOptionsTest.cs new file mode 100644 index 000000000000..05209836bd45 --- /dev/null +++ b/src/Mvc/Mvc.Core/test/JsonOptionsTest.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json.Serialization.Metadata; +using Microsoft.AspNetCore.Testing; +using Microsoft.DotNet.RemoteExecutor; + +namespace Microsoft.AspNetCore.Mvc; + +public class JsonOptionsTest +{ + [ConditionalFact] + [RemoteExecutionSupported] + public void DefaultSerializerOptions_SetsTypeInfoResolverNull_WhenEnsureJsonTrimmabilityTrue() + { + var options = new RemoteInvokeOptions(); + options.RuntimeConfigurationOptions.Add("Microsoft.AspNetCore.EnsureJsonTrimmability", true.ToString()); + + using var remoteHandle = RemoteExecutor.Invoke(static () => + { + // Arrange + var options = new JsonOptions().JsonSerializerOptions; + + // Assert + Assert.Null(options.TypeInfoResolver); + }, options); + } + + [ConditionalFact] + [RemoteExecutionSupported] + public void DefaultSerializerOptions_SetsTypeInfoResolverToDefault_WhenEnsureJsonTrimmabilityFalse() + { + var options = new RemoteInvokeOptions(); + options.RuntimeConfigurationOptions.Add("Microsoft.AspNetCore.EnsureJsonTrimmability", false.ToString()); + + using var remoteHandle = RemoteExecutor.Invoke(static () => + { + // Arrange + var options = new JsonOptions().JsonSerializerOptions; + + // Assert + Assert.NotNull(options.TypeInfoResolver); + Assert.IsType(options.TypeInfoResolver); + }, options); + } +} diff --git a/src/Shared/ProblemDetails/HttpValidationProblemDetailsJsonConverter.cs b/src/Shared/ProblemDetails/HttpValidationProblemDetailsJsonConverter.cs index d903bf99c34d..6686b2c0d8c1 100644 --- a/src/Shared/ProblemDetails/HttpValidationProblemDetailsJsonConverter.cs +++ b/src/Shared/ProblemDetails/HttpValidationProblemDetailsJsonConverter.cs @@ -23,6 +23,7 @@ public static HttpValidationProblemDetails ReadProblemDetails(ref Utf8JsonReader throw new JsonException("Unexpected end when reading JSON."); } + var objectTypeInfo = options.GetTypeInfo(typeof(object)); while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) { if (reader.ValueTextEquals(Errors.EncodedUtf8Bytes)) @@ -31,7 +32,7 @@ public static HttpValidationProblemDetails ReadProblemDetails(ref Utf8JsonReader } else { - ProblemDetailsJsonConverter.ReadValue(ref reader, problemDetails, options); + ProblemDetailsJsonConverter.ReadValue(ref reader, problemDetails, objectTypeInfo); } } diff --git a/src/Shared/ProblemDetails/ProblemDetailsJsonConverter.cs b/src/Shared/ProblemDetails/ProblemDetailsJsonConverter.cs index 13fa59608f2b..f72524daad9a 100644 --- a/src/Shared/ProblemDetails/ProblemDetailsJsonConverter.cs +++ b/src/Shared/ProblemDetails/ProblemDetailsJsonConverter.cs @@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; using Microsoft.AspNetCore.Mvc; namespace Microsoft.AspNetCore.Http; @@ -25,9 +26,10 @@ public override ProblemDetails Read(ref Utf8JsonReader reader, Type typeToConver throw new JsonException("Unexpected end when reading JSON."); } + var objectTypeInfo = options.GetTypeInfo(typeof(object)); while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) { - ReadValue(ref reader, problemDetails, options); + ReadValue(ref reader, problemDetails, objectTypeInfo); } if (reader.TokenType != JsonTokenType.EndObject) @@ -45,7 +47,7 @@ public override void Write(Utf8JsonWriter writer, ProblemDetails value, JsonSeri writer.WriteEndObject(); } - internal static void ReadValue(ref Utf8JsonReader reader, ProblemDetails value, JsonSerializerOptions options) + internal static void ReadValue(ref Utf8JsonReader reader, ProblemDetails value, JsonTypeInfo extensionDataTypeInfo) { if (TryReadStringProperty(ref reader, Type, out var propertyValue)) { @@ -79,14 +81,7 @@ internal static void ReadValue(ref Utf8JsonReader reader, ProblemDetails value, { var key = reader.GetString()!; reader.Read(); - ReadExtension(value, key, ref reader, options); - } - - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "ProblemDetails.Extensions is annotated to expose this warning to callers.")] - [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "ProblemDetails.Extensions is annotated to expose this warning to callers.")] - static void ReadExtension(ProblemDetails problemDetails, string key, ref Utf8JsonReader reader, JsonSerializerOptions options) - { - problemDetails.Extensions[key] = JsonSerializer.Deserialize(ref reader, typeof(object), options); + value.Extensions[key] = JsonSerializer.Deserialize(ref reader, extensionDataTypeInfo); } } @@ -130,17 +125,17 @@ internal static void WriteProblemDetails(Utf8JsonWriter writer, ProblemDetails v writer.WriteString(Instance, value.Instance); } - WriteExtensions(value, writer, options); - - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "ProblemDetails.Extensions is annotated to expose this warning to callers.")] - [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "ProblemDetails.Extensions is annotated to expose this warning to callers.")] - static void WriteExtensions(ProblemDetails problemDetails, Utf8JsonWriter writer, JsonSerializerOptions options) + foreach (var kvp in value.Extensions) { - foreach (var kvp in problemDetails.Extensions) + writer.WritePropertyName(kvp.Key); + + if (kvp.Value is null) + { + writer.WriteNullValue(); + } + else { - writer.WritePropertyName(kvp.Key); - // When AOT is enabled, Serialize will only work with values specified on the JsonContext. - JsonSerializer.Serialize(writer, kvp.Value, kvp.Value?.GetType() ?? typeof(object), options); + JsonSerializer.Serialize(writer, kvp.Value, options.GetTypeInfo(kvp.Value.GetType())); } } } diff --git a/src/Shared/TrimmingAppContextSwitches.cs b/src/Shared/TrimmingAppContextSwitches.cs new file mode 100644 index 000000000000..641512945289 --- /dev/null +++ b/src/Shared/TrimmingAppContextSwitches.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.AspNetCore.Internal; + +internal sealed class TrimmingAppContextSwitches +{ + private const string EnsureJsonTrimmabilityKey = "Microsoft.AspNetCore.EnsureJsonTrimmability"; + + internal static bool EnsureJsonTrimmability { get; } = AppContext.TryGetSwitch(EnsureJsonTrimmabilityKey, out var enabled) && enabled; +} diff --git a/src/Testing/src/Microsoft.AspNetCore.Testing.csproj b/src/Testing/src/Microsoft.AspNetCore.Testing.csproj index 12180a437d92..d48aac848c9d 100644 --- a/src/Testing/src/Microsoft.AspNetCore.Testing.csproj +++ b/src/Testing/src/Microsoft.AspNetCore.Testing.csproj @@ -40,6 +40,7 @@ --> +