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/arcade1b04d6de502c4108ada6ea8e5ccefdc2ddc3ee7b
+
+ 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.78.0.0-beta.23063.7
+ 8.0.0-beta.23063.78.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