Skip to content

Commit 40dd230

Browse files
authored
Making JsonOptions AOT/Trimmer-safe with EnsureJsonTrimmability switch (#45886)
* Adding EnsureJsonTrimmability switch * Set TypeResolver to null * Removing RUC/RDC attributes * Removing ProblemDetails.Extension RUC/RDC * Adding Test remote execution support * Adding jsonoptions tests * Update ProblemDetails.cs * Update HttpValidationProblemDetailsJsonConverter.cs
1 parent 7560737 commit 40dd230

22 files changed

+268
-52
lines changed

eng/Dependencies.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ and are generated based on the last package release.
141141
<LatestPackageReference Include="Microsoft.Internal.Runtime.AspNetCore.Transport" />
142142
<LatestPackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
143143
<LatestPackageReference Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" />
144+
<LatestPackageReference Include="Microsoft.DotNet.RemoteExecutor" />
144145
<LatestPackageReference Include="Microsoft.EntityFrameworkCore.Design" />
145146
<LatestPackageReference Include="Microsoft.EntityFrameworkCore.InMemory" />
146147
<LatestPackageReference Include="Microsoft.EntityFrameworkCore.Relational" />

eng/Version.Details.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,5 +319,9 @@
319319
<Uri>https://github.com/dotnet/arcade</Uri>
320320
<Sha>1b04d6de502c4108ada6ea8e5ccefdc2ddc3ee7b</Sha>
321321
</Dependency>
322+
<Dependency Name="Microsoft.DotNet.RemoteExecutor" Version="8.0.0-beta.23063.7">
323+
<Uri>https://github.com/dotnet/arcade</Uri>
324+
<Sha>000000</Sha>
325+
</Dependency>
322326
</ToolsetDependencies>
323327
</Dependencies>

eng/Versions.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@
137137
<!-- Packages from dotnet/arcade -->
138138
<MicrosoftDotNetBuildTasksInstallersVersion>8.0.0-beta.23063.7</MicrosoftDotNetBuildTasksInstallersVersion>
139139
<MicrosoftDotNetBuildTasksTemplatingVersion>8.0.0-beta.23063.7</MicrosoftDotNetBuildTasksTemplatingVersion>
140+
<MicrosoftDotNetRemoteExecutorVersion>8.0.0-beta.23063.7</MicrosoftDotNetRemoteExecutorVersion>
140141
<!-- Packages from dotnet/source-build-externals -->
141142
<MicrosoftSourceBuildIntermediatesourcebuildexternalsVersion>8.0.0-alpha.1.23062.2</MicrosoftSourceBuildIntermediatesourcebuildexternalsVersion>
142143
<!-- Packages from dotnet/xdt -->

src/Http/Http.Abstractions/src/ProblemDetails/ProblemDetails.cs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System.Diagnostics.CodeAnalysis;
54
using System.Text.Json.Serialization;
65
using Microsoft.AspNetCore.Http;
76

@@ -13,8 +12,6 @@ namespace Microsoft.AspNetCore.Mvc;
1312
[JsonConverter(typeof(ProblemDetailsJsonConverter))]
1413
public class ProblemDetails
1514
{
16-
private readonly IDictionary<string, object?> _extensions = new Dictionary<string, object?>(StringComparer.Ordinal);
17-
1815
/// <summary>
1916
/// A URI reference [RFC3986] that identifies the problem type. This specification encourages that, when
2017
/// dereferenced, it provide human-readable documentation for the problem type
@@ -62,10 +59,5 @@ public class ProblemDetails
6259
/// In particular, complex types or collection types may not round-trip to the original type when using the built-in JSON or XML formatters.
6360
/// </remarks>
6461
[JsonExtensionData]
65-
public IDictionary<string, object?> Extensions
66-
{
67-
[RequiresUnreferencedCode("JSON serialization and deserialization of ProblemDetails.Extensions might require types that cannot be statically analyzed.")]
68-
[RequiresDynamicCode("JSON serialization and deserialization of ProblemDetails.Extensions might require types that cannot be statically analyzed.")]
69-
get => _extensions;
70-
}
62+
public IDictionary<string, object?> Extensions { get; } = new Dictionary<string, object?>(StringComparer.Ordinal);
7163
}

src/Http/Http.Abstractions/test/ProblemDetailsJsonConverterTest.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Text;
55
using System.Text.Json;
6+
using System.Text.Json.Nodes;
67
using Microsoft.AspNetCore.Http.Json;
78
using Microsoft.AspNetCore.Mvc;
89

@@ -92,6 +93,39 @@ public void Read_UsingJsonSerializerWorks()
9293
});
9394
}
9495

