From f4ee6c150844ed73a0aa99e46f358664c8cb42d4 Mon Sep 17 00:00:00 2001
From: "dotnet-maestro[bot]"
<42748379+dotnet-maestro[bot]@users.noreply.github.com>
Date: Wed, 18 Sep 2024 20:06:12 +0000
Subject: [PATCH 1/6] Update dependencies from https://github.com/dotnet/arcade
build 20240916.2 (#57931)
[release/9.0-rc2] Update dependencies from dotnet/arcade
---
eng/Version.Details.xml | 24 ++++++++++++------------
eng/Versions.props | 8 ++++----
global.json | 4 ++--
3 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index cd58caf6e98f..22516dfbaa82 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -388,31 +388,31 @@
https://github.com/dotnet/winforms
b1fd89453ed5e3ad91e4f18c9386cac8dade6e36
-
+
https://github.com/dotnet/arcade
- bcba6939456aa552554eae9ea9538a039cc98d96
+ 04b9022eba9c184a8036328af513c22e6949e8b6
-
+
https://github.com/dotnet/arcade
- bcba6939456aa552554eae9ea9538a039cc98d96
+ 04b9022eba9c184a8036328af513c22e6949e8b6
-
+
https://github.com/dotnet/arcade
- bcba6939456aa552554eae9ea9538a039cc98d96
+ 04b9022eba9c184a8036328af513c22e6949e8b6
-
+
https://github.com/dotnet/arcade
- bcba6939456aa552554eae9ea9538a039cc98d96
+ 04b9022eba9c184a8036328af513c22e6949e8b6
-
+
https://github.com/dotnet/arcade
- bcba6939456aa552554eae9ea9538a039cc98d96
+ 04b9022eba9c184a8036328af513c22e6949e8b6
-
+
https://github.com/dotnet/arcade
- bcba6939456aa552554eae9ea9538a039cc98d96
+ 04b9022eba9c184a8036328af513c22e6949e8b6
https://github.com/dotnet/extensions
diff --git a/eng/Versions.props b/eng/Versions.props
index da0888cc291e..bf582fec104f 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -165,10 +165,10 @@
6.2.4
6.2.4
- 9.0.0-beta.24463.2
- 9.0.0-beta.24463.2
- 9.0.0-beta.24463.2
- 9.0.0-beta.24463.2
+ 9.0.0-beta.24466.2
+ 9.0.0-beta.24466.2
+ 9.0.0-beta.24466.2
+ 9.0.0-beta.24466.2
9.0.0-alpha.1.24452.1
diff --git a/global.json b/global.json
index 44c47e0578f5..d4c978d226d2 100644
--- a/global.json
+++ b/global.json
@@ -27,7 +27,7 @@
"jdk": "11"
},
"msbuild-sdks": {
- "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.24463.2",
- "Microsoft.DotNet.Helix.Sdk": "9.0.0-beta.24463.2"
+ "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.24466.2",
+ "Microsoft.DotNet.Helix.Sdk": "9.0.0-beta.24466.2"
}
}
From 74bdb8bd375944d951db0ecdc2e49e0a1cfc091e Mon Sep 17 00:00:00 2001
From: William Godbe
Date: Thu, 19 Sep 2024 15:18:10 -0700
Subject: [PATCH 2/6] Add registry search for upgrade policy keys (#57952)
* Add registry search for upgrade policy keys
* SharedFx bundle too
* Add util extension
* Another fix
---
src/Installers/Windows/SharedFrameworkBundle/Bundle.wxs | 7 ++++++-
.../SharedFrameworkBundle/SharedFrameworkBundle.wixproj | 5 +++++
src/Installers/Windows/WindowsHostingBundle/Bundle.wxs | 5 +++++
.../WindowsHostingBundle/WindowsHostingBundle.wixproj | 1 +
src/Installers/Windows/Wix.targets | 2 +-
5 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/src/Installers/Windows/SharedFrameworkBundle/Bundle.wxs b/src/Installers/Windows/SharedFrameworkBundle/Bundle.wxs
index f0508bc3b611..52e85b9a96ce 100644
--- a/src/Installers/Windows/SharedFrameworkBundle/Bundle.wxs
+++ b/src/Installers/Windows/SharedFrameworkBundle/Bundle.wxs
@@ -1,5 +1,6 @@
-
+
+
+
+
+
diff --git a/src/Installers/Windows/SharedFrameworkBundle/SharedFrameworkBundle.wixproj b/src/Installers/Windows/SharedFrameworkBundle/SharedFrameworkBundle.wixproj
index e7c93b5ecba8..5f47072f1fee 100644
--- a/src/Installers/Windows/SharedFrameworkBundle/SharedFrameworkBundle.wixproj
+++ b/src/Installers/Windows/SharedFrameworkBundle/SharedFrameworkBundle.wixproj
@@ -13,6 +13,10 @@
+
+ $(WixExtDir)\WixUtilExtension.dll
+ WixUtilExtension
+
$(WixExtDir)\WixDependencyExtension.dll
WixDependencyExtension
@@ -25,6 +29,7 @@
+
diff --git a/src/Installers/Windows/WindowsHostingBundle/Bundle.wxs b/src/Installers/Windows/WindowsHostingBundle/Bundle.wxs
index 429c66c241a3..6131a49404f4 100644
--- a/src/Installers/Windows/WindowsHostingBundle/Bundle.wxs
+++ b/src/Installers/Windows/WindowsHostingBundle/Bundle.wxs
@@ -13,6 +13,11 @@
+
+
+
+
+
diff --git a/src/Installers/Windows/WindowsHostingBundle/WindowsHostingBundle.wixproj b/src/Installers/Windows/WindowsHostingBundle/WindowsHostingBundle.wixproj
index cfb32de2dc5d..2e5c010ed0e7 100644
--- a/src/Installers/Windows/WindowsHostingBundle/WindowsHostingBundle.wixproj
+++ b/src/Installers/Windows/WindowsHostingBundle/WindowsHostingBundle.wixproj
@@ -36,6 +36,7 @@
+
diff --git a/src/Installers/Windows/Wix.targets b/src/Installers/Windows/Wix.targets
index 4fa41e244bd9..7b0a4577492b 100644
--- a/src/Installers/Windows/Wix.targets
+++ b/src/Installers/Windows/Wix.targets
@@ -110,7 +110,7 @@
NoLogo="true"
Cultures="en-us"
InstallerFile="%(WixInstallerFilesToProcess.Identity)"
- AdditionalBasePaths="$(MSBuildProjectDirectory)"
+ AdditionalBasePaths="$(MSBuildProjectDirectory);$(PkgMicrosoft_DotNet_Build_Tasks_Installers)\build\wix\bundle"
WixExtensions="@(WixExtension)"
Loc="@(EmbeddedResource)"
Sice="$(SuppressIces)"
From a717e7fd80e639f4603c126a8e772e541d58a43e Mon Sep 17 00:00:00 2001
From: Andrew Casey
Date: Fri, 20 Sep 2024 11:59:15 -0700
Subject: [PATCH 3/6] Check for sentinel value when setting HTTP/3 error code
(#57976)
If no error code has been set, `IProtocolErrorFeature.Error` will be `-1`. If we pass that through verbatim, it will be caught by validation in the setter (ironically, of the same property on the same feature object), resulting in an exception and a Critical (but apparently benign) log message.
Fixes #57933
---
.../src/Internal/Http3/Http3Connection.cs | 11 ++-
.../shared/test/Http3/Http3InMemory.cs | 2 +-
.../Http3/Http3ConnectionTests.cs | 78 +++++++++++++++++++
3 files changed, 87 insertions(+), 4 deletions(-)
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs
index e0f300839104..92777ea1d79c 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs
@@ -101,6 +101,9 @@ private void UpdateHighestOpenedRequestStreamId(long streamId)
public string ConnectionId => _context.ConnectionId;
public ITimeoutControl TimeoutControl => _context.TimeoutControl;
+ // The default error value is -1. If it hasn't been changed before abort is called then default to HTTP/3's NoError value.
+ private Http3ErrorCode Http3ErrorCodeOrNoError => _errorCodeFeature.Error == -1 ? Http3ErrorCode.NoError : (Http3ErrorCode)_errorCodeFeature.Error;
+
public void StopProcessingNextRequest(ConnectionEndReason reason)
=> StopProcessingNextRequest(serverInitiated: true, reason);
@@ -505,12 +508,14 @@ public async Task ProcessRequestsAsync(IHttpApplication appl
}
}
+ var errorCode = Http3ErrorCodeOrNoError;
+
// Abort active request streams.
lock (_streams)
{
foreach (var stream in _streams.Values)
{
- stream.Abort(CreateConnectionAbortError(error, clientAbort), (Http3ErrorCode)_errorCodeFeature.Error);
+ stream.Abort(CreateConnectionAbortError(error, clientAbort), errorCode);
}
}
@@ -546,7 +551,7 @@ public async Task ProcessRequestsAsync(IHttpApplication appl
}
// Complete
- Abort(CreateConnectionAbortError(error, clientAbort), (Http3ErrorCode)_errorCodeFeature.Error, reason);
+ Abort(CreateConnectionAbortError(error, clientAbort), errorCode, reason);
// Wait for active requests to complete.
while (_activeRequestCount > 0)
@@ -905,7 +910,7 @@ public void OnInputOrOutputCompleted()
TryStopAcceptingStreams();
// Abort the connection using the error code the client used. For a graceful close, this should be H3_NO_ERROR.
- Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient), (Http3ErrorCode)_errorCodeFeature.Error, ConnectionEndReason.TransportCompleted);
+ Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient), Http3ErrorCodeOrNoError, ConnectionEndReason.TransportCompleted);
}
internal WebTransportSession OpenNewWebTransportSession(Http3Stream http3Stream)
diff --git a/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs b/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs
index 730e80862de8..64ebcdb07b41 100644
--- a/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs
+++ b/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs
@@ -225,7 +225,7 @@ public void TriggerTick(TimeSpan timeSpan = default)
public async Task InitializeConnectionAsync(RequestDelegate application)
{
- MultiplexedConnectionContext = new TestMultiplexedConnectionContext(this)
+ MultiplexedConnectionContext ??= new TestMultiplexedConnectionContext(this)
{
ConnectionId = "TEST"
};
diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs
index 4471e7db42f3..06d96adc238f 100644
--- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs
+++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs
@@ -701,6 +701,84 @@ await Http3Api.InitializeConnectionAsync(async c =>
await tcs.Task;
}
+ [Fact]
+ public async Task ErrorCodeIsValidOnConnectionTimeout()
+ {
+ // This test loosely repros the scenario in https://github.com/dotnet/aspnetcore/issues/57933.
+ // In particular, there's a request from the server and, once a response has been sent,
+ // the (simulated) transport throws a QuicException that surfaces through AcceptAsync.
+ // This test confirms that Http3Connection.ProcessRequestsAsync doesn't (indirectly) cause
+ // IProtocolErrorCodeFeature.Error to be set to (or left at) -1, which System.Net.Quic will
+ // not accept.
+
+ // Used to signal that a request has been sent and a response has been received
+ var requestTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ // Used to signal that the connection context has been aborted
+ var abortTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ // InitializeConnectionAsync consumes the connection context, so set it first
+ Http3Api.MultiplexedConnectionContext = new ThrowingMultiplexedConnectionContext(Http3Api, skipCount: 2, requestTcs, abortTcs);
+ await Http3Api.InitializeConnectionAsync(_echoApplication);
+
+ await Http3Api.CreateControlStream();
+ await Http3Api.GetInboundControlStream();
+ var requestStream = await Http3Api.CreateRequestStream(Headers, endStream: true);
+ var responseHeaders = await requestStream.ExpectHeadersAsync();
+
+ await requestStream.ExpectReceiveEndOfStream();
+ await requestStream.OnDisposedTask.DefaultTimeout();
+
+ requestTcs.SetResult();
+
+ // By the time the connection context is aborted, the error code feature has been updated
+ await abortTcs.Task.DefaultTimeout();
+
+ Http3Api.CloseServerGracefully();
+
+ var errorCodeFeature = Http3Api.MultiplexedConnectionContext.Features.Get();
+ Assert.InRange(errorCodeFeature.Error, 0, (1L << 62) - 1); // Valid range for HTTP/3 error codes
+ }
+
+ private sealed class ThrowingMultiplexedConnectionContext : TestMultiplexedConnectionContext
+ {
+ private int _skipCount;
+ private readonly TaskCompletionSource _requestTcs;
+ private readonly TaskCompletionSource _abortTcs;
+
+ ///
+ /// After calls to , the next call will throw a
+ /// (after waiting for to be set).
+ ///
+ /// lets this type signal that has been called.
+ ///
+ public ThrowingMultiplexedConnectionContext(Http3InMemory testBase, int skipCount, TaskCompletionSource requestTcs, TaskCompletionSource abortTcs)
+ : base(testBase)
+ {
+ _skipCount = skipCount;
+ _requestTcs = requestTcs;
+ _abortTcs = abortTcs;
+ }
+
+ public override async ValueTask AcceptAsync(CancellationToken cancellationToken = default)
+ {
+ if (_skipCount-- <= 0)
+ {
+ await _requestTcs.Task.DefaultTimeout();
+ throw new System.Net.Quic.QuicException(
+ System.Net.Quic.QuicError.ConnectionTimeout,
+ applicationErrorCode: null,
+ "Connection timed out waiting for a response from the peer.");
+ }
+ return await base.AcceptAsync(cancellationToken);
+ }
+
+ public override void Abort(ConnectionAbortedException abortReason)
+ {
+ _abortTcs.SetResult();
+ base.Abort(abortReason);
+ }
+ }
+
private async Task MakeRequestAsync(int index, KeyValuePair[] headers, bool sendData, bool waitForServerDispose)
{
var requestStream = await Http3Api.CreateRequestStream(headers, endStream: !sendData);
From 8371724b2f349b5817558c005e831d0d00ca66ed Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 20 Sep 2024 12:14:26 -0700
Subject: [PATCH 4/6] [release/9.0-rc2] [Blazor] Update `WebAssembly.DevServer`
to serve the `Blazor-Environment` header (#57974)
* Serve 'Blazor-Environment' header
* PR feedback
* Update src/Components/WebAssembly/DevServer/src/Server/Startup.cs
---------
Co-authored-by: Mackinnon Buck
---
.../DevServer/src/Server/Startup.cs | 30 ++++++++++---------
.../Tests/WebAssemblyConfigurationTest.cs | 6 ++++
2 files changed, 22 insertions(+), 14 deletions(-)
diff --git a/src/Components/WebAssembly/DevServer/src/Server/Startup.cs b/src/Components/WebAssembly/DevServer/src/Server/Startup.cs
index 342370171656..046031a29f79 100644
--- a/src/Components/WebAssembly/DevServer/src/Server/Startup.cs
+++ b/src/Components/WebAssembly/DevServer/src/Server/Startup.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@@ -29,27 +30,28 @@ public static void Configure(IApplicationBuilder app, IConfiguration configurati
app.UseWebAssemblyDebugging();
- bool applyCopHeaders = configuration.GetValue("ApplyCopHeaders");
+ var webHostEnvironment = app.ApplicationServices.GetRequiredService();
+ var applyCopHeaders = configuration.GetValue("ApplyCopHeaders");
- if (applyCopHeaders)
+ app.Use(async (ctx, next) =>
{
- app.Use(async (ctx, next) =>
+ if (ctx.Request.Path.StartsWithSegments("/_framework/blazor.boot.json"))
{
- if (ctx.Request.Path.StartsWithSegments("/_framework") && !ctx.Request.Path.StartsWithSegments("/_framework/blazor.server.js") && !ctx.Request.Path.StartsWithSegments("/_framework/blazor.web.js"))
+ ctx.Response.Headers.Append("Blazor-Environment", webHostEnvironment.EnvironmentName);
+ }
+ else if (applyCopHeaders && ctx.Request.Path.StartsWithSegments("/_framework") && !ctx.Request.Path.StartsWithSegments("/_framework/blazor.server.js") && !ctx.Request.Path.StartsWithSegments("/_framework/blazor.web.js"))
+ {
+ var fileExtension = Path.GetExtension(ctx.Request.Path);
+ if (string.Equals(fileExtension, ".js", StringComparison.OrdinalIgnoreCase))
{
- string fileExtension = Path.GetExtension(ctx.Request.Path);
- if (string.Equals(fileExtension, ".js"))
- {
- // Browser multi-threaded runtime requires cross-origin policy headers to enable SharedArrayBuffer.
- ApplyCrossOriginPolicyHeaders(ctx);
- }
+ // Browser multi-threaded runtime requires cross-origin policy headers to enable SharedArrayBuffer.
+ ApplyCrossOriginPolicyHeaders(ctx);
}
+ }
- await next(ctx);
- });
- }
+ await next(ctx);
+ });
- //app.UseBlazorFrameworkFiles();
app.UseRouting();
app.UseStaticFiles(new StaticFileOptions
diff --git a/src/Components/test/E2ETest/Tests/WebAssemblyConfigurationTest.cs b/src/Components/test/E2ETest/Tests/WebAssemblyConfigurationTest.cs
index aee9648a4109..b231545db2aa 100644
--- a/src/Components/test/E2ETest/Tests/WebAssemblyConfigurationTest.cs
+++ b/src/Components/test/E2ETest/Tests/WebAssemblyConfigurationTest.cs
@@ -39,6 +39,9 @@ public void WebAssemblyConfiguration_Works()
if (_serverFixture.TestTrimmedOrMultithreadingApps)
{
+ // Verify that the environment gets detected as 'Production'.
+ Browser.Equal("Production", () => _appElement.FindElement(By.Id("environment")).Text);
+
// Verify values overriden by an environment specific 'appsettings.$(Environment).json are read
Assert.Equal("Prod key2-value", _appElement.FindElement(By.Id("key2")).Text);
@@ -47,6 +50,9 @@ public void WebAssemblyConfiguration_Works()
}
else
{
+ // Verify that the dev server always correctly serves the 'Blazor-Environment: Development' header.
+ Browser.Equal("Development", () => _appElement.FindElement(By.Id("environment")).Text);
+
// Verify values overriden by an environment specific 'appsettings.$(Environment).json are read
Assert.Equal("Development key2-value", _appElement.FindElement(By.Id("key2")).Text);
From 92376ce36468ed605a66f09cfe02d4c436ea3a2b Mon Sep 17 00:00:00 2001
From: Brennan
Date: Fri, 20 Sep 2024 16:42:17 -0700
Subject: [PATCH 5/6] Fix IAsyncEnumerable controller methods to allow setting
headers (#57924)
* Fix IAsyncEnumerable controller methods to allow setting headers
* name
* httpjson extensions too
* revert
---
.../src/HttpResponseJsonExtensions.cs | 102 ++++--------------
.../test/HttpResponseJsonExtensionsTests.cs | 70 +++++++++++-
...ft.AspNetCore.Http.Extensions.Tests.csproj | 1 +
.../SystemTextJsonOutputFormatter.cs | 5 -
.../SystemTextJsonResultExecutor.cs | 6 --
.../SystemTextJsonOutputFormatterTest.cs | 17 +++
...SystemTextJsonOutputFormatterController.cs | 8 ++
7 files changed, 117 insertions(+), 92 deletions(-)
diff --git a/src/Http/Http.Extensions/src/HttpResponseJsonExtensions.cs b/src/Http/Http.Extensions/src/HttpResponseJsonExtensions.cs
index c7d003e6bb0b..84e09c1a3581 100644
--- a/src/Http/Http.Extensions/src/HttpResponseJsonExtensions.cs
+++ b/src/Http/Http.Extensions/src/HttpResponseJsonExtensions.cs
@@ -90,22 +90,12 @@ public static Task WriteAsJsonAsync(
response.ContentType = contentType ?? ContentTypeConstants.JsonContentTypeWithCharset;
- var startTask = Task.CompletedTask;
- if (!response.HasStarted)
- {
- // Flush headers before starting Json serialization. This avoids an extra layer of buffering before the first flush.
- startTask = response.StartAsync(cancellationToken);
- }
-
// if no user provided token, pass the RequestAborted token and ignore OperationCanceledException
- if (!startTask.IsCompleted || !cancellationToken.CanBeCanceled)
+ if (!cancellationToken.CanBeCanceled)
{
- return WriteAsJsonAsyncSlow(startTask, response.BodyWriter, value, options,
- ignoreOCE: !cancellationToken.CanBeCanceled,
- cancellationToken.CanBeCanceled ? cancellationToken : response.HttpContext.RequestAborted);
+ return WriteAsJsonAsyncSlow(response.BodyWriter, value, options, response.HttpContext.RequestAborted);
}
- startTask.GetAwaiter().GetResult();
return JsonSerializer.SerializeAsync(response.BodyWriter, value, options, cancellationToken);
}
@@ -131,33 +121,22 @@ public static Task WriteAsJsonAsync(
response.ContentType = contentType ?? ContentTypeConstants.JsonContentTypeWithCharset;
- var startTask = Task.CompletedTask;
- if (!response.HasStarted)
- {
- // Flush headers before starting Json serialization. This avoids an extra layer of buffering before the first flush.
- startTask = response.StartAsync(cancellationToken);
- }
-
// if no user provided token, pass the RequestAborted token and ignore OperationCanceledException
- if (!startTask.IsCompleted || !cancellationToken.CanBeCanceled)
+ if (!cancellationToken.CanBeCanceled)
{
- return WriteAsJsonAsyncSlow(startTask, response, value, jsonTypeInfo,
- ignoreOCE: !cancellationToken.CanBeCanceled,
- cancellationToken.CanBeCanceled ? cancellationToken : response.HttpContext.RequestAborted);
+ return WriteAsJsonAsyncSlow(response, value, jsonTypeInfo, response.HttpContext.RequestAborted);
}
- startTask.GetAwaiter().GetResult();
return JsonSerializer.SerializeAsync(response.BodyWriter, value, jsonTypeInfo, cancellationToken);
- static async Task WriteAsJsonAsyncSlow(Task startTask, HttpResponse response, TValue value, JsonTypeInfo jsonTypeInfo,
- bool ignoreOCE, CancellationToken cancellationToken)
+ static async Task WriteAsJsonAsyncSlow(HttpResponse response, TValue value, JsonTypeInfo jsonTypeInfo,
+ CancellationToken cancellationToken)
{
try
{
- await startTask;
await JsonSerializer.SerializeAsync(response.BodyWriter, value, jsonTypeInfo, cancellationToken);
}
- catch (OperationCanceledException) when (ignoreOCE) { }
+ catch (OperationCanceledException) { }
}
}
@@ -184,52 +163,38 @@ public static Task WriteAsJsonAsync(
response.ContentType = contentType ?? ContentTypeConstants.JsonContentTypeWithCharset;
- var startTask = Task.CompletedTask;
- if (!response.HasStarted)
- {
- // Flush headers before starting Json serialization. This avoids an extra layer of buffering before the first flush.
- startTask = response.StartAsync(cancellationToken);
- }
-
// if no user provided token, pass the RequestAborted token and ignore OperationCanceledException
- if (!startTask.IsCompleted || !cancellationToken.CanBeCanceled)
+ if (!cancellationToken.CanBeCanceled)
{
- return WriteAsJsonAsyncSlow(startTask, response, value, jsonTypeInfo,
- ignoreOCE: !cancellationToken.CanBeCanceled,
- cancellationToken.CanBeCanceled ? cancellationToken : response.HttpContext.RequestAborted);
+ return WriteAsJsonAsyncSlow(response, value, jsonTypeInfo, response.HttpContext.RequestAborted);
}
- startTask.GetAwaiter().GetResult();
return JsonSerializer.SerializeAsync(response.BodyWriter, value, jsonTypeInfo, cancellationToken);
- static async Task WriteAsJsonAsyncSlow(Task startTask, HttpResponse response, object? value, JsonTypeInfo jsonTypeInfo,
- bool ignoreOCE, CancellationToken cancellationToken)
+ static async Task WriteAsJsonAsyncSlow(HttpResponse response, object? value, JsonTypeInfo jsonTypeInfo,
+ CancellationToken cancellationToken)
{
try
{
- await startTask;
await JsonSerializer.SerializeAsync(response.BodyWriter, value, jsonTypeInfo, cancellationToken);
}
- catch (OperationCanceledException) when (ignoreOCE) { }
+ catch (OperationCanceledException) { }
}
}
[RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(RequiresDynamicCodeMessage)]
private static async Task WriteAsJsonAsyncSlow(
- Task startTask,
PipeWriter body,
TValue value,
JsonSerializerOptions? options,
- bool ignoreOCE,
CancellationToken cancellationToken)
{
try
{
- await startTask;
await JsonSerializer.SerializeAsync(body, value, options, cancellationToken);
}
- catch (OperationCanceledException) when (ignoreOCE) { }
+ catch (OperationCanceledException) { }
}
///
@@ -304,42 +269,30 @@ public static Task WriteAsJsonAsync(
response.ContentType = contentType ?? ContentTypeConstants.JsonContentTypeWithCharset;
- var startTask = Task.CompletedTask;
- if (!response.HasStarted)
- {
- // Flush headers before starting Json serialization. This avoids an extra layer of buffering before the first flush.
- startTask = response.StartAsync(cancellationToken);
- }
-
// if no user provided token, pass the RequestAborted token and ignore OperationCanceledException
- if (!startTask.IsCompleted || !cancellationToken.CanBeCanceled)
+ if (!cancellationToken.CanBeCanceled)
{
- return WriteAsJsonAsyncSlow(startTask, response.BodyWriter, value, type, options,
- ignoreOCE: !cancellationToken.CanBeCanceled,
- cancellationToken.CanBeCanceled ? cancellationToken : response.HttpContext.RequestAborted);
+ return WriteAsJsonAsyncSlow(response.BodyWriter, value, type, options,
+ response.HttpContext.RequestAborted);
}
- startTask.GetAwaiter().GetResult();
return JsonSerializer.SerializeAsync(response.BodyWriter, value, type, options, cancellationToken);
}
[RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(RequiresDynamicCodeMessage)]
private static async Task WriteAsJsonAsyncSlow(
- Task startTask,
PipeWriter body,
object? value,
Type type,
JsonSerializerOptions? options,
- bool ignoreOCE,
CancellationToken cancellationToken)
{
try
{
- await startTask;
await JsonSerializer.SerializeAsync(body, value, type, options, cancellationToken);
}
- catch (OperationCanceledException) when (ignoreOCE) { }
+ catch (OperationCanceledException) { }
}
///
@@ -367,33 +320,22 @@ public static Task WriteAsJsonAsync(
response.ContentType = contentType ?? ContentTypeConstants.JsonContentTypeWithCharset;
- var startTask = Task.CompletedTask;
- if (!response.HasStarted)
- {
- // Flush headers before starting Json serialization. This avoids an extra layer of buffering before the first flush.
- startTask = response.StartAsync(cancellationToken);
- }
-
// if no user provided token, pass the RequestAborted token and ignore OperationCanceledException
- if (!startTask.IsCompleted || !cancellationToken.CanBeCanceled)
+ if (!cancellationToken.CanBeCanceled)
{
- return WriteAsJsonAsyncSlow(startTask, response.BodyWriter, value, type, context,
- ignoreOCE: !cancellationToken.CanBeCanceled,
- cancellationToken.CanBeCanceled ? cancellationToken : response.HttpContext.RequestAborted);
+ return WriteAsJsonAsyncSlow(response.BodyWriter, value, type, context, response.HttpContext.RequestAborted);
}
- startTask.GetAwaiter().GetResult();
return JsonSerializer.SerializeAsync(response.BodyWriter, value, type, context, cancellationToken);
- static async Task WriteAsJsonAsyncSlow(Task startTask, PipeWriter body, object? value, Type type, JsonSerializerContext context,
- bool ignoreOCE, CancellationToken cancellationToken)
+ static async Task WriteAsJsonAsyncSlow(PipeWriter body, object? value, Type type, JsonSerializerContext context,
+ CancellationToken cancellationToken)
{
try
{
- await startTask;
await JsonSerializer.SerializeAsync(body, value, type, context, cancellationToken);
}
- catch (OperationCanceledException) when (ignoreOCE) { }
+ catch (OperationCanceledException) { }
}
}
diff --git a/src/Http/Http.Extensions/test/HttpResponseJsonExtensionsTests.cs b/src/Http/Http.Extensions/test/HttpResponseJsonExtensionsTests.cs
index 35cfa265d7f1..3d5007e73d26 100644
--- a/src/Http/Http.Extensions/test/HttpResponseJsonExtensionsTests.cs
+++ b/src/Http/Http.Extensions/test/HttpResponseJsonExtensionsTests.cs
@@ -1,12 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.IO.Pipelines;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
-using Microsoft.AspNetCore.InternalTesting;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.TestHost;
#nullable enable
@@ -481,6 +484,71 @@ public async Task WriteAsJsonAsync_NullValue_WithJsonTypeInfo_JsonResponse()
Assert.Equal("null", data);
}
+ // Regression test: https://github.com/dotnet/aspnetcore/issues/57895
+ [Fact]
+ public async Task AsyncEnumerableCanSetHeader()
+ {
+ var builder = WebApplication.CreateBuilder();
+ builder.WebHost.UseTestServer();
+
+ await using var app = builder.Build();
+
+ app.MapGet("/", IAsyncEnumerable (HttpContext httpContext) =>
+ {
+ return AsyncEnum();
+
+ async IAsyncEnumerable AsyncEnum()
+ {
+ await Task.Yield();
+ httpContext.Response.Headers["Test"] = "t";
+ yield return 1;
+ }
+ });
+
+ await app.StartAsync();
+
+ var client = app.GetTestClient();
+
+ var result = await client.GetAsync("/");
+ result.EnsureSuccessStatusCode();
+ var headerValue = Assert.Single(result.Headers.GetValues("Test"));
+ Assert.Equal("t", headerValue);
+
+ await app.StopAsync();
+ }
+
+ // Regression test: https://github.com/dotnet/aspnetcore/issues/57895
+ [Fact]
+ public async Task EnumerableCanSetHeader()
+ {
+ var builder = WebApplication.CreateBuilder();
+ builder.WebHost.UseTestServer();
+
+ await using var app = builder.Build();
+
+ app.MapGet("/", IEnumerable (HttpContext httpContext) =>
+ {
+ return Enum();
+
+ IEnumerable Enum()
+ {
+ httpContext.Response.Headers["Test"] = "t";
+ yield return 1;
+ }
+ });
+
+ await app.StartAsync();
+
+ var client = app.GetTestClient();
+
+ var result = await client.GetAsync("/");
+ result.EnsureSuccessStatusCode();
+ var headerValue = Assert.Single(result.Headers.GetValues("Test"));
+ Assert.Equal("t", headerValue);
+
+ await app.StopAsync();
+ }
+
public class TestObject
{
public string? StringProperty { get; set; }
diff --git a/src/Http/Http.Extensions/test/Microsoft.AspNetCore.Http.Extensions.Tests.csproj b/src/Http/Http.Extensions/test/Microsoft.AspNetCore.Http.Extensions.Tests.csproj
index 686ab34dd28a..4a35778afa55 100644
--- a/src/Http/Http.Extensions/test/Microsoft.AspNetCore.Http.Extensions.Tests.csproj
+++ b/src/Http/Http.Extensions/test/Microsoft.AspNetCore.Http.Extensions.Tests.csproj
@@ -18,6 +18,7 @@
+
diff --git a/src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonOutputFormatter.cs b/src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonOutputFormatter.cs
index c51ca745d8e7..f4e82f6857f7 100644
--- a/src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonOutputFormatter.cs
+++ b/src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonOutputFormatter.cs
@@ -88,11 +88,6 @@ public sealed override async Task WriteResponseBodyAsync(OutputFormatterWriteCon
try
{
var responseWriter = httpContext.Response.BodyWriter;
- if (!httpContext.Response.HasStarted)
- {
- // Flush headers before starting Json serialization. This avoids an extra layer of buffering before the first flush.
- await httpContext.Response.StartAsync();
- }
if (jsonTypeInfo is not null)
{
diff --git a/src/Mvc/Mvc.Core/src/Infrastructure/SystemTextJsonResultExecutor.cs b/src/Mvc/Mvc.Core/src/Infrastructure/SystemTextJsonResultExecutor.cs
index cfce28c8dc64..167d4f71bec0 100644
--- a/src/Mvc/Mvc.Core/src/Infrastructure/SystemTextJsonResultExecutor.cs
+++ b/src/Mvc/Mvc.Core/src/Infrastructure/SystemTextJsonResultExecutor.cs
@@ -66,12 +66,6 @@ public async Task ExecuteAsync(ActionContext context, JsonResult result)
try
{
var responseWriter = response.BodyWriter;
- if (!response.HasStarted)
- {
- // Flush headers before starting Json serialization. This avoids an extra layer of buffering before the first flush.
- await response.StartAsync();
- }
-
await JsonSerializer.SerializeAsync(responseWriter, value, objectType, jsonSerializerOptions, context.HttpContext.RequestAborted);
}
catch (OperationCanceledException) when (context.HttpContext.RequestAborted.IsCancellationRequested) { }
diff --git a/src/Mvc/test/Mvc.FunctionalTests/SystemTextJsonOutputFormatterTest.cs b/src/Mvc/test/Mvc.FunctionalTests/SystemTextJsonOutputFormatterTest.cs
index df54ab0d8cd9..d4906ab320f6 100644
--- a/src/Mvc/test/Mvc.FunctionalTests/SystemTextJsonOutputFormatterTest.cs
+++ b/src/Mvc/test/Mvc.FunctionalTests/SystemTextJsonOutputFormatterTest.cs
@@ -65,4 +65,21 @@ public async Task Formatting_PolymorphicModel_WithJsonPolymorphism()
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
Assert.Equal(expected, await response.Content.ReadAsStringAsync());
}
+
+ // Regression test: https://github.com/dotnet/aspnetcore/issues/57895
+ [Fact]
+ public async Task CanSetHeaderWithAsyncEnumerable()
+ {
+ // Arrange
+ var expected = "[1]";
+
+ // Act
+ var response = await Client.GetAsync($"/SystemTextJsonOutputFormatter/{nameof(SystemTextJsonOutputFormatterController.AsyncEnumerable)}");
+
+ // Assert
+ await response.AssertStatusCodeAsync(HttpStatusCode.OK);
+ Assert.Equal(expected, await response.Content.ReadAsStringAsync());
+ var headerValue = Assert.Single(response.Headers.GetValues("Test"));
+ Assert.Equal("t", headerValue);
+ }
}
diff --git a/src/Mvc/test/WebSites/FormatterWebSite/Controllers/SystemTextJsonOutputFormatterController.cs b/src/Mvc/test/WebSites/FormatterWebSite/Controllers/SystemTextJsonOutputFormatterController.cs
index 287ffa90fd91..dcbd10cb1171 100644
--- a/src/Mvc/test/WebSites/FormatterWebSite/Controllers/SystemTextJsonOutputFormatterController.cs
+++ b/src/Mvc/test/WebSites/FormatterWebSite/Controllers/SystemTextJsonOutputFormatterController.cs
@@ -19,6 +19,14 @@ public class SystemTextJsonOutputFormatterController : ControllerBase
Address = "Some address",
};
+ [HttpGet]
+ public async IAsyncEnumerable AsyncEnumerable()
+ {
+ await Task.Yield();
+ HttpContext.Response.Headers["Test"] = "t";
+ yield return 1;
+ }
+
[JsonPolymorphic]
[JsonDerivedType(typeof(DerivedModel), nameof(DerivedModel))]
public class SimpleModel
From b77c79e89d9aec0055f6c1851040cc4425a20563 Mon Sep 17 00:00:00 2001
From: Brennan
Date: Fri, 20 Sep 2024 16:43:03 -0700
Subject: [PATCH 6/6] Add partitioned to cookie for SignalR browser testing
(#57997)
* Add partitioned to cookie for SignalR browser testing
Looks like Chromium (not chrome) headless now requires 'partitioned' on the cookie when using `Secure` and `Same-Site=None`
* Apply suggestions from code review
Co-authored-by: Andrew Casey
---------
Co-authored-by: Andrew Casey
---
src/SignalR/clients/ts/FunctionalTests/Startup.cs | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/SignalR/clients/ts/FunctionalTests/Startup.cs b/src/SignalR/clients/ts/FunctionalTests/Startup.cs
index 45d3f8546a71..dd3ee9378458 100644
--- a/src/SignalR/clients/ts/FunctionalTests/Startup.cs
+++ b/src/SignalR/clients/ts/FunctionalTests/Startup.cs
@@ -184,9 +184,11 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<
{
cookieOptions.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.None;
cookieOptions.Secure = true;
+ cookieOptions.Extensions.Add("partitioned"); // Required by Chromium
expiredCookieOptions.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.None;
expiredCookieOptions.Secure = true;
+ expiredCookieOptions.Extensions.Add("partitioned"); // Required by Chromium
}
context.Response.Cookies.Append("testCookie", "testValue", cookieOptions);
context.Response.Cookies.Append("testCookie2", "testValue2", cookieOptions);