Skip to content

Commit 334da01

Browse files
Fix DefaultProblemDetailsWriter polymorphism (#45906)
* Support PD Polymorphism * Code clean up * Update src/Http/Http.Extensions/test/ProblemDetailsDefaultWriterTest.cs Co-authored-by: Stephen Halter <[email protected]> * Combining typeresolvers on postconfigure * Combining typeresolvers in PDWriter * Adding more unit tests * Adding more unit tests * Moving back to PostConfigure * Update ProblemDetailsServiceCollectionExtensions.cs * Adding ProblemDetailsJsonOptionsSetup * Update ProblemDetailsServiceCollectionExtensions.cs * Update DefaultProblemDetailsWriter.cs Co-authored-by: Stephen Halter <[email protected]>
1 parent d10938c commit 334da01

7 files changed

+509
-44
lines changed
Lines changed: 7 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
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;
5-
using System.Runtime.CompilerServices;
64
using System.Text.Json;
7-
using System.Text.Json.Serialization;
8-
using Microsoft.AspNetCore.Mvc;
5+
using Microsoft.AspNetCore.Http.Json;
96
using Microsoft.Extensions.Options;
107
using Microsoft.Net.Http.Headers;
118

@@ -15,11 +12,14 @@ internal sealed partial class DefaultProblemDetailsWriter : IProblemDetailsWrite
1512
{
1613
private static readonly MediaTypeHeaderValue _jsonMediaType = new("application/json");
1714
private static readonly MediaTypeHeaderValue _problemDetailsJsonMediaType = new("application/problem+json");
15+
1816
private readonly ProblemDetailsOptions _options;
17+
private readonly JsonSerializerOptions _serializerOptions;
1918

20-
public DefaultProblemDetailsWriter(IOptions<ProblemDetailsOptions> options)
19+
public DefaultProblemDetailsWriter(IOptions<ProblemDetailsOptions> options, IOptions<JsonOptions> jsonOptions)
2120
{
2221
_options = options.Value;
22+
_serializerOptions = jsonOptions.Value.SerializerOptions;
2323
}
2424

2525
public bool CanWrite(ProblemDetailsContext context)
@@ -49,49 +49,17 @@ public bool CanWrite(ProblemDetailsContext context)
4949
return false;
5050
}
5151

52-
[UnconditionalSuppressMessage("Trimming", "IL2026",
53-
Justification = "JSON serialization of ProblemDetails.Extensions might require types that cannot be statically analyzed. The property is annotated with RequiresUnreferencedCode.")]
54-
[UnconditionalSuppressMessage("Trimming", "IL3050",
55-
Justification = "JSON serialization of ProblemDetails.Extensions might require types that cannot be statically analyzed. The property is annotated with RequiresDynamicCode.")]
5652
public ValueTask WriteAsync(ProblemDetailsContext context)
5753
{
5854
var httpContext = context.HttpContext;
5955
ProblemDetailsDefaults.Apply(context.ProblemDetails, httpContext.Response.StatusCode);
6056
_options.CustomizeProblemDetails?.Invoke(context);
6157

62-
// Use source generation serialization in two scenarios:
63-
// 1. There are no extensions. Source generation is faster and works well with trimming.
64-
// 2. Native AOT. In this case only the data types specified on ProblemDetailsJsonContext will work.
65-
if (context.ProblemDetails.Extensions is { Count: 0 } || !RuntimeFeature.IsDynamicCodeSupported)
66-
{
67-
return new ValueTask(httpContext.Response.WriteAsJsonAsync(
68-
context.ProblemDetails,
69-
ProblemDetailsJsonContext.Default.ProblemDetails,
70-
contentType: "application/problem+json"));
71-
}
58+
var problemDetailsType = context.ProblemDetails.GetType();
7259

7360
return new ValueTask(httpContext.Response.WriteAsJsonAsync(
7461
context.ProblemDetails,
75-
options: null,
62+
_serializerOptions.GetTypeInfo(problemDetailsType),
7663
contentType: "application/problem+json"));
7764
}
78-
79-
// Additional values are specified on JsonSerializerContext to support some values for extensions.
80-
// For example, the DeveloperExceptionMiddleware serializes its complex type to JsonElement, which problem details then needs to serialize.
81-
[JsonSerializable(typeof(ProblemDetails))]
82-
[JsonSerializable(typeof(JsonElement))]
83-
[JsonSerializable(typeof(string))]
84-
[JsonSerializable(typeof(decimal))]
85-
[JsonSerializable(typeof(float))]
86-
[JsonSerializable(typeof(double))]
87-
[JsonSerializable(typeof(int))]
88-
[JsonSerializable(typeof(long))]
89-
[JsonSerializable(typeof(Guid))]
90-
[JsonSerializable(typeof(Uri))]
91-
[JsonSerializable(typeof(TimeSpan))]
92-
[JsonSerializable(typeof(DateTime))]
93-
[JsonSerializable(typeof(DateTimeOffset))]
94-
internal sealed partial class ProblemDetailsJsonContext : JsonSerializerContext
95-
{
96-
}
9765
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public class JsonOptions
3333
/// <summary>
3434
/// Gets the <see cref="JsonSerializerOptions"/>.
3535
/// </summary>
36-
public JsonSerializerOptions SerializerOptions { get; } = new JsonSerializerOptions(DefaultSerializerOptions);
36+
public JsonSerializerOptions SerializerOptions { get; internal set; } = new JsonSerializerOptions(DefaultSerializerOptions);
3737

3838
#pragma warning disable IL2026 // Suppressed in Microsoft.AspNetCore.Http.Extensions.WarningSuppressions.xml
3939
#pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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;
5+
using System.Text.Json;
6+
using Microsoft.AspNetCore.Mvc;
7+
8+
namespace Microsoft.AspNetCore.Http;
9+
10+
[JsonSerializable(typeof(ProblemDetails))]
11+
[JsonSerializable(typeof(HttpValidationProblemDetails))]
12+
// Additional values are specified on JsonSerializerContext to support some values for extensions.
13+
// For example, the DeveloperExceptionMiddleware serializes its complex type to JsonElement, which problem details then needs to serialize.
14+
[JsonSerializable(typeof(JsonElement))]
15+
internal sealed partial class ProblemDetailsJsonContext : JsonSerializerContext
16+
{
17+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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.Extensions.Options;
7+
8+
namespace Microsoft.AspNetCore.Http;
9+
10+
internal sealed class ProblemDetailsJsonOptionsSetup : IPostConfigureOptions<JsonOptions>
11+
{
12+
public void PostConfigure(string? name, JsonOptions options)
13+
{
14+
if (options.SerializerOptions.TypeInfoResolver is not null)
15+
{
16+
if (options.SerializerOptions.IsReadOnly)
17+
{
18+
options.SerializerOptions = new(options.SerializerOptions);
19+
}
20+
21+
// Combine the current resolver with our internal problem details context
22+
options.SerializerOptions.TypeInfoResolver = JsonTypeInfoResolver.Combine(options.SerializerOptions.TypeInfoResolver!, ProblemDetailsJsonContext.Default);
23+
}
24+
}
25+
}

src/Http/Http.Extensions/src/ProblemDetailsServiceCollectionExtensions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using Microsoft.AspNetCore.Http;
5+
using Microsoft.AspNetCore.Http.Json;
56
using Microsoft.AspNetCore.Mvc;
67
using Microsoft.Extensions.DependencyInjection.Extensions;
8+
using Microsoft.Extensions.Options;
79

810
namespace Microsoft.Extensions.DependencyInjection;
911

@@ -38,6 +40,7 @@ public static IServiceCollection AddProblemDetails(
3840
// Adding default services;
3941
services.TryAddSingleton<IProblemDetailsService, ProblemDetailsService>();
4042
services.TryAddEnumerable(ServiceDescriptor.Singleton<IProblemDetailsWriter, DefaultProblemDetailsWriter>());
43+
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<JsonOptions>, ProblemDetailsJsonOptionsSetup>());
4144

4245
if (configure != null)
4346
{

0 commit comments

Comments
 (0)