96+
[Fact]
97+
public void Read_WithUnknownTypeHandling_Works()
98+
{
99+
// Arrange
100+
var type = "https://tools.ietf.org/html/rfc9110#section-15.5.5";
101+
var title = "Not found";
102+
var status = 404;
103+
var detail = "Product not found";
104+
var instance = "http://example.com/products/14";
105+
var traceId = "|37dd3dd5-4a9619f953c40a16.";
106+
var json = $"{{\"type\":\"{type}\",\"title\":\"{title}\",\"status\":{status},\"detail\":\"{detail}\", \"instance\":\"{instance}\",\"traceId\":\"{traceId}\"}}";
107+
var serializerOptions = new JsonSerializerOptions(JsonSerializerOptions) { UnknownTypeHandling = System.Text.Json.Serialization.JsonUnknownTypeHandling.JsonNode };
108+
109+
// Act
110+
var problemDetails = JsonSerializer.Deserialize<ProblemDetails>(json, serializerOptions);
111+
112+
// Assert
113+
Assert.NotNull(problemDetails);
114+
Assert.Equal(type, problemDetails!.Type);
115+
Assert.Equal(title, problemDetails.Title);
116+
Assert.Equal(status, problemDetails.Status);
117+
Assert.Equal(instance, problemDetails.Instance);
118+
Assert.Equal(detail, problemDetails.Detail);
119+
Assert.Collection(
120+
problemDetails.Extensions,
121+
kvp =>
122+
{
123+
Assert.Equal("traceId", kvp.Key);
124+
Assert.IsAssignableFrom<JsonNode>(kvp.Value!);
125+
Assert.Equal(traceId, kvp.Value?.ToString());
126+
});
127+
}
128+
95129
[Fact]
96130
public void Read_WithSomeMissingValues_Works()
97131
{
@@ -178,4 +212,36 @@ public void Write_WithSomeMissingContent_Works()
178212
var actual = Encoding.UTF8.GetString(stream.ToArray());
179213
Assert.Equal(expected, actual);
180214
}
215+
216+
[Fact]
217+
public void Write_WithNullExtensionValue_Works()
218+
{
219+
// Arrange
220+
var value = new ProblemDetails
221+
{
222+
Title = "Not found",
223+
Type = "https://tools.ietf.org/html/rfc9110#section-15.5.5",
224+
Status = 404,
225+
Detail = "Product not found",
226+
Instance = "http://example.com/products/14",
227+
Extensions =
228+
{
229+
{ "traceId", null },
230+
{ "some-data", new[] { "value1", "value2" } }
231+
}
232+
};
233+
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\"]}}";
234+
var converter = new ProblemDetailsJsonConverter();
235+
var stream = new MemoryStream();
236+
237+
// Act
238+
using (var writer = new Utf8JsonWriter(stream))
239+
{
240+
converter.Write(writer, value, JsonSerializerOptions);
241+
}
242+
243+
// Assert
244+
var actual = Encoding.UTF8.GetString(stream.ToArray());
245+
Assert.Equal(expected, actual);
246+
}
181247
}

