Skip to content

Commit b7ee505

Browse files
authored
Adding untyped JsonTypeInfo overloads (#45593)
* Adding untyped JsonTypeInfo overloads * Adding to unshipped.txt
1 parent 655a02f commit b7ee505

6 files changed

+196
-29
lines changed

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,47 @@ public static class HttpRequestJsonExtensions
126126
}
127127
}
128128

129+
/// <summary>
130+
/// Read JSON from the request and deserialize to object type.
131+
/// If the request's content-type is not a known JSON type then an error will be thrown.
132+
/// </summary>
133+
/// <param name="request">The request to read from.</param>
134+
/// <param name="jsonTypeInfo">Metadata about the type to convert.</param>
135+
/// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
136+
/// <returns>The deserialized value.</returns>
137+
#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters
138+
public static async ValueTask<object?> ReadFromJsonAsync(
139+
#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
140+
this HttpRequest request,
141+
JsonTypeInfo jsonTypeInfo,
142+
CancellationToken cancellationToken = default)
143+
{
144+
if (request == null)
145+
{
146+
throw new ArgumentNullException(nameof(request));
147+
}
148+
149+
if (!request.HasJsonContentType(out var charset))
150+
{
151+
ThrowContentTypeError(request);
152+
}
153+
154+
var encoding = GetEncodingFromCharset(charset);
155+
var (inputStream, usesTranscodingStream) = GetInputStream(request.HttpContext, encoding);
156+
157+
try
158+
{
159+
return await JsonSerializer.DeserializeAsync(inputStream, jsonTypeInfo, cancellationToken);
160+
}
161+
finally
162+
{
163+
if (usesTranscodingStream)
164+
{
165+
await inputStream.DisposeAsync();
166+
}
167+
}
168+
}
169+
129170
/// <summary>
130171
/// Read JSON from the request and deserialize to the specified type.
131172
/// If the request's content-type is not a known JSON type then an error will be thrown.

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

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,50 @@ static async Task WriteAsJsonAsyncSlow(HttpResponse response, TValue value, Json
144144
}
145145
}
146146

