diff --git a/src/Http/Http.Extensions/src/HttpRequestJsonExtensions.cs b/src/Http/Http.Extensions/src/HttpRequestJsonExtensions.cs
index a204e74c0477..1bd0b4b28b65 100644
--- a/src/Http/Http.Extensions/src/HttpRequestJsonExtensions.cs
+++ b/src/Http/Http.Extensions/src/HttpRequestJsonExtensions.cs
@@ -122,6 +122,47 @@ public static class HttpRequestJsonExtensions
}
}
+ ///
+ /// Read JSON from the request and deserialize to object type.
+ /// If the request's content-type is not a known JSON type then an error will be thrown.
+ ///
+ /// The request to read from.
+ /// Metadata about the type to convert.
+ /// A used to cancel the operation.
+ /// The deserialized value.
+#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters
+ public static async ValueTask ReadFromJsonAsync(
+#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
+ this HttpRequest request,
+ JsonTypeInfo jsonTypeInfo,
+ CancellationToken cancellationToken = default)
+ {
+ if (request == null)
+ {
+ throw new ArgumentNullException(nameof(request));
+ }
+
+ if (!request.HasJsonContentType(out var charset))
+ {
+ ThrowContentTypeError(request);
+ }
+
+ var encoding = GetEncodingFromCharset(charset);
+ var (inputStream, usesTranscodingStream) = GetInputStream(request.HttpContext, encoding);
+
+ try
+ {
+ return await JsonSerializer.DeserializeAsync(inputStream, jsonTypeInfo, cancellationToken);
+ }
+ finally
+ {
+ if (usesTranscodingStream)
+ {
+ await inputStream.DisposeAsync();
+ }
+ }
+ }
+
///
/// Read JSON from the request and deserialize to the specified type.
/// If the request's content-type is not a known JSON type then an error will be thrown.
diff --git a/src/Http/Http.Extensions/src/HttpResponseJsonExtensions.cs b/src/Http/Http.Extensions/src/HttpResponseJsonExtensions.cs
index 300b836a7d34..34f92a3e78f8 100644
--- a/src/Http/Http.Extensions/src/HttpResponseJsonExtensions.cs
+++ b/src/Http/Http.Extensions/src/HttpResponseJsonExtensions.cs
@@ -139,6 +139,50 @@ static async Task WriteAsJsonAsyncSlow(HttpResponse response, TValue value, Json
}
}
+ ///
+ /// Write the specified value as JSON to the response body. The response content-type will be set to
+ /// the specified content-type.
+ ///
+ /// The response to write JSON to.
+ /// The value to write as JSON.
+ /// Metadata about the type to convert.
+ /// The content-type to set on the response.
+ /// A used to cancel the operation.
+ /// The task object representing the asynchronous operation.
+#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters
+ public static Task WriteAsJsonAsync(
+#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
+ this HttpResponse response,
+ object? value,
+ JsonTypeInfo jsonTypeInfo,
+ string? contentType = default,
+ CancellationToken cancellationToken = default)
+ {
+ if (response == null)
+ {
+ throw new ArgumentNullException(nameof(response));
+ }
+
+ response.ContentType = contentType ?? JsonConstants.JsonContentTypeWithCharset;
+
+ // if no user provided token, pass the RequestAborted token and ignore OperationCanceledException
+ if (!cancellationToken.CanBeCanceled)
+ {
+ return WriteAsJsonAsyncSlow(response, value, jsonTypeInfo);
+ }
+
+ return JsonSerializer.SerializeAsync(response.Body, value, jsonTypeInfo, cancellationToken);
+
+ static async Task WriteAsJsonAsyncSlow(HttpResponse response, object? value, JsonTypeInfo jsonTypeInfo)
+ {
+ try
+ {
+ await JsonSerializer.SerializeAsync(response.Body, value, jsonTypeInfo, response.HttpContext.RequestAborted);
+ }
+ catch (OperationCanceledException) { }
+ }
+ }
+
[RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
private static async Task WriteAsJsonAsyncSlow(
Stream body,
diff --git a/src/Http/Http.Extensions/src/PublicAPI.Unshipped.txt b/src/Http/Http.Extensions/src/PublicAPI.Unshipped.txt
index 6945deb350e5..7182ff420d4f 100644
--- a/src/Http/Http.Extensions/src/PublicAPI.Unshipped.txt
+++ b/src/Http/Http.Extensions/src/PublicAPI.Unshipped.txt
@@ -45,8 +45,10 @@ Microsoft.AspNetCore.Mvc.ProblemDetails.Type.get -> string? (forwarded, containe
Microsoft.AspNetCore.Mvc.ProblemDetails.Type.set -> void (forwarded, contained in Microsoft.AspNetCore.Http.Abstractions)
Microsoft.Extensions.DependencyInjection.ProblemDetailsServiceCollectionExtensions
Microsoft.Extensions.DependencyInjection.HttpJsonServiceExtensions
+static Microsoft.AspNetCore.Http.HttpRequestJsonExtensions.ReadFromJsonAsync(this Microsoft.AspNetCore.Http.HttpRequest! request, System.Text.Json.Serialization.Metadata.JsonTypeInfo! jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
static Microsoft.AspNetCore.Http.HttpRequestJsonExtensions.ReadFromJsonAsync(this Microsoft.AspNetCore.Http.HttpRequest! request, System.Type! type, System.Text.Json.Serialization.JsonSerializerContext! context, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
static Microsoft.AspNetCore.Http.HttpRequestJsonExtensions.ReadFromJsonAsync(this Microsoft.AspNetCore.Http.HttpRequest! request, System.Text.Json.Serialization.Metadata.JsonTypeInfo! jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
+static Microsoft.AspNetCore.Http.HttpResponseJsonExtensions.WriteAsJsonAsync(this Microsoft.AspNetCore.Http.HttpResponse! response, object? value, System.Text.Json.Serialization.Metadata.JsonTypeInfo! jsonTypeInfo, string? contentType = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
static Microsoft.AspNetCore.Http.HttpResponseJsonExtensions.WriteAsJsonAsync(this Microsoft.AspNetCore.Http.HttpResponse! response, object? value, System.Type! type, System.Text.Json.Serialization.JsonSerializerContext! context, string? contentType = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
static Microsoft.AspNetCore.Http.HttpResponseJsonExtensions.WriteAsJsonAsync(this Microsoft.AspNetCore.Http.HttpResponse! response, TValue value, System.Text.Json.Serialization.Metadata.JsonTypeInfo! jsonTypeInfo, string? contentType = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
*REMOVED*static Microsoft.AspNetCore.Http.RequestDelegateFactory.Create(System.Delegate! handler, Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions? options = null) -> Microsoft.AspNetCore.Http.RequestDelegateResult!
diff --git a/src/Http/Http.Extensions/test/HttpRequestExtensionsTests.cs b/src/Http/Http.Extensions/test/HttpRequestExtensionsTests.cs
deleted file mode 100644
index bd6735818e55..000000000000
--- a/src/Http/Http.Extensions/test/HttpRequestExtensionsTests.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-#nullable enable
-
-namespace Microsoft.AspNetCore.Http.Extensions.Tests;
-
-public class HttpRequestExtensionsTests
-{
- [Theory]
- [InlineData(null, false)]
- [InlineData("", false)]
- [InlineData("application/xml", false)]
- [InlineData("text/json", false)]
- [InlineData("text/json; charset=utf-8", false)]
- [InlineData("application/json", true)]
- [InlineData("application/json; charset=utf-8", true)]
- [InlineData("application/ld+json", true)]
- [InlineData("APPLICATION/JSON", true)]
- [InlineData("APPLICATION/JSON; CHARSET=UTF-8", true)]
- [InlineData("APPLICATION/LD+JSON", true)]
- public void HasJsonContentType(string contentType, bool hasJsonContentType)
- {
- var request = new DefaultHttpContext().Request;
- request.ContentType = contentType;
-
- Assert.Equal(hasJsonContentType, request.HasJsonContentType());
- }
-}
diff --git a/src/Http/Http.Extensions/test/HttpRequestJsonExtensionsTests.cs b/src/Http/Http.Extensions/test/HttpRequestJsonExtensionsTests.cs
index 9ac9ddd2a66c..4d63107214e9 100644
--- a/src/Http/Http.Extensions/test/HttpRequestJsonExtensionsTests.cs
+++ b/src/Http/Http.Extensions/test/HttpRequestJsonExtensionsTests.cs
@@ -3,6 +3,7 @@
using System.Text;
using System.Text.Json;
+using System.Text.Json.Serialization.Metadata;
#nullable enable
@@ -10,6 +11,26 @@ namespace Microsoft.AspNetCore.Http.Extensions.Tests;
public class HttpRequestJsonExtensionsTests
{
+ [Theory]
+ [InlineData(null, false)]
+ [InlineData("", false)]
+ [InlineData("application/xml", false)]
+ [InlineData("text/json", false)]
+ [InlineData("text/json; charset=utf-8", false)]
+ [InlineData("application/json", true)]
+ [InlineData("application/json; charset=utf-8", true)]
+ [InlineData("application/ld+json", true)]
+ [InlineData("APPLICATION/JSON", true)]
+ [InlineData("APPLICATION/JSON; CHARSET=UTF-8", true)]
+ [InlineData("APPLICATION/LD+JSON", true)]
+ public void HasJsonContentType(string contentType, bool hasJsonContentType)
+ {
+ var request = new DefaultHttpContext().Request;
+ request.ContentType = contentType;
+
+ Assert.Equal(hasJsonContentType, request.HasJsonContentType());
+ }
+
[Fact]
public async Task ReadFromJsonAsyncGeneric_NonJsonContentType_ThrowError()
{
@@ -207,4 +228,49 @@ public async Task ReadFromJsonAsync_WithOptions_ReturnValue()
i => Assert.Equal(1, i),
i => Assert.Equal(2, i));
}
+
+ [Fact]
+ public async Task ReadFromJsonAsync_WithTypeInfo_ReturnValue()
+ {
+ // Arrange
+ var context = new DefaultHttpContext();
+ context.Request.ContentType = "application/json";
+ context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("[1,2,]"));
+
+ var options = new JsonSerializerOptions();
+ options.AllowTrailingCommas = true;
+ options.TypeInfoResolver = new DefaultJsonTypeInfoResolver();
+
+ // Act
+ var result = (List?)await context.Request.ReadFromJsonAsync(options.GetTypeInfo(typeof(List)));
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Collection(result,
+ i => Assert.Equal(1, i),
+ i => Assert.Equal(2, i));
+ }
+
+ [Fact]
+ public async Task ReadFromJsonAsync_WithGenericTypeInfo_ReturnValue()
+ {
+ // Arrange
+ var context = new DefaultHttpContext();
+ context.Request.ContentType = "application/json";
+ context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("[1,2,]"));
+
+ var options = new JsonSerializerOptions();
+ options.AllowTrailingCommas = true;
+ options.TypeInfoResolver = new DefaultJsonTypeInfoResolver();
+
+ // Act
+ var typeInfo = (JsonTypeInfo>)options.GetTypeInfo(typeof(List));
+ var result = await context.Request.ReadFromJsonAsync(typeInfo);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Collection(result,
+ i => Assert.Equal(1, i),
+ i => Assert.Equal(2, i));
+ }
}
diff --git a/src/Http/Http.Extensions/test/HttpResponseJsonExtensionsTests.cs b/src/Http/Http.Extensions/test/HttpResponseJsonExtensionsTests.cs
index 0f93ce83e644..9562ee94c099 100644
--- a/src/Http/Http.Extensions/test/HttpResponseJsonExtensionsTests.cs
+++ b/src/Http/Http.Extensions/test/HttpResponseJsonExtensionsTests.cs
@@ -5,6 +5,7 @@
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Metadata;
using Microsoft.AspNetCore.Testing;
#nullable enable
@@ -438,6 +439,48 @@ async IAsyncEnumerable AsyncEnumerable([EnumeratorCancellation] Cancellatio
}
}
+ [Fact]
+ public async Task WriteAsJsonAsyncGeneric_WithJsonTypeInfo_JsonResponse()
+ {
+ // Arrange
+ var body = new MemoryStream();
+ var context = new DefaultHttpContext();
+ context.Response.Body = body;
+
+ // Act
+ var options = new JsonSerializerOptions();
+ options.TypeInfoResolver = new DefaultJsonTypeInfoResolver();
+
+ await context.Response.WriteAsJsonAsync(new int[] { 1, 2, 3 }, (JsonTypeInfo)options.GetTypeInfo(typeof(int[])));
+
+ // Assert
+ Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
+
+ var data = Encoding.UTF8.GetString(body.ToArray());
+ Assert.Equal("[1,2,3]", data);
+ }
+
+ [Fact]
+ public async Task WriteAsJsonAsync_NullValue_WithJsonTypeInfo_JsonResponse()
+ {
+ // Arrange
+ var body = new MemoryStream();
+ var context = new DefaultHttpContext();
+ context.Response.Body = body;
+
+ // Act
+ var options = new JsonSerializerOptions();
+ options.TypeInfoResolver = new DefaultJsonTypeInfoResolver();
+
+ await context.Response.WriteAsJsonAsync(value : null, options.GetTypeInfo(typeof(Uri)));
+
+ // Assert
+ Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
+
+ var data = Encoding.UTF8.GetString(body.ToArray());
+ Assert.Equal("null", data);
+ }
+
public class TestObject
{
public string? StringProperty { get; set; }