From ed44a47dc7cb44fd5fcc42b8e2ceddc502bd9ca0 Mon Sep 17 00:00:00 2001 From: David Pine Date: Tue, 18 Jul 2023 13:43:28 -0500 Subject: [PATCH 01/14] Contributes to #87577 --- .../src/System.Net.Http.Json.csproj | 2 + ...ientJsonExtensions.GetAsAsyncEnumerable.cs | 136 ++++++++++++++++++ ...entJsonExtensions.ReadAsAsyncEnumerable.cs | 70 +++++++++ 3 files changed, 208 insertions(+) create mode 100644 src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.GetAsAsyncEnumerable.cs create mode 100644 src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.ReadAsAsyncEnumerable.cs diff --git a/src/libraries/System.Net.Http.Json/src/System.Net.Http.Json.csproj b/src/libraries/System.Net.Http.Json/src/System.Net.Http.Json.csproj index 34f94be7e01092..31e25840a374fb 100644 --- a/src/libraries/System.Net.Http.Json/src/System.Net.Http.Json.csproj +++ b/src/libraries/System.Net.Http.Json/src/System.Net.Http.Json.csproj @@ -11,7 +11,9 @@ System.Net.Http.Json.JsonContent + + diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.GetAsAsyncEnumerable.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.GetAsAsyncEnumerable.cs new file mode 100644 index 00000000000000..2a3e2e2bd00666 --- /dev/null +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.GetAsAsyncEnumerable.cs @@ -0,0 +1,136 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Runtime.CompilerServices; +using System.Text.Json; +using System.Text.Json.Serialization.Metadata; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Http.Json +{ + /// + /// Contains the extensions methods for using JSON as the content-type in HttpClient. + /// + public static partial class HttpClientJsonExtensions + { + [RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)] + [RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)] + public static IAsyncEnumerable GetFromJsonAsAsyncEnumerable(this HttpClient client, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, JsonSerializerOptions? options, CancellationToken cancellationToken = default) => + GetFromJsonAsAsyncEnumerable(client, CreateUri(requestUri), options, cancellationToken); + + [RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)] + [RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)] + public static IAsyncEnumerable GetFromJsonAsAsyncEnumerable(this HttpClient client, Uri? requestUri, JsonSerializerOptions? options, CancellationToken cancellationToken = default) => + FromJsonStreamAsyncCore(s_getAsync, client, requestUri, options, cancellationToken); + + public static IAsyncEnumerable GetFromJsonAsAsyncEnumerable(this HttpClient client, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) => + GetFromJsonAsAsyncEnumerable(client, CreateUri(requestUri), jsonTypeInfo, cancellationToken); + + public static IAsyncEnumerable GetFromJsonAsAsyncEnumerable(this HttpClient client, Uri? requestUri, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) => + FromJsonStreamAsyncCore(s_getAsync, client, requestUri, jsonTypeInfo, cancellationToken); + + [RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)] + [RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)] + public static IAsyncEnumerable GetFromJsonAsAsyncEnumerable(this HttpClient client, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, CancellationToken cancellationToken = default) => + GetFromJsonAsAsyncEnumerable(client, requestUri, options: null, cancellationToken); + + [RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)] + [RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)] + public static IAsyncEnumerable GetFromJsonAsAsyncEnumerable(this HttpClient client, Uri? requestUri, CancellationToken cancellationToken = default) => + GetFromJsonAsAsyncEnumerable(client, requestUri, options: null, cancellationToken); + + [RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)] + [RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)] + private static IAsyncEnumerable FromJsonStreamAsyncCore(Func> getMethod, HttpClient client, Uri? requestUri, JsonSerializerOptions? options, CancellationToken cancellationToken = default) => + FromJsonStreamAsyncCore(getMethod, client, requestUri, static (stream, options, cancellation) => JsonSerializer.DeserializeAsyncEnumerable(stream, options ?? JsonHelpers.s_defaultSerializerOptions, cancellation), options, cancellationToken); + + private static IAsyncEnumerable FromJsonStreamAsyncCore(Func> getMethod, HttpClient client, Uri? requestUri, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken) => + FromJsonStreamAsyncCore(getMethod, client, requestUri, static (stream, options, cancellation) => JsonSerializer.DeserializeAsyncEnumerable(stream, options, cancellation), jsonTypeInfo, cancellationToken); + + private static IAsyncEnumerable FromJsonStreamAsyncCore( + Func> getMethod, + HttpClient client, + Uri? requestUri, + Func> deserializeMethod, + TJsonOptions jsonOptions, + CancellationToken cancellationToken) + { + if (client is null) + { + throw new ArgumentNullException(nameof(client)); + } + + TimeSpan timeout = client.Timeout; + + // Create the CTS before the initial SendAsync so that the SendAsync counts against the timeout. + CancellationTokenSource? linkedCTS = null; + if (timeout != Timeout.InfiniteTimeSpan) + { + linkedCTS = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + linkedCTS.CancelAfter(timeout); + } + + // We call SendAsync outside of the async Core method to propagate exception even without awaiting the returned task. + Task responseTask; + try + { + // Intentionally using cancellationToken instead of the linked one here as HttpClient will enforce the Timeout on its own for this part + responseTask = getMethod(client, requestUri, cancellationToken); + } + catch + { + linkedCTS?.Dispose(); + throw; + } + + bool usingResponseHeadersRead = !ReferenceEquals(getMethod, s_deleteAsync); + + return Core(client, responseTask, usingResponseHeadersRead, linkedCTS, deserializeMethod, jsonOptions, cancellationToken); + + static async IAsyncEnumerable Core( + HttpClient client, + Task responseTask, + bool usingResponseHeadersRead, + CancellationTokenSource? linkedCTS, + Func> deserializeMethod, + TJsonOptions jsonOptions, + [EnumeratorCancellation] CancellationToken cancellationToken) + { + try + { + using HttpResponseMessage response = await responseTask.ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + + Debug.Assert(client.MaxResponseContentBufferSize is > 0 and <= int.MaxValue); + int contentLengthLimit = (int)client.MaxResponseContentBufferSize; + + if (response.Content.Headers.ContentLength is long contentLength && contentLength > contentLengthLimit) + { + LengthLimitReadStream.ThrowExceededBufferLimit(contentLengthLimit); + } + + using Stream contentStream = await HttpContentJsonExtensions.GetContentStreamAsync(response.Content, linkedCTS?.Token ?? cancellationToken).ConfigureAwait(false); + + // If ResponseHeadersRead wasn't used, HttpClient will have already buffered the whole response upfront. No need to check the limit again. + Stream readStream = usingResponseHeadersRead + ? new LengthLimitReadStream(contentStream, (int)client.MaxResponseContentBufferSize) + : contentStream; + + await foreach (TValue value in deserializeMethod(readStream, jsonOptions, linkedCTS?.Token ?? cancellationToken).ConfigureAwait(false)) + { + yield return value; + } + } + finally + { + linkedCTS?.Dispose(); + } + } + } + } +} diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.ReadAsAsyncEnumerable.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.ReadAsAsyncEnumerable.cs new file mode 100644 index 00000000000000..b62c047f19370c --- /dev/null +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.ReadAsAsyncEnumerable.cs @@ -0,0 +1,70 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Runtime.CompilerServices; +using System.Text.Json; +using System.Text.Json.Serialization.Metadata; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Http.Json +{ + public static partial class HttpContentJsonExtensions + { + [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)] + [RequiresDynamicCode(SerializationDynamicCodeMessage)] + public static IAsyncEnumerable ReadFromJsonAsAsyncEnumerable(this HttpContent content, JsonSerializerOptions? options, CancellationToken cancellationToken = default) + { + if (content is null) + { + throw new ArgumentNullException(nameof(content)); + } + + return ReadFromJsonAsAsyncEnumerableCore(content, options, cancellationToken); + } + + [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)] + [RequiresDynamicCode(SerializationDynamicCodeMessage)] + public static IAsyncEnumerable ReadFromJsonAsAsyncEnumerable(this HttpContent content, CancellationToken cancellationToken = default) + { + return ReadFromJsonAsAsyncEnumerable(content, options: null, cancellationToken: cancellationToken); + } + + [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)] + [RequiresDynamicCode(SerializationDynamicCodeMessage)] + private static async IAsyncEnumerable ReadFromJsonAsAsyncEnumerableCore(HttpContent content, JsonSerializerOptions? options, [EnumeratorCancellation] CancellationToken cancellationToken) + { + using (Stream contentStream = await GetContentStreamAsync(content, cancellationToken).ConfigureAwait(false)) + { + await foreach (T value in JsonSerializer.DeserializeAsyncEnumerable(contentStream, options ?? JsonHelpers.s_defaultSerializerOptions, cancellationToken).ConfigureAwait(false)) + { + yield return value; + } + } + } + + public static IAsyncEnumerable ReadFromJsonAsAsyncEnumerable(this HttpContent content, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) + { + if (content is null) + { + throw new ArgumentNullException(nameof(content)); + } + + return ReadFromJsonAsAsyncEnumerableCore(content, jsonTypeInfo, cancellationToken); + } + + private static async IAsyncEnumerable ReadFromJsonAsAsyncEnumerableCore(HttpContent content, JsonTypeInfo jsonTypeInfo, [EnumeratorCancellation] CancellationToken cancellationToken) + { + using (Stream contentStream = await GetContentStreamAsync(content, cancellationToken).ConfigureAwait(false)) + { + await foreach (T value in JsonSerializer.DeserializeAsyncEnumerable(contentStream, jsonTypeInfo, cancellationToken).ConfigureAwait(false)) + { + yield return value; + } + } + } + } +} From 9901e29263bd9e67b968455ef6cf779567f22ed4 Mon Sep 17 00:00:00 2001 From: David Pine Date: Wed, 19 Jul 2023 15:05:30 -0500 Subject: [PATCH 02/14] More updates --- ...ientJsonExtensions.GetAsAsyncEnumerable.cs | 184 ++++++++++++------ ...entJsonExtensions.ReadAsAsyncEnumerable.cs | 42 ++-- 2 files changed, 160 insertions(+), 66 deletions(-) diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.GetAsAsyncEnumerable.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.GetAsAsyncEnumerable.cs index 2a3e2e2bd00666..617531d7807bee 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.GetAsAsyncEnumerable.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.GetAsAsyncEnumerable.cs @@ -13,51 +13,105 @@ namespace System.Net.Http.Json { - /// - /// Contains the extensions methods for using JSON as the content-type in HttpClient. - /// public static partial class HttpClientJsonExtensions { [RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)] [RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)] - public static IAsyncEnumerable GetFromJsonAsAsyncEnumerable(this HttpClient client, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, JsonSerializerOptions? options, CancellationToken cancellationToken = default) => + public static IAsyncEnumerable GetFromJsonAsAsyncEnumerable( + this HttpClient client, + [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, + JsonSerializerOptions? options, + CancellationToken cancellationToken = default) => GetFromJsonAsAsyncEnumerable(client, CreateUri(requestUri), options, cancellationToken); [RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)] [RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)] - public static IAsyncEnumerable GetFromJsonAsAsyncEnumerable(this HttpClient client, Uri? requestUri, JsonSerializerOptions? options, CancellationToken cancellationToken = default) => + public static IAsyncEnumerable GetFromJsonAsAsyncEnumerable( + this HttpClient client, + Uri? requestUri, + JsonSerializerOptions? options, + CancellationToken cancellationToken = default) => FromJsonStreamAsyncCore(s_getAsync, client, requestUri, options, cancellationToken); - public static IAsyncEnumerable GetFromJsonAsAsyncEnumerable(this HttpClient client, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) => + public static IAsyncEnumerable GetFromJsonAsAsyncEnumerable( + this HttpClient client, + [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, + JsonTypeInfo jsonTypeInfo, + CancellationToken cancellationToken = default) => GetFromJsonAsAsyncEnumerable(client, CreateUri(requestUri), jsonTypeInfo, cancellationToken); - public static IAsyncEnumerable GetFromJsonAsAsyncEnumerable(this HttpClient client, Uri? requestUri, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) => + public static IAsyncEnumerable GetFromJsonAsAsyncEnumerable( + this HttpClient client, + Uri? requestUri, + JsonTypeInfo jsonTypeInfo, + CancellationToken cancellationToken = default) => FromJsonStreamAsyncCore(s_getAsync, client, requestUri, jsonTypeInfo, cancellationToken); [RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)] [RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)] - public static IAsyncEnumerable GetFromJsonAsAsyncEnumerable(this HttpClient client, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, CancellationToken cancellationToken = default) => + public static IAsyncEnumerable GetFromJsonAsAsyncEnumerable( + this HttpClient client, + [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, + CancellationToken cancellationToken = default) => GetFromJsonAsAsyncEnumerable(client, requestUri, options: null, cancellationToken); [RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)] [RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)] - public static IAsyncEnumerable GetFromJsonAsAsyncEnumerable(this HttpClient client, Uri? requestUri, CancellationToken cancellationToken = default) => + public static IAsyncEnumerable GetFromJsonAsAsyncEnumerable( + this HttpClient client, + Uri? requestUri, + CancellationToken cancellationToken = default) => GetFromJsonAsAsyncEnumerable(client, requestUri, options: null, cancellationToken); [RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)] [RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)] - private static IAsyncEnumerable FromJsonStreamAsyncCore(Func> getMethod, HttpClient client, Uri? requestUri, JsonSerializerOptions? options, CancellationToken cancellationToken = default) => - FromJsonStreamAsyncCore(getMethod, client, requestUri, static (stream, options, cancellation) => JsonSerializer.DeserializeAsyncEnumerable(stream, options ?? JsonHelpers.s_defaultSerializerOptions, cancellation), options, cancellationToken); + private static IAsyncEnumerable FromJsonStreamAsyncCore( + Func> getMethod, + HttpClient client, + Uri? requestUri, + JsonSerializerOptions? options, + CancellationToken cancellationToken) + { + if (client is null) + { + throw new ArgumentNullException(nameof(client)); + } + + CancellationTokenSource? linkedCTS = CreateLinkedCTSFromClientTimeout(client, cancellationToken); + Task responseTask = GetHttpResponseMessageTask(getMethod, client, requestUri, linkedCTS, cancellationToken); + + return Core(client, responseTask, options ?? JsonHelpers.s_defaultSerializerOptions, linkedCTS, cancellationToken); + + static async IAsyncEnumerable Core( + HttpClient client, + Task responseTask, + JsonSerializerOptions options, + CancellationTokenSource? linkedCTS, + [EnumeratorCancellation] CancellationToken cancellationToken) + { + try + { + using HttpResponseMessage response = await EnsureHttpResponseAsync(client, responseTask) + .ConfigureAwait(false); - private static IAsyncEnumerable FromJsonStreamAsyncCore(Func> getMethod, HttpClient client, Uri? requestUri, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken) => - FromJsonStreamAsyncCore(getMethod, client, requestUri, static (stream, options, cancellation) => JsonSerializer.DeserializeAsyncEnumerable(stream, options, cancellation), jsonTypeInfo, cancellationToken); + await foreach (TValue? value in response.Content.ReadFromJsonAsAsyncEnumerable( + options, cancellationToken)) + { + yield return value; + } + } + finally + { + linkedCTS?.Dispose(); + } + } + } - private static IAsyncEnumerable FromJsonStreamAsyncCore( + private static IAsyncEnumerable FromJsonStreamAsyncCore( Func> getMethod, HttpClient client, Uri? requestUri, - Func> deserializeMethod, - TJsonOptions jsonOptions, + JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken) { if (client is null) @@ -65,6 +119,40 @@ public static partial class HttpClientJsonExtensions throw new ArgumentNullException(nameof(client)); } + CancellationTokenSource? linkedCTS = CreateLinkedCTSFromClientTimeout(client, cancellationToken); + Task responseTask = GetHttpResponseMessageTask(getMethod, client, requestUri, linkedCTS, cancellationToken); + + return Core(client, responseTask, jsonTypeInfo, linkedCTS, cancellationToken); + + static async IAsyncEnumerable Core( + HttpClient client, + Task responseTask, + JsonTypeInfo jsonTypeInfo, + CancellationTokenSource? linkedCTS, + [EnumeratorCancellation] CancellationToken cancellationToken) + { + try + { + using HttpResponseMessage response = await EnsureHttpResponseAsync(client, responseTask) + .ConfigureAwait(false); + + await foreach (TValue? value in response.Content.ReadFromJsonAsAsyncEnumerable( + jsonTypeInfo, cancellationToken)) + { + yield return value; + } + } + finally + { + linkedCTS?.Dispose(); + } + } + } + + private static CancellationTokenSource? CreateLinkedCTSFromClientTimeout( + HttpClient client, + CancellationToken cancellationToken) + { TimeSpan timeout = client.Timeout; // Create the CTS before the initial SendAsync so that the SendAsync counts against the timeout. @@ -75,6 +163,16 @@ public static partial class HttpClientJsonExtensions linkedCTS.CancelAfter(timeout); } + return linkedCTS; + } + + private static Task GetHttpResponseMessageTask( + Func> getMethod, + HttpClient client, + Uri? requestUri, + CancellationTokenSource? linkedCTS, + CancellationToken cancellationToken) + { // We call SendAsync outside of the async Core method to propagate exception even without awaiting the returned task. Task responseTask; try @@ -88,49 +186,25 @@ public static partial class HttpClientJsonExtensions throw; } - bool usingResponseHeadersRead = !ReferenceEquals(getMethod, s_deleteAsync); - - return Core(client, responseTask, usingResponseHeadersRead, linkedCTS, deserializeMethod, jsonOptions, cancellationToken); - - static async IAsyncEnumerable Core( - HttpClient client, - Task responseTask, - bool usingResponseHeadersRead, - CancellationTokenSource? linkedCTS, - Func> deserializeMethod, - TJsonOptions jsonOptions, - [EnumeratorCancellation] CancellationToken cancellationToken) - { - try - { - using HttpResponseMessage response = await responseTask.ConfigureAwait(false); - response.EnsureSuccessStatusCode(); - - Debug.Assert(client.MaxResponseContentBufferSize is > 0 and <= int.MaxValue); - int contentLengthLimit = (int)client.MaxResponseContentBufferSize; - - if (response.Content.Headers.ContentLength is long contentLength && contentLength > contentLengthLimit) - { - LengthLimitReadStream.ThrowExceededBufferLimit(contentLengthLimit); - } + return responseTask; + } - using Stream contentStream = await HttpContentJsonExtensions.GetContentStreamAsync(response.Content, linkedCTS?.Token ?? cancellationToken).ConfigureAwait(false); + private static async Task EnsureHttpResponseAsync( + HttpClient client, + Task responseTask) + { + HttpResponseMessage response = await responseTask.ConfigureAwait(false); + response.EnsureSuccessStatusCode(); - // If ResponseHeadersRead wasn't used, HttpClient will have already buffered the whole response upfront. No need to check the limit again. - Stream readStream = usingResponseHeadersRead - ? new LengthLimitReadStream(contentStream, (int)client.MaxResponseContentBufferSize) - : contentStream; + Debug.Assert(client.MaxResponseContentBufferSize is > 0 and <= int.MaxValue); + int contentLengthLimit = (int)client.MaxResponseContentBufferSize; - await foreach (TValue value in deserializeMethod(readStream, jsonOptions, linkedCTS?.Token ?? cancellationToken).ConfigureAwait(false)) - { - yield return value; - } - } - finally - { - linkedCTS?.Dispose(); - } + if (response.Content.Headers.ContentLength is long contentLength && contentLength > contentLengthLimit) + { + LengthLimitReadStream.ThrowExceededBufferLimit(contentLengthLimit); } + + return response; } } } diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.ReadAsAsyncEnumerable.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.ReadAsAsyncEnumerable.cs index b62c047f19370c..ea4060332031a9 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.ReadAsAsyncEnumerable.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.ReadAsAsyncEnumerable.cs @@ -16,37 +16,51 @@ public static partial class HttpContentJsonExtensions { [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)] [RequiresDynamicCode(SerializationDynamicCodeMessage)] - public static IAsyncEnumerable ReadFromJsonAsAsyncEnumerable(this HttpContent content, JsonSerializerOptions? options, CancellationToken cancellationToken = default) + public static IAsyncEnumerable ReadFromJsonAsAsyncEnumerable( + this HttpContent content, + JsonSerializerOptions? options, + CancellationToken cancellationToken = default) { if (content is null) { throw new ArgumentNullException(nameof(content)); } - return ReadFromJsonAsAsyncEnumerableCore(content, options, cancellationToken); + return ReadFromJsonAsAsyncEnumerableCore(content, options, cancellationToken); } [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)] [RequiresDynamicCode(SerializationDynamicCodeMessage)] - public static IAsyncEnumerable ReadFromJsonAsAsyncEnumerable(this HttpContent content, CancellationToken cancellationToken = default) + public static IAsyncEnumerable ReadFromJsonAsAsyncEnumerable( + this HttpContent content, + CancellationToken cancellationToken = default) { - return ReadFromJsonAsAsyncEnumerable(content, options: null, cancellationToken: cancellationToken); + return ReadFromJsonAsAsyncEnumerable(content, options: null, cancellationToken: cancellationToken); } [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)] [RequiresDynamicCode(SerializationDynamicCodeMessage)] - private static async IAsyncEnumerable ReadFromJsonAsAsyncEnumerableCore(HttpContent content, JsonSerializerOptions? options, [EnumeratorCancellation] CancellationToken cancellationToken) + private static async IAsyncEnumerable ReadFromJsonAsAsyncEnumerableCore( + HttpContent content, + JsonSerializerOptions? options, + [EnumeratorCancellation] CancellationToken cancellationToken) { - using (Stream contentStream = await GetContentStreamAsync(content, cancellationToken).ConfigureAwait(false)) + using (Stream contentStream = await GetContentStreamAsync(content, cancellationToken) + .ConfigureAwait(false)) { - await foreach (T value in JsonSerializer.DeserializeAsyncEnumerable(contentStream, options ?? JsonHelpers.s_defaultSerializerOptions, cancellationToken).ConfigureAwait(false)) + await foreach (TValue? value in JsonSerializer.DeserializeAsyncEnumerable( + contentStream, options ?? JsonHelpers.s_defaultSerializerOptions, cancellationToken) + .ConfigureAwait(false)) { yield return value; } } } - public static IAsyncEnumerable ReadFromJsonAsAsyncEnumerable(this HttpContent content, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) + public static IAsyncEnumerable ReadFromJsonAsAsyncEnumerable( + this HttpContent content, + JsonTypeInfo jsonTypeInfo, + CancellationToken cancellationToken = default) { if (content is null) { @@ -56,11 +70,17 @@ public static partial class HttpContentJsonExtensions return ReadFromJsonAsAsyncEnumerableCore(content, jsonTypeInfo, cancellationToken); } - private static async IAsyncEnumerable ReadFromJsonAsAsyncEnumerableCore(HttpContent content, JsonTypeInfo jsonTypeInfo, [EnumeratorCancellation] CancellationToken cancellationToken) + private static async IAsyncEnumerable ReadFromJsonAsAsyncEnumerableCore( + HttpContent content, + JsonTypeInfo jsonTypeInfo, + [EnumeratorCancellation] CancellationToken cancellationToken) { - using (Stream contentStream = await GetContentStreamAsync(content, cancellationToken).ConfigureAwait(false)) + using (Stream contentStream = await GetContentStreamAsync(content, cancellationToken) + .ConfigureAwait(false)) { - await foreach (T value in JsonSerializer.DeserializeAsyncEnumerable(contentStream, jsonTypeInfo, cancellationToken).ConfigureAwait(false)) + await foreach (TValue? value in JsonSerializer.DeserializeAsyncEnumerable( + contentStream, jsonTypeInfo, cancellationToken) + .ConfigureAwait(false)) { yield return value; } From 352141c9b51185c17afa37e7c9c1f241168b5420 Mon Sep 17 00:00:00 2001 From: David Pine Date: Thu, 20 Jul 2023 10:51:37 -0500 Subject: [PATCH 03/14] Added unit tests, generated ref, and minor clean up --- .../ref/System.Net.Http.Json.cs | 89 +++++++++------ .../src/System.Net.Http.Json.csproj | 4 +- ...ientJsonExtensions.Get.AsyncEnumerable.cs} | 0 ...pContentJsonExtensions.AsyncEnumerable.cs} | 0 .../HttpClientJsonExtensionsTests.cs | 105 ++++++++++++++++++ .../HttpContentJsonExtensionsTests.cs | 60 ++++++++-- .../tests/FunctionalTests/TestClasses.cs | 26 +++++ 7 files changed, 239 insertions(+), 45 deletions(-) rename src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/{HttpClientJsonExtensions.GetAsAsyncEnumerable.cs => HttpClientJsonExtensions.Get.AsyncEnumerable.cs} (100%) rename src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/{HttpContentJsonExtensions.ReadAsAsyncEnumerable.cs => HttpContentJsonExtensions.AsyncEnumerable.cs} (100%) diff --git a/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.cs b/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.cs index b1520a4d98dc2b..efde74c2b3c49f 100644 --- a/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.cs +++ b/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.cs @@ -8,107 +8,128 @@ namespace System.Net.Http.Json { public static partial class HttpClientJsonExtensions { - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] public static System.Threading.Tasks.Task DeleteFromJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, System.Type type, System.Text.Json.JsonSerializerOptions? options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public static System.Threading.Tasks.Task DeleteFromJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, System.Type type, System.Text.Json.Serialization.JsonSerializerContext context, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] - public static System.Threading.Tasks.Task DeleteFromJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, System.Type type, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] + public static System.Threading.Tasks.Task DeleteFromJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, System.Type type, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] public static System.Threading.Tasks.Task DeleteFromJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, System.Type type, System.Text.Json.JsonSerializerOptions? options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public static System.Threading.Tasks.Task DeleteFromJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, System.Type type, System.Text.Json.Serialization.JsonSerializerContext context, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] - public static System.Threading.Tasks.Task DeleteFromJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, System.Type type, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] + public static System.Threading.Tasks.Task DeleteFromJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, System.Type type, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] public static System.Threading.Tasks.Task DeleteFromJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, System.Text.Json.JsonSerializerOptions? options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public static System.Threading.Tasks.Task DeleteFromJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] - public static System.Threading.Tasks.Task DeleteFromJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] + public static System.Threading.Tasks.Task DeleteFromJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] public static System.Threading.Tasks.Task DeleteFromJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, System.Text.Json.JsonSerializerOptions? options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public static System.Threading.Tasks.Task DeleteFromJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] public static System.Threading.Tasks.Task DeleteFromJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] + public static System.Collections.Generic.IAsyncEnumerable GetFromJsonAsAsyncEnumerable(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, System.Text.Json.JsonSerializerOptions? options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public static System.Collections.Generic.IAsyncEnumerable GetFromJsonAsAsyncEnumerable(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] + public static System.Collections.Generic.IAsyncEnumerable GetFromJsonAsAsyncEnumerable(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] + public static System.Collections.Generic.IAsyncEnumerable GetFromJsonAsAsyncEnumerable(this System.Net.Http.HttpClient client, System.Uri? requestUri, System.Text.Json.JsonSerializerOptions? options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public static System.Collections.Generic.IAsyncEnumerable GetFromJsonAsAsyncEnumerable(this System.Net.Http.HttpClient client, System.Uri? requestUri, System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] + public static System.Collections.Generic.IAsyncEnumerable GetFromJsonAsAsyncEnumerable(this System.Net.Http.HttpClient client, System.Uri? requestUri, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] public static System.Threading.Tasks.Task GetFromJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, System.Type type, System.Text.Json.JsonSerializerOptions? options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public static System.Threading.Tasks.Task GetFromJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, System.Type type, System.Text.Json.Serialization.JsonSerializerContext context, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] - public static System.Threading.Tasks.Task GetFromJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, System.Type type, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] + public static System.Threading.Tasks.Task GetFromJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, System.Type type, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] public static System.Threading.Tasks.Task GetFromJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, System.Type type, System.Text.Json.JsonSerializerOptions? options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public static System.Threading.Tasks.Task GetFromJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, System.Type type, System.Text.Json.Serialization.JsonSerializerContext context, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] - public static System.Threading.Tasks.Task GetFromJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, System.Type type, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] + public static System.Threading.Tasks.Task GetFromJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, System.Type type, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] public static System.Threading.Tasks.Task GetFromJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, System.Text.Json.JsonSerializerOptions? options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public static System.Threading.Tasks.Task GetFromJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] - public static System.Threading.Tasks.Task GetFromJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] + public static System.Threading.Tasks.Task GetFromJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] public static System.Threading.Tasks.Task GetFromJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, System.Text.Json.JsonSerializerOptions? options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public static System.Threading.Tasks.Task GetFromJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] - public static System.Threading.Tasks.Task GetFromJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] + public static System.Threading.Tasks.Task GetFromJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] - public static System.Threading.Tasks.Task PostAsJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, TValue value, System.Text.Json.JsonSerializerOptions? options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public static System.Threading.Tasks.Task PostAsJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, TValue value, System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] + public static System.Threading.Tasks.Task PatchAsJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, TValue value, System.Text.Json.JsonSerializerOptions? options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public static System.Threading.Tasks.Task PatchAsJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, TValue value, System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] - public static System.Threading.Tasks.Task PostAsJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, TValue value, System.Threading.CancellationToken cancellationToken) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] + public static System.Threading.Tasks.Task PatchAsJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, TValue value, System.Threading.CancellationToken cancellationToken) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] - public static System.Threading.Tasks.Task PostAsJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, TValue value, System.Text.Json.JsonSerializerOptions? options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public static System.Threading.Tasks.Task PostAsJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, TValue value, System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] + public static System.Threading.Tasks.Task PatchAsJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, TValue value, System.Text.Json.JsonSerializerOptions? options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public static System.Threading.Tasks.Task PatchAsJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, TValue value, System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] - public static System.Threading.Tasks.Task PostAsJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, TValue value, System.Threading.CancellationToken cancellationToken) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] + public static System.Threading.Tasks.Task PatchAsJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, TValue value, System.Threading.CancellationToken cancellationToken) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] - public static System.Threading.Tasks.Task PutAsJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, TValue value, System.Text.Json.JsonSerializerOptions? options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public static System.Threading.Tasks.Task PutAsJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, TValue value, System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] + public static System.Threading.Tasks.Task PostAsJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, TValue value, System.Text.Json.JsonSerializerOptions? options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public static System.Threading.Tasks.Task PostAsJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, TValue value, System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] - public static System.Threading.Tasks.Task PutAsJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, TValue value, System.Threading.CancellationToken cancellationToken) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] + public static System.Threading.Tasks.Task PostAsJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, TValue value, System.Threading.CancellationToken cancellationToken) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] - public static System.Threading.Tasks.Task PutAsJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, TValue value, System.Text.Json.JsonSerializerOptions? options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public static System.Threading.Tasks.Task PutAsJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, TValue value, System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] + public static System.Threading.Tasks.Task PostAsJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, TValue value, System.Text.Json.JsonSerializerOptions? options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public static System.Threading.Tasks.Task PostAsJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, TValue value, System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] - public static System.Threading.Tasks.Task PutAsJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, TValue value, System.Threading.CancellationToken cancellationToken) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] + public static System.Threading.Tasks.Task PostAsJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, TValue value, System.Threading.CancellationToken cancellationToken) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] - public static System.Threading.Tasks.Task PatchAsJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, TValue value, System.Text.Json.JsonSerializerOptions? options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public static System.Threading.Tasks.Task PatchAsJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, TValue value, System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] + public static System.Threading.Tasks.Task PutAsJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, TValue value, System.Text.Json.JsonSerializerOptions? options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public static System.Threading.Tasks.Task PutAsJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, TValue value, System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] - public static System.Threading.Tasks.Task PatchAsJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, TValue value, System.Threading.CancellationToken cancellationToken) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] + public static System.Threading.Tasks.Task PutAsJsonAsync(this System.Net.Http.HttpClient client, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Uri")] string? requestUri, TValue value, System.Threading.CancellationToken cancellationToken) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] - public static System.Threading.Tasks.Task PatchAsJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, TValue value, System.Text.Json.JsonSerializerOptions? options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public static System.Threading.Tasks.Task PatchAsJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, TValue value, System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] + public static System.Threading.Tasks.Task PutAsJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, TValue value, System.Text.Json.JsonSerializerOptions? options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public static System.Threading.Tasks.Task PutAsJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, TValue value, System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] - public static System.Threading.Tasks.Task PatchAsJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, TValue value, System.Threading.CancellationToken cancellationToken) { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] + public static System.Threading.Tasks.Task PutAsJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, TValue value, System.Threading.CancellationToken cancellationToken) { throw null; } } public static partial class HttpContentJsonExtensions { + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] + public static System.Collections.Generic.IAsyncEnumerable ReadFromJsonAsAsyncEnumerable(this System.Net.Http.HttpContent content, System.Text.Json.JsonSerializerOptions? options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public static System.Collections.Generic.IAsyncEnumerable ReadFromJsonAsAsyncEnumerable(this System.Net.Http.HttpContent content, System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] + public static System.Collections.Generic.IAsyncEnumerable ReadFromJsonAsAsyncEnumerable(this System.Net.Http.HttpContent content, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")] [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] public static System.Threading.Tasks.Task ReadFromJsonAsync(this System.Net.Http.HttpContent content, System.Type type, System.Text.Json.JsonSerializerOptions? options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } diff --git a/src/libraries/System.Net.Http.Json/src/System.Net.Http.Json.csproj b/src/libraries/System.Net.Http.Json/src/System.Net.Http.Json.csproj index 31e25840a374fb..9ed9b541f24031 100644 --- a/src/libraries/System.Net.Http.Json/src/System.Net.Http.Json.csproj +++ b/src/libraries/System.Net.Http.Json/src/System.Net.Http.Json.csproj @@ -11,9 +11,9 @@ System.Net.Http.Json.JsonContent - + - + diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.GetAsAsyncEnumerable.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs similarity index 100% rename from src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.GetAsAsyncEnumerable.cs rename to src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.ReadAsAsyncEnumerable.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.AsyncEnumerable.cs similarity index 100% rename from src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.ReadAsAsyncEnumerable.cs rename to src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.AsyncEnumerable.cs diff --git a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs index 6c142dfe28ac3a..d7ad2c09d7c687 100644 --- a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs +++ b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Net.Test.Common; using System.Text.Json; @@ -288,6 +289,13 @@ public void TestHttpClientIsNullAsync() AssertExtensions.Throws(clientParamName, () => client.GetFromJsonAsync(uriString, JsonContext.Default.Person)); AssertExtensions.Throws(clientParamName, () => client.GetFromJsonAsync(uri, JsonContext.Default.Person)); + AssertExtensions.Throws(clientParamName, () => client.GetFromJsonAsAsyncEnumerable(uriString)); + AssertExtensions.Throws(clientParamName, () => client.GetFromJsonAsAsyncEnumerable(uri)); + AssertExtensions.Throws(clientParamName, () => client.GetFromJsonAsAsyncEnumerable(uriString, options: null)); + AssertExtensions.Throws(clientParamName, () => client.GetFromJsonAsAsyncEnumerable(uri, options: null)); + AssertExtensions.Throws(clientParamName, () => client.GetFromJsonAsAsyncEnumerable(uriString, jsonTypeInfo: null)); + AssertExtensions.Throws(clientParamName, () => client.GetFromJsonAsAsyncEnumerable(uri, jsonTypeInfo: null)); + AssertExtensions.Throws(clientParamName, () => client.PostAsJsonAsync(uriString, null)); AssertExtensions.Throws(clientParamName, () => client.PostAsJsonAsync(uri, null)); AssertExtensions.Throws(clientParamName, () => client.PostAsJsonAsync(uriString, null, JsonContext.Default.Person)); @@ -314,6 +322,9 @@ public void TestTypeMetadataIsNull() AssertExtensions.Throws(jsonTypeInfoParamName, () => client.GetFromJsonAsync(uriString, JsonContext.Default.Person)); AssertExtensions.Throws(jsonTypeInfoParamName, () => client.GetFromJsonAsync(uri, JsonContext.Default.Person)); + AssertExtensions.Throws(jsonTypeInfoParamName, () => client.GetFromJsonAsAsyncEnumerable(uriString, JsonContext.Default.Person)); + AssertExtensions.Throws(jsonTypeInfoParamName, () => client.GetFromJsonAsAsyncEnumerable(uri, JsonContext.Default.Person)); + AssertExtensions.Throws(jsonTypeInfoParamName, () => client.PostAsJsonAsync(uriString, null, JsonContext.Default.Person)); AssertExtensions.Throws(jsonTypeInfoParamName, () => client.PostAsJsonAsync(uri, null, JsonContext.Default.Person)); @@ -355,6 +366,52 @@ await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync( }); } + [Fact] + public async Task AllowNullRequestUrlAsAsyncEnuermable() + { + await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync( + async (handler, uri) => + { + using (HttpClient client = new HttpClient(handler)) + { + client.BaseAddress = uri; + + static void AssertPeopleEquality(List actualPeople) + { + for (int i = 0; i < People.WomenOfProgramming.Length; i++) + { + var expected = People.WomenOfProgramming[i]; + var actual = actualPeople[i]; + + Person.AssertPersonEquality(expected, actual); + } + } + + List people = new List(); + await foreach (Person? person in client.GetFromJsonAsAsyncEnumerable((string)null)) + { + people.Add(Assert.IsType(person)); + } + + AssertPeopleEquality(people); + + people = new List(); + await foreach (Person? person in client.GetFromJsonAsAsyncEnumerable((Uri)null)) + { + people.Add(Assert.IsType(person)); + } + + AssertPeopleEquality(people); + } + }, + async server => { + List headers = new List { new HttpHeaderData("Content-Type", "application/json") }; + string json = People.Serialize(); + + await server.HandleRequestAsync(content: json, headers: headers); + }); + } + public static IEnumerable GetFromJsonAsync_EnforcesMaxResponseContentBufferSize_MemberData() => from useDeleteAsync in new[] { true, false } from limit in new[] { 2, 100, 100000 } @@ -413,6 +470,54 @@ await LoopbackServer.CreateClientAndServerAsync(async uri => Exception ex = await Assert.ThrowsAsync(() => useDeleteAsync ? client.DeleteFromJsonAsync(uri) : client.GetFromJsonAsync(uri)); +#if NETCORE + Assert.Contains("HttpClient.Timeout", ex.Message); + Assert.IsType(ex.InnerException); +#endif + + exceptionThrown.SetResult(0); + }, + async server => + { + // The client may timeout before even connecting the server + await Task.WhenAny(exceptionThrown.Task, Task.Run(async () => + { + try + { + await server.AcceptConnectionAsync(async connection => + { + if (!slowHeaders) + { + await connection.SendPartialResponseHeadersAsync(headers: new[] { new HttpHeaderData("Content-Length", "42") }); + } + + await exceptionThrown.Task; + }); + } + catch { } + })); + }); + } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] // No Socket support + [InlineData(true)] + [InlineData( false)] + public async Task GetFromJsonAsAsyncEnumerable_EnforcesTimeout(bool slowHeaders) + { + TaskCompletionSource exceptionThrown = new(TaskCreationOptions.RunContinuationsAsynchronously); + + await LoopbackServer.CreateClientAndServerAsync(async uri => + { + using var client = new HttpClient { Timeout = TimeSpan.FromMilliseconds(100) }; + + Exception ex = await Assert.ThrowsAsync(async () => + { + await foreach (string? str in client.GetFromJsonAsAsyncEnumerable(uri)) + { + _ = str; + } + }); + #if NETCORE Assert.Contains("HttpClient.Timeout", ex.Message); Assert.IsType(ex.InnerException); diff --git a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpContentJsonExtensionsTests.cs b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpContentJsonExtensionsTests.cs index 450df417d673b7..6d2be61be1a1e9 100644 --- a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpContentJsonExtensionsTests.cs +++ b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpContentJsonExtensionsTests.cs @@ -20,6 +20,10 @@ public void ThrowOnNull() HttpContent content = null; AssertExtensions.Throws("content", () => content.ReadFromJsonAsync()); AssertExtensions.Throws("content", () => content.ReadFromJsonAsync(typeof(Person))); + + AssertExtensions.Throws("content", () => content.ReadFromJsonAsAsyncEnumerable()); + AssertExtensions.Throws("content", () => content.ReadFromJsonAsAsyncEnumerable(options: null)); + AssertExtensions.Throws("content", () => content.ReadFromJsonAsAsyncEnumerable(jsonTypeInfo: null)); } [Theory] @@ -55,13 +59,6 @@ await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync( { using (HttpClient client = new HttpClient(handler)) { - static void AssertPersonEquality(Person first, Person second) - { - Assert.Equal(first.Age, second.Age); - Assert.Equal(first.Name, second.Name); - Assert.Equal(first.Parent, second.Parent); - Assert.Equal(first.PlaceOfBirth, second.PlaceOfBirth); - } var request = new HttpRequestMessage(HttpMethod.Get, uri); HttpResponseMessage response = await client.SendAsync(request); Person per1 = (Person) await response.Content.ReadFromJsonAsync(typeof(Person)); @@ -71,7 +68,7 @@ static void AssertPersonEquality(Person first, Person second) response = await client.SendAsync(request); Person per2 = (Person) await response.Content.ReadFromJsonAsync(typeof(Person), options: null); per2.Validate(); - AssertPersonEquality(per1, per2); + Person.AssertPersonEquality(per1, per2); request = new HttpRequestMessage(HttpMethod.Get, uri); response = await client.SendAsync(request); @@ -82,7 +79,7 @@ static void AssertPersonEquality(Person first, Person second) response = await client.SendAsync(request); per2 = await response.Content.ReadFromJsonAsync(options:null); per2.Validate(); - AssertPersonEquality(per1, per2); + Person.AssertPersonEquality(per1, per2); } }, server => server.HandleRequestAsync(headers: _headers, content: json)); @@ -117,6 +114,50 @@ await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync( server => server.HandleRequestAsync(headers: _headers, content: "null")); } + [Fact] + public async Task HttpContentReturnValueIsNullWithAsAsyncEnumerable() + { + await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync( + async (handler, uri) => + { + using (HttpClient client = new HttpClient(handler)) + { + var request = new HttpRequestMessage(HttpMethod.Get, uri); + HttpResponseMessage response = await client.SendAsync(request); + await foreach (Person? per in response.Content.ReadFromJsonAsAsyncEnumerable()) + { + Assert.Null(per); + } + } + }, + server => server.HandleRequestAsync(headers: _headers, content: "null")); + } + + [Fact] + public async Task TestReadFromJsonAsAsyncEnumerableNoMessageBodyAsync() + { + await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync( + async (handler, uri) => + { + using (HttpClient client = new HttpClient(handler)) + { + var request = new HttpRequestMessage(HttpMethod.Get, uri); + HttpResponseMessage response = await client.SendAsync(request); + + // As of now, we pass the message body to the serializer even when its empty which causes the serializer to throw. + JsonException ex = await Assert.ThrowsAsync(async () => + { + await foreach (Person? per in response.Content.ReadFromJsonAsAsyncEnumerable()) + { + _ = per; + } + }); + Assert.Contains("Path: $ | LineNumber: 0 | BytePositionInLine: 0", ex.Message); + } + }, + server => server.HandleRequestAsync(headers: _headers)); + } + [Fact] public async Task TestReadFromJsonNoMessageBodyAsync() { @@ -210,6 +251,7 @@ await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync( await server.HandleRequestAsync(statusCode: HttpStatusCode.OK, headers: headers, bytes: Encoding.Unicode.GetBytes(json)); }); } + [Fact] public async Task EnsureDefaultJsonSerializerOptionsAsync() { diff --git a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/TestClasses.cs b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/TestClasses.cs index 715477247e6f3d..a1c8eff95365ce 100644 --- a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/TestClasses.cs +++ b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/TestClasses.cs @@ -38,6 +38,32 @@ public string SerializeWithNumbersAsStrings(JsonSerializerOptions options = null options.NumberHandling = options.NumberHandling | JsonNumberHandling.WriteAsString; return JsonSerializer.Serialize(this, options); } + + public static void AssertPersonEquality(Person first, Person second) + { + Assert.Equal(first.Age, second.Age); + Assert.Equal(first.Name, second.Name); + Assert.Equal(first.Parent, second.Parent); + Assert.Equal(first.PlaceOfBirth, second.PlaceOfBirth); + } + } + + internal class People + { + public static int PeopleCount => WomenOfProgramming.Length; + + public static Person[] WomenOfProgramming = new[] + { + new Person { Name = "Ada Lovelace", Age = 13_140, PlaceOfBirth = "London, England" }, + new Person { Name = "Jean Bartik", Age = 31_390, PlaceOfBirth = "Alanthus Grove, Missouri, U.S." }, + new Person { Name = "Grace Hopper", Age = 31_025, PlaceOfBirth = "New York City, New York, U.S." }, + new Person { Name = "Margaret Hamilton", Age = 31_390, PlaceOfBirth = "Paoli, Indiana, U.S." }, + }; + + public static string Serialize(JsonSerializerOptions options = null) + { + return JsonSerializer.Serialize(WomenOfProgramming, options); + } } internal static class JsonOptions From 73c44b90dbafebe34abddf0b341cdea65950ddf7 Mon Sep 17 00:00:00 2001 From: David Pine Date: Thu, 20 Jul 2023 12:59:01 -0500 Subject: [PATCH 04/14] Added missing triple slash comments --- ...lientJsonExtensions.Get.AsyncEnumerable.cs | 64 +++++++++++++++++++ ...tpContentJsonExtensions.AsyncEnumerable.cs | 40 +++++++++--- 2 files changed, 95 insertions(+), 9 deletions(-) diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs index 617531d7807bee..1fee6afab7cb78 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs @@ -15,6 +15,17 @@ namespace System.Net.Http.Json { public static partial class HttpClientJsonExtensions { + /// + /// Sends an HTTP GET request to the specified and returns the value that results + /// from deserializing the response body as JSON in an async enumerable operation. + /// + /// The target type to deserialize to. + /// The client used to send the request. + /// The Uri the request is sent to. + /// + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// An that represents the deserialized response body. + /// The is . [RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)] [RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)] public static IAsyncEnumerable GetFromJsonAsAsyncEnumerable( @@ -24,6 +35,17 @@ public static partial class HttpClientJsonExtensions CancellationToken cancellationToken = default) => GetFromJsonAsAsyncEnumerable(client, CreateUri(requestUri), options, cancellationToken); + /// + /// Sends an HTTP GETrequest to the specified and returns the value that results + /// from deserializing the response body as JSON in an async enumerable operation. + /// + /// The target type to deserialize to. + /// The client used to send the request. + /// The Uri the request is sent to. + /// + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// An that represents the deserialized response body. + /// The is . [RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)] [RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)] public static IAsyncEnumerable GetFromJsonAsAsyncEnumerable( @@ -33,6 +55,17 @@ public static partial class HttpClientJsonExtensions CancellationToken cancellationToken = default) => FromJsonStreamAsyncCore(s_getAsync, client, requestUri, options, cancellationToken); + /// + /// Sends an HTTP GETrequest to the specified and returns the value that results + /// from deserializing the response body as JSON in an async enumerable operation. + /// + /// The target type to deserialize to. + /// The client used to send the request. + /// The Uri the request is sent to. + /// Source generated JsonTypeInfo to control the behavior during deserialization. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// An that represents the deserialized response body. + /// The is . public static IAsyncEnumerable GetFromJsonAsAsyncEnumerable( this HttpClient client, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, @@ -40,6 +73,17 @@ public static partial class HttpClientJsonExtensions CancellationToken cancellationToken = default) => GetFromJsonAsAsyncEnumerable(client, CreateUri(requestUri), jsonTypeInfo, cancellationToken); + /// + /// Sends an HTTP GETrequest to the specified and returns the value that results + /// from deserializing the response body as JSON in an async enumerable operation. + /// + /// The target type to deserialize to. + /// The client used to send the request. + /// The Uri the request is sent to. + /// Source generated JsonTypeInfo to control the behavior during deserialization. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// An that represents the deserialized response body. + /// The is . public static IAsyncEnumerable GetFromJsonAsAsyncEnumerable( this HttpClient client, Uri? requestUri, @@ -47,6 +91,16 @@ public static partial class HttpClientJsonExtensions CancellationToken cancellationToken = default) => FromJsonStreamAsyncCore(s_getAsync, client, requestUri, jsonTypeInfo, cancellationToken); + /// + /// Sends an HTTP GETrequest to the specified and returns the value that results + /// from deserializing the response body as JSON in an async enumerable operation. + /// + /// The target type to deserialize to. + /// The client used to send the request. + /// The Uri the request is sent to. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// An that represents the deserialized response body. + /// The is . [RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)] [RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)] public static IAsyncEnumerable GetFromJsonAsAsyncEnumerable( @@ -55,6 +109,16 @@ public static partial class HttpClientJsonExtensions CancellationToken cancellationToken = default) => GetFromJsonAsAsyncEnumerable(client, requestUri, options: null, cancellationToken); + /// + /// Sends an HTTP GETrequest to the specified and returns the value that results + /// from deserializing the response body as JSON in an async enumerable operation. + /// + /// The target type to deserialize to. + /// The client used to send the request. + /// The Uri the request is sent to. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// An that represents the deserialized response body. + /// The is . [RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)] [RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)] public static IAsyncEnumerable GetFromJsonAsAsyncEnumerable( diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.AsyncEnumerable.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.AsyncEnumerable.cs index ea4060332031a9..1bd294ad2e2dca 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.AsyncEnumerable.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.AsyncEnumerable.cs @@ -14,6 +14,37 @@ namespace System.Net.Http.Json { public static partial class HttpContentJsonExtensions { + /// + /// Reads the HTTP content and returns the value that results from deserializing the content as + /// JSON in an async enumerable operation. + /// + /// The target type to deserialize to. + /// + /// + /// An that represents the deserialized response body. + /// + /// The is . + /// + [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)] + [RequiresDynamicCode(SerializationDynamicCodeMessage)] + public static IAsyncEnumerable ReadFromJsonAsAsyncEnumerable( + this HttpContent content, + CancellationToken cancellationToken = default) => + ReadFromJsonAsAsyncEnumerable(content, options: null, cancellationToken: cancellationToken); + + /// + /// Reads the HTTP content and returns the value that results from deserializing the content as + /// JSON in an async enumerable operation. + /// + /// The target type to deserialize to. + /// The content to read from. + /// Options to control the behavior during deserialization. + /// The default options are those specified by . + /// + /// An that represents the deserialized response body. + /// + /// The is . + /// [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)] [RequiresDynamicCode(SerializationDynamicCodeMessage)] public static IAsyncEnumerable ReadFromJsonAsAsyncEnumerable( @@ -29,15 +60,6 @@ public static partial class HttpContentJsonExtensions return ReadFromJsonAsAsyncEnumerableCore(content, options, cancellationToken); } - [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)] - [RequiresDynamicCode(SerializationDynamicCodeMessage)] - public static IAsyncEnumerable ReadFromJsonAsAsyncEnumerable( - this HttpContent content, - CancellationToken cancellationToken = default) - { - return ReadFromJsonAsAsyncEnumerable(content, options: null, cancellationToken: cancellationToken); - } - [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)] [RequiresDynamicCode(SerializationDynamicCodeMessage)] private static async IAsyncEnumerable ReadFromJsonAsAsyncEnumerableCore( From 04a2537fc15529586a7f8cb4cad62c29eb5088e1 Mon Sep 17 00:00:00 2001 From: David Pine Date: Fri, 21 Jul 2023 07:58:07 -0500 Subject: [PATCH 05/14] Update src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs Co-authored-by: Eirik Tsarpalis --- .../tests/FunctionalTests/HttpClientJsonExtensionsTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs index d7ad2c09d7c687..82fb36d2ef9d43 100644 --- a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs +++ b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs @@ -367,7 +367,7 @@ await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync( } [Fact] - public async Task AllowNullRequestUrlAsAsyncEnuermable() + public async Task AllowNullRequestUrlAsAsyncEnumerable() { await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync( async (handler, uri) => From ad465c85a15fd7a1923e51845319c1d8f834dd78 Mon Sep 17 00:00:00 2001 From: David Pine Date: Fri, 21 Jul 2023 10:35:40 -0500 Subject: [PATCH 06/14] Correct the preprocessor directives, and delegate to JsonTypeInfo overload - per peer feedback. --- ...lientJsonExtensions.Get.AsyncEnumerable.cs | 35 ++----------------- .../HttpClientJsonExtensionsTests.cs | 4 +-- 2 files changed, 5 insertions(+), 34 deletions(-) diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs index 1fee6afab7cb78..96d3add0551e85 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs @@ -136,39 +136,10 @@ public static partial class HttpClientJsonExtensions JsonSerializerOptions? options, CancellationToken cancellationToken) { - if (client is null) - { - throw new ArgumentNullException(nameof(client)); - } - - CancellationTokenSource? linkedCTS = CreateLinkedCTSFromClientTimeout(client, cancellationToken); - Task responseTask = GetHttpResponseMessageTask(getMethod, client, requestUri, linkedCTS, cancellationToken); - - return Core(client, responseTask, options ?? JsonHelpers.s_defaultSerializerOptions, linkedCTS, cancellationToken); + options ??= JsonSerializerOptions.Default; + var jsonTypeInfo = (JsonTypeInfo)options.GetTypeInfo(typeof(TValue)); - static async IAsyncEnumerable Core( - HttpClient client, - Task responseTask, - JsonSerializerOptions options, - CancellationTokenSource? linkedCTS, - [EnumeratorCancellation] CancellationToken cancellationToken) - { - try - { - using HttpResponseMessage response = await EnsureHttpResponseAsync(client, responseTask) - .ConfigureAwait(false); - - await foreach (TValue? value in response.Content.ReadFromJsonAsAsyncEnumerable( - options, cancellationToken)) - { - yield return value; - } - } - finally - { - linkedCTS?.Dispose(); - } - } + return FromJsonStreamAsyncCore(getMethod, client, requestUri, jsonTypeInfo, cancellationToken); } private static IAsyncEnumerable FromJsonStreamAsyncCore( diff --git a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs index 82fb36d2ef9d43..d1e9fd19ac025a 100644 --- a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs +++ b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs @@ -470,7 +470,7 @@ await LoopbackServer.CreateClientAndServerAsync(async uri => Exception ex = await Assert.ThrowsAsync(() => useDeleteAsync ? client.DeleteFromJsonAsync(uri) : client.GetFromJsonAsync(uri)); -#if NETCORE +#if NETCOREAPP Assert.Contains("HttpClient.Timeout", ex.Message); Assert.IsType(ex.InnerException); #endif @@ -518,7 +518,7 @@ await LoopbackServer.CreateClientAndServerAsync(async uri => } }); -#if NETCORE +#if NETCOREAPP Assert.Contains("HttpClient.Timeout", ex.Message); Assert.IsType(ex.InnerException); #endif From 4c4ff608fb1d19f2dcde9cffca83c3551e3e8594 Mon Sep 17 00:00:00 2001 From: David Pine Date: Fri, 21 Jul 2023 11:11:48 -0500 Subject: [PATCH 07/14] Refactor for deferred execution, remove helper methods since they're no longer needed --- ...lientJsonExtensions.Get.AsyncEnumerable.cs | 102 +++++++----------- 1 file changed, 36 insertions(+), 66 deletions(-) diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs index 96d3add0551e85..f873d175694b36 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs @@ -154,22 +154,50 @@ public static partial class HttpClientJsonExtensions throw new ArgumentNullException(nameof(client)); } - CancellationTokenSource? linkedCTS = CreateLinkedCTSFromClientTimeout(client, cancellationToken); - Task responseTask = GetHttpResponseMessageTask(getMethod, client, requestUri, linkedCTS, cancellationToken); - - return Core(client, responseTask, jsonTypeInfo, linkedCTS, cancellationToken); + return Core(getMethod, client, requestUri, jsonTypeInfo, cancellationToken); static async IAsyncEnumerable Core( + Func> getMethod, HttpClient client, - Task responseTask, + Uri? requestUri, JsonTypeInfo jsonTypeInfo, - CancellationTokenSource? linkedCTS, [EnumeratorCancellation] CancellationToken cancellationToken) { + TimeSpan timeout = client.Timeout; + + // Create the CTS before the initial SendAsync so that the SendAsync counts against the timeout. + CancellationTokenSource? linkedCTS = null; + if (timeout != Timeout.InfiniteTimeSpan) + { + linkedCTS = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + linkedCTS.CancelAfter(timeout); + } + + // We call SendAsync outside of the async Core method to propagate exception even without awaiting the returned task. + Task responseTask; + try + { + // Intentionally using cancellationToken instead of the linked one here as HttpClient will enforce the Timeout on its own for this part + responseTask = getMethod(client, requestUri, cancellationToken); + } + catch + { + linkedCTS?.Dispose(); + throw; + } + try { - using HttpResponseMessage response = await EnsureHttpResponseAsync(client, responseTask) - .ConfigureAwait(false); + using HttpResponseMessage response = await responseTask.ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + + Debug.Assert(client.MaxResponseContentBufferSize is > 0 and <= int.MaxValue); + int contentLengthLimit = (int)client.MaxResponseContentBufferSize; + + if (response.Content.Headers.ContentLength is long contentLength && contentLength > contentLengthLimit) + { + LengthLimitReadStream.ThrowExceededBufferLimit(contentLengthLimit); + } await foreach (TValue? value in response.Content.ReadFromJsonAsAsyncEnumerable( jsonTypeInfo, cancellationToken)) @@ -183,63 +211,5 @@ public static partial class HttpClientJsonExtensions } } } - - private static CancellationTokenSource? CreateLinkedCTSFromClientTimeout( - HttpClient client, - CancellationToken cancellationToken) - { - TimeSpan timeout = client.Timeout; - - // Create the CTS before the initial SendAsync so that the SendAsync counts against the timeout. - CancellationTokenSource? linkedCTS = null; - if (timeout != Timeout.InfiniteTimeSpan) - { - linkedCTS = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - linkedCTS.CancelAfter(timeout); - } - - return linkedCTS; - } - - private static Task GetHttpResponseMessageTask( - Func> getMethod, - HttpClient client, - Uri? requestUri, - CancellationTokenSource? linkedCTS, - CancellationToken cancellationToken) - { - // We call SendAsync outside of the async Core method to propagate exception even without awaiting the returned task. - Task responseTask; - try - { - // Intentionally using cancellationToken instead of the linked one here as HttpClient will enforce the Timeout on its own for this part - responseTask = getMethod(client, requestUri, cancellationToken); - } - catch - { - linkedCTS?.Dispose(); - throw; - } - - return responseTask; - } - - private static async Task EnsureHttpResponseAsync( - HttpClient client, - Task responseTask) - { - HttpResponseMessage response = await responseTask.ConfigureAwait(false); - response.EnsureSuccessStatusCode(); - - Debug.Assert(client.MaxResponseContentBufferSize is > 0 and <= int.MaxValue); - int contentLengthLimit = (int)client.MaxResponseContentBufferSize; - - if (response.Content.Headers.ContentLength is long contentLength && contentLength > contentLengthLimit) - { - LengthLimitReadStream.ThrowExceededBufferLimit(contentLengthLimit); - } - - return response; - } } } From 7c76121250b8cdf8db533051f35be16da10ee634 Mon Sep 17 00:00:00 2001 From: David Pine Date: Fri, 21 Jul 2023 13:19:32 -0500 Subject: [PATCH 08/14] Add test to ensure deferred execution semantics, updates from Miha. --- ...lientJsonExtensions.Get.AsyncEnumerable.cs | 68 ++++++------------- ...tpContentJsonExtensions.AsyncEnumerable.cs | 31 ++++----- .../Net/Http/Json/LengthLimitReadStream.cs | 2 + .../HttpClientJsonExtensionsTests.cs | 32 ++++++++- 4 files changed, 67 insertions(+), 66 deletions(-) diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs index f873d175694b36..c5e6bd6a04175d 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs @@ -53,7 +53,7 @@ public static partial class HttpClientJsonExtensions Uri? requestUri, JsonSerializerOptions? options, CancellationToken cancellationToken = default) => - FromJsonStreamAsyncCore(s_getAsync, client, requestUri, options, cancellationToken); + FromJsonStreamAsyncCore(client, requestUri, options, cancellationToken); /// /// Sends an HTTP GETrequest to the specified and returns the value that results @@ -89,7 +89,7 @@ public static partial class HttpClientJsonExtensions Uri? requestUri, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) => - FromJsonStreamAsyncCore(s_getAsync, client, requestUri, jsonTypeInfo, cancellationToken); + FromJsonStreamAsyncCore(client, requestUri, jsonTypeInfo, cancellationToken); /// /// Sends an HTTP GETrequest to the specified and returns the value that results @@ -130,7 +130,6 @@ public static partial class HttpClientJsonExtensions [RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)] [RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)] private static IAsyncEnumerable FromJsonStreamAsyncCore( - Func> getMethod, HttpClient client, Uri? requestUri, JsonSerializerOptions? options, @@ -139,11 +138,10 @@ public static partial class HttpClientJsonExtensions options ??= JsonSerializerOptions.Default; var jsonTypeInfo = (JsonTypeInfo)options.GetTypeInfo(typeof(TValue)); - return FromJsonStreamAsyncCore(getMethod, client, requestUri, jsonTypeInfo, cancellationToken); + return FromJsonStreamAsyncCore(client, requestUri, jsonTypeInfo, cancellationToken); } private static IAsyncEnumerable FromJsonStreamAsyncCore( - Func> getMethod, HttpClient client, Uri? requestUri, JsonTypeInfo jsonTypeInfo, @@ -154,60 +152,38 @@ public static partial class HttpClientJsonExtensions throw new ArgumentNullException(nameof(client)); } - return Core(getMethod, client, requestUri, jsonTypeInfo, cancellationToken); + return Core(client, requestUri, jsonTypeInfo, cancellationToken); static async IAsyncEnumerable Core( - Func> getMethod, HttpClient client, Uri? requestUri, JsonTypeInfo jsonTypeInfo, [EnumeratorCancellation] CancellationToken cancellationToken) { - TimeSpan timeout = client.Timeout; + using HttpResponseMessage response = await s_getAsync(client, requestUri, cancellationToken) + .ConfigureAwait(false); - // Create the CTS before the initial SendAsync so that the SendAsync counts against the timeout. - CancellationTokenSource? linkedCTS = null; - if (timeout != Timeout.InfiniteTimeSpan) - { - linkedCTS = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - linkedCTS.CancelAfter(timeout); - } + response.EnsureSuccessStatusCode(); - // We call SendAsync outside of the async Core method to propagate exception even without awaiting the returned task. - Task responseTask; - try - { - // Intentionally using cancellationToken instead of the linked one here as HttpClient will enforce the Timeout on its own for this part - responseTask = getMethod(client, requestUri, cancellationToken); - } - catch - { - linkedCTS?.Dispose(); - throw; - } + Debug.Assert(client.MaxResponseContentBufferSize is > 0 and <= int.MaxValue); + int contentLengthLimit = (int)client.MaxResponseContentBufferSize; - try + if (response.Content.Headers.ContentLength is long contentLength && contentLength > contentLengthLimit) { - using HttpResponseMessage response = await responseTask.ConfigureAwait(false); - response.EnsureSuccessStatusCode(); - - Debug.Assert(client.MaxResponseContentBufferSize is > 0 and <= int.MaxValue); - int contentLengthLimit = (int)client.MaxResponseContentBufferSize; - - if (response.Content.Headers.ContentLength is long contentLength && contentLength > contentLengthLimit) - { - LengthLimitReadStream.ThrowExceededBufferLimit(contentLengthLimit); - } - - await foreach (TValue? value in response.Content.ReadFromJsonAsAsyncEnumerable( - jsonTypeInfo, cancellationToken)) - { - yield return value; - } + LengthLimitReadStream.ThrowExceededBufferLimit(contentLengthLimit); } - finally + + using Stream contentStream = await HttpContentJsonExtensions.GetContentStreamAsync( + response.Content, cancellationToken).ConfigureAwait(false); + + using LengthLimitReadStream readStream = new(contentStream, (int)client.MaxResponseContentBufferSize); + + await foreach (TValue? value in JsonSerializer.DeserializeAsyncEnumerable( + readStream, jsonTypeInfo, cancellationToken).ConfigureAwait(false)) { - linkedCTS?.Dispose(); + yield return value; + + readStream.ResetCount(); } } } diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.AsyncEnumerable.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.AsyncEnumerable.cs index 1bd294ad2e2dca..9ac34bc695a20f 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.AsyncEnumerable.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.AsyncEnumerable.cs @@ -62,21 +62,15 @@ public static partial class HttpContentJsonExtensions [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)] [RequiresDynamicCode(SerializationDynamicCodeMessage)] - private static async IAsyncEnumerable ReadFromJsonAsAsyncEnumerableCore( + private static IAsyncEnumerable ReadFromJsonAsAsyncEnumerableCore( HttpContent content, JsonSerializerOptions? options, - [EnumeratorCancellation] CancellationToken cancellationToken) + CancellationToken cancellationToken) { - using (Stream contentStream = await GetContentStreamAsync(content, cancellationToken) - .ConfigureAwait(false)) - { - await foreach (TValue? value in JsonSerializer.DeserializeAsyncEnumerable( - contentStream, options ?? JsonHelpers.s_defaultSerializerOptions, cancellationToken) - .ConfigureAwait(false)) - { - yield return value; - } - } + options ??= JsonSerializerOptions.Default; + var jsonTypeInfo = (JsonTypeInfo)options.GetTypeInfo(typeof(TValue)); + + return ReadFromJsonAsAsyncEnumerableCore(content, jsonTypeInfo, cancellationToken); } public static IAsyncEnumerable ReadFromJsonAsAsyncEnumerable( @@ -97,15 +91,14 @@ public static partial class HttpContentJsonExtensions JsonTypeInfo jsonTypeInfo, [EnumeratorCancellation] CancellationToken cancellationToken) { - using (Stream contentStream = await GetContentStreamAsync(content, cancellationToken) + using Stream contentStream = await GetContentStreamAsync(content, cancellationToken) + .ConfigureAwait(false); + + await foreach (TValue? value in JsonSerializer.DeserializeAsyncEnumerable( + contentStream, jsonTypeInfo, cancellationToken) .ConfigureAwait(false)) { - await foreach (TValue? value in JsonSerializer.DeserializeAsyncEnumerable( - contentStream, jsonTypeInfo, cancellationToken) - .ConfigureAwait(false)) - { - yield return value; - } + yield return value; } } } diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/LengthLimitReadStream.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/LengthLimitReadStream.cs index 9837587958a927..aed46ff7ef96f8 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/LengthLimitReadStream.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/LengthLimitReadStream.cs @@ -33,6 +33,8 @@ internal static void ThrowExceededBufferLimit(int limit) throw new HttpRequestException(SR.Format(SR.net_http_content_buffersize_exceeded, limit)); } + internal void ResetCount() => _remainingLength = _lengthLimit; + public override bool CanRead => _innerStream.CanRead; public override bool CanSeek => _innerStream.CanSeek; public override bool CanWrite => false; diff --git a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs index d1e9fd19ac025a..2da86739f2a3ad 100644 --- a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs +++ b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs @@ -404,7 +404,8 @@ static void AssertPeopleEquality(List actualPeople) AssertPeopleEquality(people); } }, - async server => { + async server => + { List headers = new List { new HttpHeaderData("Content-Type", "application/json") }; string json = People.Serialize(); @@ -546,5 +547,34 @@ await server.AcceptConnectionAsync(async connection => })); }); } + + [Fact] + public async Task GetFromJsonAsAsyncEnumerable_CorrectlyExposesDeferredExecutionSemantics() + { + await LoopbackServer.CreateClientAndServerAsync(async uri => + { + using var client = new HttpClient { Timeout = TimeSpan.FromMilliseconds(100) }; + + // Get the async enumerable, but don't iterate it - this is used to validate deferred execution. + var getAsyncEnumerable = client.GetFromJsonAsAsyncEnumerable(uri); + + // Wait longer than the timeout. + await Task.Delay(TimeSpan.FromMilliseconds(150)); + + // No exception should be thrown while iterating, timeout doesn't start until iteration begins. + await foreach (string? str in getAsyncEnumerable) + { + _ = str; + } + }, + async server => + { + List headers = new List { new HttpHeaderData("Content-Type", "application/json") }; + string[] values = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" }; + string json = JsonSerializer.Serialize(values); + + await server.HandleRequestAsync(content: json, headers: headers); + }); + } } } From f790d55ee177e02dc8a5f3736fa2f1b24406d3cc Mon Sep 17 00:00:00 2001 From: David Pine Date: Fri, 21 Jul 2023 13:58:38 -0500 Subject: [PATCH 09/14] Apply suggestions from code review Co-authored-by: Miha Zupan --- .../Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs index c5e6bd6a04175d..2a1369aa14e4d3 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs @@ -166,12 +166,6 @@ public static partial class HttpClientJsonExtensions response.EnsureSuccessStatusCode(); Debug.Assert(client.MaxResponseContentBufferSize is > 0 and <= int.MaxValue); - int contentLengthLimit = (int)client.MaxResponseContentBufferSize; - - if (response.Content.Headers.ContentLength is long contentLength && contentLength > contentLengthLimit) - { - LengthLimitReadStream.ThrowExceededBufferLimit(contentLengthLimit); - } using Stream contentStream = await HttpContentJsonExtensions.GetContentStreamAsync( response.Content, cancellationToken).ConfigureAwait(false); From b73f3ce5b5650f6c68a04d3d0b43ee966e305eb0 Mon Sep 17 00:00:00 2001 From: David Pine Date: Mon, 24 Jul 2023 07:56:33 -0500 Subject: [PATCH 10/14] Update src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs Co-authored-by: Eirik Tsarpalis --- .../Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs index 2a1369aa14e4d3..ff7796ee2f98a3 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs @@ -136,6 +136,7 @@ public static partial class HttpClientJsonExtensions CancellationToken cancellationToken) { options ??= JsonSerializerOptions.Default; + options.MakeReadOnly(); var jsonTypeInfo = (JsonTypeInfo)options.GetTypeInfo(typeof(TValue)); return FromJsonStreamAsyncCore(client, requestUri, jsonTypeInfo, cancellationToken); From deb2409a668d812af9c194149813ef777d351ed7 Mon Sep 17 00:00:00 2001 From: David Pine Date: Mon, 24 Jul 2023 07:57:42 -0500 Subject: [PATCH 11/14] Update test per Miha's feedback --- .../HttpClientJsonExtensionsTests.cs | 69 +++++++++++-------- 1 file changed, 42 insertions(+), 27 deletions(-) diff --git a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs index 2da86739f2a3ad..328f28f1bbf1e3 100644 --- a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs +++ b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs @@ -1,9 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using System.Net.Http; using System.Net.Test.Common; using System.Text.Json; using System.Threading; @@ -116,7 +118,8 @@ await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync( Assert.True(response8.StatusCode == HttpStatusCode.OK); } }, - async server => { + async server => + { HttpRequestData request = await server.HandleRequestAsync(); ValidateRequest(request, "POST"); Person per = JsonSerializer.Deserialize(request.Body, JsonOptions.DefaultSerializerOptions); @@ -160,7 +163,8 @@ await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync( Assert.True(response8.StatusCode == HttpStatusCode.OK); } }, - async server => { + async server => + { HttpRequestData request = await server.HandleRequestAsync(); ValidateRequest(request, "PUT"); @@ -211,7 +215,8 @@ await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync( Assert.True(response8.StatusCode == HttpStatusCode.OK); } }, - async server => { + async server => + { HttpRequestData request = await server.HandleRequestAsync(); ValidateRequest(request, "PATCH"); byte[] json = request.Body; @@ -261,7 +266,8 @@ await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync( Assert.IsType(response).Validate(); } }, - async server => { + async server => + { HttpRequestData request = await server.HandleRequestAsync(); Assert.Equal("DELETE", request.Method); @@ -358,7 +364,8 @@ await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync( per = await client.GetFromJsonAsync((Uri)null); } }, - async server => { + async server => + { List headers = new List { new HttpHeaderData("Content-Type", "application/json") }; string json = Person.Create().Serialize(); @@ -502,7 +509,7 @@ await server.AcceptConnectionAsync(async connection => [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] // No Socket support [InlineData(true)] - [InlineData( false)] + [InlineData(false)] public async Task GetFromJsonAsAsyncEnumerable_EnforcesTimeout(bool slowHeaders) { TaskCompletionSource exceptionThrown = new(TaskCreationOptions.RunContinuationsAsynchronously); @@ -549,32 +556,40 @@ await server.AcceptConnectionAsync(async connection => } [Fact] - public async Task GetFromJsonAsAsyncEnumerable_CorrectlyExposesDeferredExecutionSemantics() + public async Task GetFromJsonAsAsyncEnumerable_EnforcesTimeoutOnInitialRequest() { - await LoopbackServer.CreateClientAndServerAsync(async uri => - { - using var client = new HttpClient { Timeout = TimeSpan.FromMilliseconds(100) }; - - // Get the async enumerable, but don't iterate it - this is used to validate deferred execution. - var getAsyncEnumerable = client.GetFromJsonAsAsyncEnumerable(uri); - - // Wait longer than the timeout. - await Task.Delay(TimeSpan.FromMilliseconds(150)); - - // No exception should be thrown while iterating, timeout doesn't start until iteration begins. - await foreach (string? str in getAsyncEnumerable) - { - _ = str; - } - }, - async server => + // Using CustomResponseHandler here to effectively skip the Timeout for the initial request. + using var client = new HttpClient(new CustomResponseHandler((r, c) => { - List headers = new List { new HttpHeaderData("Content-Type", "application/json") }; string[] values = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" }; string json = JsonSerializer.Serialize(values); + HttpResponseMessage response = new() + { + Content = new StringContent(json) + }; - await server.HandleRequestAsync(content: json, headers: headers); - }); + return Task.FromResult(response); + })) + { + Timeout = TimeSpan.FromMilliseconds(1) + }; + + await foreach (string s in client.GetFromJsonAsAsyncEnumerable("http://dummyUrl")) + { + // Wait longer than the timeout. + await Task.Delay(TimeSpan.FromMilliseconds(10)); + } } } } + +file sealed class CustomResponseHandler : HttpMessageHandler +{ + private readonly Func> _func; + + public CustomResponseHandler( + Func> func) => _func = func; + + protected override Task SendAsync( + HttpRequestMessage request, CancellationToken cancellationToken) => _func(request, cancellationToken); +} From ad03a323293672fd9581f06c49ec9cb0cdbb1c21 Mon Sep 17 00:00:00 2001 From: David Pine Date: Mon, 24 Jul 2023 08:01:34 -0500 Subject: [PATCH 12/14] A few more updates from peer review, Eirik's nits. --- .../Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs | 7 ++++--- .../Http/Json/HttpContentJsonExtensions.AsyncEnumerable.cs | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs index ff7796ee2f98a3..5472332e175741 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs @@ -137,6 +137,7 @@ public static partial class HttpClientJsonExtensions { options ??= JsonSerializerOptions.Default; options.MakeReadOnly(); + var jsonTypeInfo = (JsonTypeInfo)options.GetTypeInfo(typeof(TValue)); return FromJsonStreamAsyncCore(client, requestUri, jsonTypeInfo, cancellationToken); @@ -161,13 +162,13 @@ public static partial class HttpClientJsonExtensions JsonTypeInfo jsonTypeInfo, [EnumeratorCancellation] CancellationToken cancellationToken) { - using HttpResponseMessage response = await s_getAsync(client, requestUri, cancellationToken) + Debug.Assert(client.MaxResponseContentBufferSize is > 0 and <= int.MaxValue); + + using HttpResponseMessage response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken) .ConfigureAwait(false); response.EnsureSuccessStatusCode(); - Debug.Assert(client.MaxResponseContentBufferSize is > 0 and <= int.MaxValue); - using Stream contentStream = await HttpContentJsonExtensions.GetContentStreamAsync( response.Content, cancellationToken).ConfigureAwait(false); diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.AsyncEnumerable.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.AsyncEnumerable.cs index 9ac34bc695a20f..c42f51db4f7140 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.AsyncEnumerable.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.AsyncEnumerable.cs @@ -68,6 +68,8 @@ public static partial class HttpContentJsonExtensions CancellationToken cancellationToken) { options ??= JsonSerializerOptions.Default; + options.MakeReadOnly(); + var jsonTypeInfo = (JsonTypeInfo)options.GetTypeInfo(typeof(TValue)); return ReadFromJsonAsAsyncEnumerableCore(content, jsonTypeInfo, cancellationToken); From c99e708c9b7de1231a114af1bfa099ef5848c6fa Mon Sep 17 00:00:00 2001 From: David Pine Date: Mon, 24 Jul 2023 08:20:08 -0500 Subject: [PATCH 13/14] No need for the length limit read stream. --- .../Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs | 6 +----- .../src/System/Net/Http/Json/LengthLimitReadStream.cs | 2 -- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs index 5472332e175741..36cad11275e602 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs @@ -172,14 +172,10 @@ public static partial class HttpClientJsonExtensions using Stream contentStream = await HttpContentJsonExtensions.GetContentStreamAsync( response.Content, cancellationToken).ConfigureAwait(false); - using LengthLimitReadStream readStream = new(contentStream, (int)client.MaxResponseContentBufferSize); - await foreach (TValue? value in JsonSerializer.DeserializeAsyncEnumerable( - readStream, jsonTypeInfo, cancellationToken).ConfigureAwait(false)) + contentStream, jsonTypeInfo, cancellationToken).ConfigureAwait(false)) { yield return value; - - readStream.ResetCount(); } } } diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/LengthLimitReadStream.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/LengthLimitReadStream.cs index aed46ff7ef96f8..9837587958a927 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/LengthLimitReadStream.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/LengthLimitReadStream.cs @@ -33,8 +33,6 @@ internal static void ThrowExceededBufferLimit(int limit) throw new HttpRequestException(SR.Format(SR.net_http_content_buffersize_exceeded, limit)); } - internal void ResetCount() => _remainingLength = _lengthLimit; - public override bool CanRead => _innerStream.CanRead; public override bool CanSeek => _innerStream.CanSeek; public override bool CanWrite => false; From a1528ff95b12f6de161c71dfcae92c6fbea46d24 Mon Sep 17 00:00:00 2001 From: David Pine Date: Mon, 24 Jul 2023 09:16:30 -0500 Subject: [PATCH 14/14] Add limit per invocation, not element. Share length limit read stream logic. --- ...lientJsonExtensions.Get.AsyncEnumerable.cs | 9 ++-- .../Net/Http/Json/HttpClientJsonExtensions.cs | 42 ++++++++++++------- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs index 36cad11275e602..057bf872692c3c 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Get.AsyncEnumerable.cs @@ -162,18 +162,15 @@ public static partial class HttpClientJsonExtensions JsonTypeInfo jsonTypeInfo, [EnumeratorCancellation] CancellationToken cancellationToken) { - Debug.Assert(client.MaxResponseContentBufferSize is > 0 and <= int.MaxValue); - using HttpResponseMessage response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken) .ConfigureAwait(false); - response.EnsureSuccessStatusCode(); - using Stream contentStream = await HttpContentJsonExtensions.GetContentStreamAsync( - response.Content, cancellationToken).ConfigureAwait(false); + using Stream readStream = await GetHttpResponseStreamAsync(client, response, false, cancellationToken) + .ConfigureAwait(false); await foreach (TValue? value in JsonSerializer.DeserializeAsyncEnumerable( - contentStream, jsonTypeInfo, cancellationToken).ConfigureAwait(false)) + readStream, jsonTypeInfo, cancellationToken).ConfigureAwait(false)) { yield return value; } diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.cs index 234f8c3311f8e6..4aa114c2ffb28a 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.cs @@ -84,22 +84,10 @@ public static partial class HttpClientJsonExtensions using HttpResponseMessage response = await responseTask.ConfigureAwait(false); response.EnsureSuccessStatusCode(); - Debug.Assert(client.MaxResponseContentBufferSize is > 0 and <= int.MaxValue); - int contentLengthLimit = (int)client.MaxResponseContentBufferSize; - - if (response.Content.Headers.ContentLength is long contentLength && contentLength > contentLengthLimit) - { - LengthLimitReadStream.ThrowExceededBufferLimit(contentLengthLimit); - } - try { - using Stream contentStream = await HttpContentJsonExtensions.GetContentStreamAsync(response.Content, linkedCTS?.Token ?? cancellationToken).ConfigureAwait(false); - - // If ResponseHeadersRead wasn't used, HttpClient will have already buffered the whole response upfront. No need to check the limit again. - Stream readStream = usingResponseHeadersRead - ? new LengthLimitReadStream(contentStream, (int)client.MaxResponseContentBufferSize) - : contentStream; + using Stream readStream = await GetHttpResponseStreamAsync(client, response, usingResponseHeadersRead, cancellationToken) + .ConfigureAwait(false); return await deserializeMethod(readStream, jsonOptions, linkedCTS?.Token ?? cancellationToken).ConfigureAwait(false); } @@ -123,5 +111,31 @@ public static partial class HttpClientJsonExtensions private static Uri? CreateUri(string? uri) => string.IsNullOrEmpty(uri) ? null : new Uri(uri, UriKind.RelativeOrAbsolute); + + private static async Task GetHttpResponseStreamAsync( + HttpClient client, + HttpResponseMessage response, + bool usingResponseHeadersRead, + CancellationToken cancellationToken) + { + Debug.Assert(client.MaxResponseContentBufferSize is > 0 and <= int.MaxValue); + int contentLengthLimit = (int)client.MaxResponseContentBufferSize; + + if (response.Content.Headers.ContentLength is long contentLength && contentLength > contentLengthLimit) + { + LengthLimitReadStream.ThrowExceededBufferLimit(contentLengthLimit); + } + + Stream contentStream = await HttpContentJsonExtensions.GetContentStreamAsync(response.Content, cancellationToken) + .ConfigureAwait(false); + + // If ResponseHeadersRead wasn't used, HttpClient will have already buffered the whole response upfront. + // No need to check the limit again. + Stream readStream = usingResponseHeadersRead + ? new LengthLimitReadStream(contentStream, (int)client.MaxResponseContentBufferSize) + : contentStream; + + return readStream; + } } }