147+
/// <summary>
148+
/// Write the specified value as JSON to the response body. The response content-type will be set to
149+
/// the specified content-type.
150+
/// </summary>
151+
/// <param name="response">The response to write JSON to.</param>
152+
/// <param name="value">The value to write as JSON.</param>
153+
/// <param name="jsonTypeInfo">Metadata about the type to convert.</param>
154+
/// <param name="contentType">The content-type to set on the response.</param>
155+
/// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
156+
/// <returns>The task object representing the asynchronous operation.</returns>
157+
#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters
158+
public static Task WriteAsJsonAsync(
159+
#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
160+
this HttpResponse response,
161+
object? value,
162+
JsonTypeInfo jsonTypeInfo,
163+
string? contentType = default,
164+
CancellationToken cancellationToken = default)
165+
{
166+
if (response == null)
167+
{
168+
throw new ArgumentNullException(nameof(response));
169+
}
170+
171+
response.ContentType = contentType ?? JsonConstants.JsonContentTypeWithCharset;
172+
173+
// if no user provided token, pass the RequestAborted token and ignore OperationCanceledException
174+
if (!cancellationToken.CanBeCanceled)
175+
{
176+
return WriteAsJsonAsyncSlow(response, value, jsonTypeInfo);
177+
}
178+
179+
return JsonSerializer.SerializeAsync(response.Body, value, jsonTypeInfo, cancellationToken);
180+
181+
static async Task WriteAsJsonAsyncSlow(HttpResponse response, object? value, JsonTypeInfo jsonTypeInfo)
182+
{
183+
try
184+
{
185+
await JsonSerializer.SerializeAsync(response.Body, value, jsonTypeInfo, response.HttpContext.RequestAborted);
186+
}
187+
catch (OperationCanceledException) { }
188+
}
189+
}
190+
147191
[RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
148192
[RequiresDynamicCode(RequiresDynamicCodeMessage)]
149193
private static async Task WriteAsJsonAsyncSlow<TValue>(

src/Http/Http.Extensions/src/PublicAPI.Unshipped.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,10 @@ Microsoft.AspNetCore.Mvc.ProblemDetails.Type.get -> string? (forwarded, containe
4545
Microsoft.AspNetCore.Mvc.ProblemDetails.Type.set -> void (forwarded, contained in Microsoft.AspNetCore.Http.Abstractions)
4646
Microsoft.Extensions.DependencyInjection.ProblemDetailsServiceCollectionExtensions
4747
Microsoft.Extensions.DependencyInjection.HttpJsonServiceExtensions
48+
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<object?>
4849
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<object?>
4950
static Microsoft.AspNetCore.Http.HttpRequestJsonExtensions.ReadFromJsonAsync<TValue>(this Microsoft.AspNetCore.Http.HttpRequest! request, System.Text.Json.Serialization.Metadata.JsonTypeInfo<TValue>! jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<TValue?>
51+
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!
5052
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!
5153
static Microsoft.AspNetCore.Http.HttpResponseJsonExtensions.WriteAsJsonAsync<TValue>(this Microsoft.AspNetCore.Http.HttpResponse! response, TValue value, System.Text.Json.Serialization.Metadata.JsonTypeInfo<TValue>! jsonTypeInfo, string? contentType = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
5254
*REMOVED*static Microsoft.AspNetCore.Http.RequestDelegateFactory.Create(System.Delegate! handler, Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions? options = null) -> Microsoft.AspNetCore.Http.RequestDelegateResult!

src/Http/Http.Extensions/test/HttpRequestExtensionsTests.cs

Lines changed: 0 additions & 29 deletions
This file was deleted.

src/Http/Http.Extensions/test/HttpRequestJsonExtensionsTests.cs

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

44
using System.Text;
55
using System.Text.Json;
6+
using System.Text.Json.Serialization.Metadata;
67

78
#nullable enable
89

910
namespace Microsoft.AspNetCore.Http.Extensions.Tests;
1011

1112
public class HttpRequestJsonExtensionsTests
1213
{
14+
[Theory]
15+
[InlineData(null, false)]
16+
[InlineData("", false)]
17+
[InlineData("application/xml", false)]
18+
[InlineData("text/json", false)]
19+
[InlineData("text/json; charset=utf-8", false)]
20+
[InlineData("application/json", true)]
21+
[InlineData("application/json; charset=utf-8", true)]
22+
[InlineData("application/ld+json", true)]
23+
[InlineData("APPLICATION/JSON", true)]
24+
[InlineData("APPLICATION/JSON; CHARSET=UTF-8", true)]
25+
[InlineData("APPLICATION/LD+JSON", true)]
26+
public void HasJsonContentType(string contentType, bool hasJsonContentType)
27+
{
28+
var request = new DefaultHttpContext().Request;
29+
request.ContentType = contentType;
30+
31+
Assert.Equal(hasJsonContentType, request.HasJsonContentType());
32+
}
33+
1334
[Fact]
1435
public async Task ReadFromJsonAsyncGeneric_NonJsonContentType_ThrowError()
1536
{
@@ -207,4 +228,49 @@ public async Task ReadFromJsonAsync_WithOptions_ReturnValue()
207228
i => Assert.Equal(1, i),
208229
i => Assert.Equal(2, i));
209230
}
231+
232+
[Fact]
233+
public async Task ReadFromJsonAsync_WithTypeInfo_ReturnValue()
234+
{
235+
// Arrange
236+
var context = new DefaultHttpContext();
237+
context.Request.ContentType = "application/json";
238+
context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("[1,2,]"));
239+
240+
var options = new JsonSerializerOptions();
241+
options.AllowTrailingCommas = true;
242+
options.TypeInfoResolver = new DefaultJsonTypeInfoResolver();
243+
244+
// Act
245+
var result = (List<int>?)await context.Request.ReadFromJsonAsync(options.GetTypeInfo(typeof(List<int>)));
246+
247+
// Assert
248+
Assert.NotNull(result);
249+
Assert.Collection(result,
250+
i => Assert.Equal(1, i),
251+
i => Assert.Equal(2, i));
252+
}
253+
254+
[Fact]
255+
public async Task ReadFromJsonAsync_WithGenericTypeInfo_ReturnValue()
256+
{
257+
// Arrange
258+
var context = new DefaultHttpContext();
259+
context.Request.ContentType = "application/json";
260+
context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("[1,2,]"));
261+
262+
var options = new JsonSerializerOptions();
263+
options.AllowTrailingCommas = true;
264+
options.TypeInfoResolver = new DefaultJsonTypeInfoResolver();
265+
266+
// Act
267+
var typeInfo = (JsonTypeInfo<List<int>>)options.GetTypeInfo(typeof(List<int>));
268+
var result = await context.Request.ReadFromJsonAsync(typeInfo);
269+
270+
// Assert
271+
Assert.NotNull(result);
272+
Assert.Collection(result,
273+
i => Assert.Equal(1, i),
274+
i => Assert.Equal(2, i));
275+
}
210276
}

src/Http/Http.Extensions/test/HttpResponseJsonExtensionsTests.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Text;
66
using System.Text.Json;
77
using System.Text.Json.Serialization;
8+
using System.Text.Json.Serialization.Metadata;
89
using Microsoft.AspNetCore.Testing;
910

1011
#nullable enable
@@ -438,6 +439,48 @@ async IAsyncEnumerable<int> AsyncEnumerable([EnumeratorCancellation] Cancellatio
438439
}
439440
}
440441

442+
[Fact]
443+
public async Task WriteAsJsonAsyncGeneric_WithJsonTypeInfo_JsonResponse()
444+
{
445+
// Arrange
446+
var body = new MemoryStream();
447+
var context = new DefaultHttpContext();
448+
context.Response.Body = body;
449+
450+
// Act
451+
var options = new JsonSerializerOptions();
452+
options.TypeInfoResolver = new DefaultJsonTypeInfoResolver();
453+
454+
await context.Response.WriteAsJsonAsync(new int[] { 1, 2, 3 }, (JsonTypeInfo<int[]>)options.GetTypeInfo(typeof(int[])));
455+
456+
// Assert
457+
Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
458+
459+
var data = Encoding.UTF8.GetString(body.ToArray());
460+
Assert.Equal("[1,2,3]", data);
461+
}
462+
463+
[Fact]
464+
public async Task WriteAsJsonAsync_NullValue_WithJsonTypeInfo_JsonResponse()
465+
{
466+
// Arrange
467+
var body = new MemoryStream();
468+
var context = new DefaultHttpContext();
469+
context.Response.Body = body;
470+
471+
// Act
472+
var options = new JsonSerializerOptions();
473+
options.TypeInfoResolver = new DefaultJsonTypeInfoResolver();
474+
475+
await context.Response.WriteAsJsonAsync(value : null, options.GetTypeInfo(typeof(Uri)));
476+
477+
// Assert
478+
Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
479+
480+
var data = Encoding.UTF8.GetString(body.ToArray());
481+
Assert.Equal("null", data);
482+
}
483+
441484
public class TestObject
442485
{
443486
public string? StringProperty { get; set; }

0 commit comments

Comments
 (0)