src/Http/Http.Extensions/src/HttpRequestJsonExtensions.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,14 @@ public static class HttpRequestJsonExtensions
3232
/// <param name="request">The request to read from.</param>
3333
/// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
3434
/// <returns>The task object representing the asynchronous operation.</returns>
35-
[RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
36-
[RequiresDynamicCode(RequiresDynamicCodeMessage)]
3735
public static ValueTask<TValue?> ReadFromJsonAsync<TValue>(
3836
this HttpRequest request,
3937
CancellationToken cancellationToken = default)
4038
{
41-
return request.ReadFromJsonAsync<TValue>(options: null, cancellationToken);
39+
ArgumentNullException.ThrowIfNull(request);
40+
41+
var options = ResolveSerializerOptions(request.HttpContext);
42+
return request.ReadFromJsonAsync(jsonTypeInfo: (JsonTypeInfo<TValue>)options.GetTypeInfo(typeof(TValue)), cancellationToken);
4243
}
4344

4445
/// <summary>
@@ -166,14 +167,15 @@ public static class HttpRequestJsonExtensions
166167
/// <param name="type">The type of object to read.</param>
167168
/// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
168169
/// <returns>The task object representing the asynchronous operation.</returns>
169-
[RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
170-
[RequiresDynamicCode(RequiresDynamicCodeMessage)]
171170
public static ValueTask<object?> ReadFromJsonAsync(
172171
this HttpRequest request,
173172
Type type,
174173
CancellationToken cancellationToken = default)
175174
{
176-
return request.ReadFromJsonAsync(type, options: null, cancellationToken);
175+
ArgumentNullException.ThrowIfNull(request);
176+
177+
var options = ResolveSerializerOptions(request.HttpContext);
178+
return request.ReadFromJsonAsync(jsonTypeInfo: options.GetTypeInfo(type), cancellationToken);
177179
}
178180

179181
/// <summary>

src/Http/Http.Extensions/src/HttpResponseJsonExtensions.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,15 @@ public static partial class HttpResponseJsonExtensions
3030
/// <param name="value">The value to write as JSON.</param>
3131
/// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
3232
/// <returns>The task object representing the asynchronous operation.</returns>
33-
[RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
34-
[RequiresDynamicCode(RequiresDynamicCodeMessage)]
3533
public static Task WriteAsJsonAsync<TValue>(
3634
this HttpResponse response,
3735
TValue value,
3836
CancellationToken cancellationToken = default)
3937
{
40-
return response.WriteAsJsonAsync(value, options: null, contentType: null, cancellationToken);
38+
ArgumentNullException.ThrowIfNull(response);
39+
40+
var options = ResolveSerializerOptions(response.HttpContext);
41+
return response.WriteAsJsonAsync(value, jsonTypeInfo: (JsonTypeInfo<TValue>)options.GetTypeInfo(typeof(TValue)), contentType: null, cancellationToken);
4142
}
4243

4344
/// <summary>
@@ -203,15 +204,16 @@ private static async Task WriteAsJsonAsyncSlow<TValue>(
203204
/// <param name="type">The type of object to write.</param>
204205
/// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
205206
/// <returns>The task object representing the asynchronous operation.</returns>
206-
[RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
207-
[RequiresDynamicCode(RequiresDynamicCodeMessage)]
208207
public static Task WriteAsJsonAsync(
209208
this HttpResponse response,
210209
object? value,
211210
Type type,
212211
CancellationToken cancellationToken = default)
213212
{
214-
return response.WriteAsJsonAsync(value, type, options: null, contentType: null, cancellationToken);
213+
ArgumentNullException.ThrowIfNull(response);
214+
215+
var options = ResolveSerializerOptions(response.HttpContext);
216+
return response.WriteAsJsonAsync(value, jsonTypeInfo: options.GetTypeInfo(type), contentType: null, cancellationToken);
215217
}
216218

217219
/// <summary>

src/Http/Http.Extensions/src/JsonOptions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Text.Encodings.Web;
55
using System.Text.Json;
66
using System.Text.Json.Serialization.Metadata;
7+
using Microsoft.AspNetCore.Internal;
78

89
#nullable enable
910

@@ -26,7 +27,7 @@ public class JsonOptions
2627
// The JsonSerializerOptions.GetTypeInfo method is called directly and needs a defined resolver
2728
// setting the default resolver (reflection-based) but the user can overwrite it directly or calling
2829
// .AddContext<TContext>()
29-
TypeInfoResolver = CreateDefaultTypeResolver()
30+
TypeInfoResolver = TrimmingAppContextSwitches.EnsureJsonTrimmability ? null : CreateDefaultTypeResolver()
3031
};
3132

3233
// Use a copy so the defaults are not modified.

src/Http/Http.Extensions/src/Microsoft.AspNetCore.Http.Extensions.csproj

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,18 @@
1212
</PropertyGroup>
1313

1414
<ItemGroup>
15-
<Compile Include="$(SharedSourceRoot)ObjectMethodExecutor\**\*.cs" LinkBase="Shared"/>
16-
<Compile Include="$(SharedSourceRoot)ParameterBindingMethodCache.cs" LinkBase="Shared"/>
17-
<Compile Include="$(SharedSourceRoot)EndpointMetadataPopulator.cs" LinkBase="Shared"/>
18-
<Compile Include="$(SharedSourceRoot)PropertyAsParameterInfo.cs" LinkBase="Shared"/>
15+
<Compile Include="$(SharedSourceRoot)ObjectMethodExecutor\**\*.cs" LinkBase="Shared" />
16+
<Compile Include="$(SharedSourceRoot)ParameterBindingMethodCache.cs" LinkBase="Shared" />
17+
<Compile Include="$(SharedSourceRoot)EndpointMetadataPopulator.cs" LinkBase="Shared" />
18+
<Compile Include="$(SharedSourceRoot)PropertyAsParameterInfo.cs" LinkBase="Shared" />
1919
<Compile Include="..\..\Shared\StreamCopyOperationInternal.cs" LinkBase="Shared" />
2020
<Compile Include="$(SharedSourceRoot)ApiExplorerTypes\*.cs" LinkBase="Shared" />
2121
<Compile Include="$(SharedSourceRoot)RoutingMetadata\AcceptsMetadata.cs" LinkBase="Shared" />
22-
<Compile Include="$(SharedSourceRoot)TypeNameHelper\TypeNameHelper.cs" LinkBase="Shared"/>
22+
<Compile Include="$(SharedSourceRoot)TypeNameHelper\TypeNameHelper.cs" LinkBase="Shared" />
2323
<Compile Include="$(SharedSourceRoot)ProblemDetails\ProblemDetailsDefaults.cs" LinkBase="Shared" />
24-
<Compile Include="$(SharedSourceRoot)ValueStringBuilder\**\*.cs" LinkBase="Shared"/>
25-
<Compile Include="$(SharedSourceRoot)Json\JsonSerializerExtensions.cs" LinkBase="Shared"/>
24+
<Compile Include="$(SharedSourceRoot)ValueStringBuilder\**\*.cs" LinkBase="Shared" />
25+
<Compile Include="$(SharedSourceRoot)Json\JsonSerializerExtensions.cs" LinkBase="Shared" />
26+
<Compile Include="$(SharedSourceRoot)TrimmingAppContextSwitches.cs" LinkBase="Shared" />
2627
<Compile Include="$(SharedSourceRoot)RouteHandlers\ExecuteHandlerHelper.cs" LinkBase="Shared"/>
2728
</ItemGroup>
2829

@@ -36,4 +37,5 @@
3637
<ItemGroup>
3738
<InternalsVisibleTo Include="Microsoft.AspNetCore.Http.Extensions.Tests" />
3839
</ItemGroup>
40+
3941
</Project>

src/Http/Http.Extensions/src/ProblemDetailsJsonContext.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ namespace Microsoft.AspNetCore.Http;
99

1010
[JsonSerializable(typeof(ProblemDetails))]
1111
[JsonSerializable(typeof(HttpValidationProblemDetails))]
12+
// ExtensionData
13+
[JsonSerializable(typeof(IDictionary<string, object?>))]
1214
// Additional values are specified on JsonSerializerContext to support some values for extensions.
1315
// For example, the DeveloperExceptionMiddleware serializes its complex type to JsonElement, which problem details then needs to serialize.
1416
[JsonSerializable(typeof(JsonElement))]
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<linker>
3+
<assembly fullname="Microsoft.AspNetCore.Http.Extensions">
4+
<type fullname="Microsoft.AspNetCore.Internal.TrimmingAppContextSwitches">
5+
<method signature="System.Boolean get_EnsureJsonTrimmability()" body="stub" value="true" feature="Microsoft.AspNetCore.EnsureJsonTrimmability" featurevalue="true" />
6+
</type>
7+
</assembly>
8+
</linker>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Text.Json.Serialization.Metadata;
5+
using Microsoft.AspNetCore.Http.Json;
6+
using Microsoft.AspNetCore.Testing;
7+
using Microsoft.DotNet.RemoteExecutor;
8+
9+
namespace Microsoft.AspNetCore.Http.Extensions;
10+
11+
public class JsonOptionsTests
12+
{
13+
[ConditionalFact]
14+
[RemoteExecutionSupported]
15+
public void DefaultSerializerOptions_SetsTypeInfoResolverNull_WhenEnsureJsonTrimmabilityTrue()
16+
{
17+
var options = new RemoteInvokeOptions();
18+
options.RuntimeConfigurationOptions.Add("Microsoft.AspNetCore.EnsureJsonTrimmability", true.ToString());
19+
20+
using var remoteHandle = RemoteExecutor.Invoke(static () =>
21+
{
22+
// Arrange
23+
var options = JsonOptions.DefaultSerializerOptions;
24+
25+
// Assert
26+
Assert.Null(options.TypeInfoResolver);
27+
}, options);
28+
}
29+
30+
[ConditionalFact]
31+
[RemoteExecutionSupported]
32+
public void DefaultSerializerOptions_SetsTypeInfoResolverToDefault_WhenEnsureJsonTrimmabilityFalse()
33+
{
34+
var options = new RemoteInvokeOptions();
35+
options.RuntimeConfigurationOptions.Add("Microsoft.AspNetCore.EnsureJsonTrimmability", false.ToString());
36+
37+
using var remoteHandle = RemoteExecutor.Invoke(static () =>
38+
{
39+
// Arrange
40+
var options = JsonOptions.DefaultSerializerOptions;
41+
42+
// Assert
43+
Assert.NotNull(options.TypeInfoResolver);
44+
Assert.IsType<DefaultJsonTypeInfoResolver>(options.TypeInfoResolver);
45+
}, options);
46+
}
47+
}

src/Middleware/Diagnostics/src/DeveloperExceptionPage/DeveloperExceptionPageMiddlewareImpl.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,6 @@ await _problemDetailsService.WriteAsync(new()
202202
}
203203
}
204204

205-
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Values set on ProblemDetails.Extensions are supported by the default writer.")]
206-
[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Values set on ProblemDetails.Extensions are supported by the default writer.")]
207205
private ProblemDetails CreateProblemDetails(ErrorContext errorContext, HttpContext httpContext)
208206
{
209207
var problemDetails = new ProblemDetails

src/Mvc/Mvc.Core/src/JsonOptions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Text.Json;
55
using System.Text.Json.Serialization.Metadata;
6+
using Microsoft.AspNetCore.Internal;
67
using Microsoft.AspNetCore.Mvc.Formatters;
78
using Microsoft.AspNetCore.Mvc.ModelBinding;
89

@@ -42,7 +43,7 @@ public class JsonOptions
4243
// The JsonSerializerOptions.GetTypeInfo method is called directly and needs a defined resolver
4344
// setting the default resolver (reflection-based) but the user can overwrite it directly or calling
4445
// .AddContext<TContext>()
45-
TypeInfoResolver = CreateDefaultTypeResolver()
46+
TypeInfoResolver = TrimmingAppContextSwitches.EnsureJsonTrimmability ? null : CreateDefaultTypeResolver()
4647
};
4748

4849
private static IJsonTypeInfoResolver CreateDefaultTypeResolver()

src/Mvc/Mvc.Core/src/Microsoft.AspNetCore.Mvc.Core.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Microsoft.AspNetCore.Mvc.RouteAttribute</Description>
3333
<Compile Include="$(SharedSourceRoot)MediaType\ReadOnlyMediaTypeHeaderValue.cs" LinkBase="Shared" />
3434
<Compile Include="$(SharedSourceRoot)MediaType\HttpTokenParsingRule.cs" LinkBase="Shared" />
3535
<Compile Include="$(SharedSourceRoot)RoutingMetadata\AcceptsMetadata.cs" LinkBase="Shared" />
36+
<Compile Include="$(SharedSourceRoot)TrimmingAppContextSwitches.cs" LinkBase="Shared" />
3637
<Compile Include="$(SharedSourceRoot)Json\JsonSerializerExtensions.cs" LinkBase="Shared"/>
3738
</ItemGroup>
3839

@@ -61,6 +62,10 @@ Microsoft.AspNetCore.Mvc.RouteAttribute</Description>
6162
<Compile Include="$(SharedSourceRoot)ParameterDefaultValue\*.cs" />
6263
</ItemGroup>
6364

65+
<ItemGroup>
66+
<EmbeddedResource Include="Properties\ILLink.Substitutions.xml" LogicalName="ILLink.Substitutions.xml" />
67+
</ItemGroup>
68+
6469
<ItemGroup>
6570
<InternalsVisibleTo Include="Microsoft.AspNetCore.Mvc" />
6671
<InternalsVisibleTo Include="Microsoft.AspNetCore.Mvc.ApiExplorer" />
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<linker>
3+
<assembly fullname="Microsoft.AspNetCore.Mvc.Core">
4+
<type fullname="Microsoft.AspNetCore.Internal.TrimmingAppContextSwitches">
5+
<method signature="System.Boolean get_EnsureJsonTrimmability()" body="stub" value="true" feature="Microsoft.AspNetCore.EnsureJsonTrimmability" featurevalue="true" />
6+
</type>
7+
</assembly>
8+
</linker>

0 commit comments

Comments
 (0)