Skip to content

Commit 3ec8cf7

Browse files
authored
Merge pull request #57975 from dotnet/merge/release/9.0-rc2-to-release/9.0
[automated] Merge branch 'release/9.0-rc2' => 'release/9.0'
2 parents 91ef755 + b77c79e commit 3ec8cf7

File tree

18 files changed

+246
-112
lines changed

18 files changed

+246
-112
lines changed

src/Components/WebAssembly/DevServer/src/Server/Startup.cs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using Microsoft.AspNetCore.Builder;
5+
using Microsoft.AspNetCore.Hosting;
56
using Microsoft.AspNetCore.Http;
67
using Microsoft.Extensions.Configuration;
78
using Microsoft.Extensions.DependencyInjection;
@@ -29,27 +30,28 @@ public static void Configure(IApplicationBuilder app, IConfiguration configurati
2930

3031
app.UseWebAssemblyDebugging();
3132

32-
bool applyCopHeaders = configuration.GetValue<bool>("ApplyCopHeaders");
33+
var webHostEnvironment = app.ApplicationServices.GetRequiredService<IWebHostEnvironment>();
34+
var applyCopHeaders = configuration.GetValue<bool>("ApplyCopHeaders");
3335

34-
if (applyCopHeaders)
36+
app.Use(async (ctx, next) =>
3537
{
36-
app.Use(async (ctx, next) =>
38+
if (ctx.Request.Path.StartsWithSegments("/_framework/blazor.boot.json"))
3739
{
38-
if (ctx.Request.Path.StartsWithSegments("/_framework") && !ctx.Request.Path.StartsWithSegments("/_framework/blazor.server.js") && !ctx.Request.Path.StartsWithSegments("/_framework/blazor.web.js"))
40+
ctx.Response.Headers.Append("Blazor-Environment", webHostEnvironment.EnvironmentName);
41+
}
42+
else if (applyCopHeaders && ctx.Request.Path.StartsWithSegments("/_framework") && !ctx.Request.Path.StartsWithSegments("/_framework/blazor.server.js") && !ctx.Request.Path.StartsWithSegments("/_framework/blazor.web.js"))
43+
{
44+
var fileExtension = Path.GetExtension(ctx.Request.Path);
45+
if (string.Equals(fileExtension, ".js", StringComparison.OrdinalIgnoreCase))
3946
{
40-
string fileExtension = Path.GetExtension(ctx.Request.Path);
41-
if (string.Equals(fileExtension, ".js"))
42-
{
43-
// Browser multi-threaded runtime requires cross-origin policy headers to enable SharedArrayBuffer.
44-
ApplyCrossOriginPolicyHeaders(ctx);
45-
}
47+
// Browser multi-threaded runtime requires cross-origin policy headers to enable SharedArrayBuffer.
48+
ApplyCrossOriginPolicyHeaders(ctx);
4649
}
50+
}
4751

48-
await next(ctx);
49-
});
50-
}
52+
await next(ctx);
53+
});
5154

52-
//app.UseBlazorFrameworkFiles();
5355
app.UseRouting();
5456

5557
app.UseStaticFiles(new StaticFileOptions

src/Components/test/E2ETest/Tests/WebAssemblyConfigurationTest.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ public void WebAssemblyConfiguration_Works()
3939

4040
if (_serverFixture.TestTrimmedOrMultithreadingApps)
4141
{
42+
// Verify that the environment gets detected as 'Production'.
43+
Browser.Equal("Production", () => _appElement.FindElement(By.Id("environment")).Text);
44+
4245
// Verify values overriden by an environment specific 'appsettings.$(Environment).json are read
4346
Assert.Equal("Prod key2-value", _appElement.FindElement(By.Id("key2")).Text);
4447

@@ -47,6 +50,9 @@ public void WebAssemblyConfiguration_Works()
4750
}
4851
else
4952
{
53+
// Verify that the dev server always correctly serves the 'Blazor-Environment: Development' header.
54+
Browser.Equal("Development", () => _appElement.FindElement(By.Id("environment")).Text);
55+
5056
// Verify values overriden by an environment specific 'appsettings.$(Environment).json are read
5157
Assert.Equal("Development key2-value", _appElement.FindElement(By.Id("key2")).Text);
5258

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

Lines changed: 22 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -90,22 +90,12 @@ public static Task WriteAsJsonAsync<TValue>(
9090

9191
response.ContentType = contentType ?? ContentTypeConstants.JsonContentTypeWithCharset;
9292

93-
var startTask = Task.CompletedTask;
94-
if (!response.HasStarted)
95-
{
96-
// Flush headers before starting Json serialization. This avoids an extra layer of buffering before the first flush.
97-
startTask = response.StartAsync(cancellationToken);
98-
}
99-
10093
// if no user provided token, pass the RequestAborted token and ignore OperationCanceledException
101-
if (!startTask.IsCompleted || !cancellationToken.CanBeCanceled)
94+
if (!cancellationToken.CanBeCanceled)
10295
{
103-
return WriteAsJsonAsyncSlow(startTask, response.BodyWriter, value, options,
104-
ignoreOCE: !cancellationToken.CanBeCanceled,
105-
cancellationToken.CanBeCanceled ? cancellationToken : response.HttpContext.RequestAborted);
96+
return WriteAsJsonAsyncSlow(response.BodyWriter, value, options, response.HttpContext.RequestAborted);
10697
}
10798

108-
startTask.GetAwaiter().GetResult();
10999
return JsonSerializer.SerializeAsync(response.BodyWriter, value, options, cancellationToken);
110100
}
111101

@@ -131,33 +121,22 @@ public static Task WriteAsJsonAsync<TValue>(
131121

132122
response.ContentType = contentType ?? ContentTypeConstants.JsonContentTypeWithCharset;
133123

134-
var startTask = Task.CompletedTask;
135-
if (!response.HasStarted)
136-
{
137-
// Flush headers before starting Json serialization. This avoids an extra layer of buffering before the first flush.
138-
startTask = response.StartAsync(cancellationToken);
139-
}
140-
141124
// if no user provided token, pass the RequestAborted token and ignore OperationCanceledException
142-
if (!startTask.IsCompleted || !cancellationToken.CanBeCanceled)
125+
if (!cancellationToken.CanBeCanceled)
143126
{
144-
return WriteAsJsonAsyncSlow(startTask, response, value, jsonTypeInfo,
145-
ignoreOCE: !cancellationToken.CanBeCanceled,
146-
cancellationToken.CanBeCanceled ? cancellationToken : response.HttpContext.RequestAborted);
127+
return WriteAsJsonAsyncSlow(response, value, jsonTypeInfo, response.HttpContext.RequestAborted);
147128
}
148129

149-
startTask.GetAwaiter().GetResult();
150130
return JsonSerializer.SerializeAsync(response.BodyWriter, value, jsonTypeInfo, cancellationToken);
151131

152-
static async Task WriteAsJsonAsyncSlow(Task startTask, HttpResponse response, TValue value, JsonTypeInfo<TValue> jsonTypeInfo,
153-
bool ignoreOCE, CancellationToken cancellationToken)
132+
static async Task WriteAsJsonAsyncSlow(HttpResponse response, TValue value, JsonTypeInfo<TValue> jsonTypeInfo,
133+
CancellationToken cancellationToken)
154134
{
155135
try
156136
{
157-
await startTask;
158137
await JsonSerializer.SerializeAsync(response.BodyWriter, value, jsonTypeInfo, cancellationToken);
159138
}
160-
catch (OperationCanceledException) when (ignoreOCE) { }
139+
catch (OperationCanceledException) { }
161140
}
162141
}
163142

@@ -184,52 +163,38 @@ public static Task WriteAsJsonAsync(
184163

185164
response.ContentType = contentType ?? ContentTypeConstants.JsonContentTypeWithCharset;
186165

187-
var startTask = Task.CompletedTask;
188-
if (!response.HasStarted)
189-
{
190-
// Flush headers before starting Json serialization. This avoids an extra layer of buffering before the first flush.
191-
startTask = response.StartAsync(cancellationToken);
192-
}
193-
194166
// if no user provided token, pass the RequestAborted token and ignore OperationCanceledException
195-
if (!startTask.IsCompleted || !cancellationToken.CanBeCanceled)
167+
if (!cancellationToken.CanBeCanceled)
196168
{
197-
return WriteAsJsonAsyncSlow(startTask, response, value, jsonTypeInfo,
198-
ignoreOCE: !cancellationToken.CanBeCanceled,
199-
cancellationToken.CanBeCanceled ? cancellationToken : response.HttpContext.RequestAborted);
169+
return WriteAsJsonAsyncSlow(response, value, jsonTypeInfo, response.HttpContext.RequestAborted);
200170
}
201171

202-
startTask.GetAwaiter().GetResult();
203172
return JsonSerializer.SerializeAsync(response.BodyWriter, value, jsonTypeInfo, cancellationToken);
204173

205-
static async Task WriteAsJsonAsyncSlow(Task startTask, HttpResponse response, object? value, JsonTypeInfo jsonTypeInfo,
206-
bool ignoreOCE, CancellationToken cancellationToken)
174+
static async Task WriteAsJsonAsyncSlow(HttpResponse response, object? value, JsonTypeInfo jsonTypeInfo,
175+
CancellationToken cancellationToken)
207176
{
208177
try
209178
{
210-
await startTask;
211179
await JsonSerializer.SerializeAsync(response.BodyWriter, value, jsonTypeInfo, cancellationToken);
212180
}
213-
catch (OperationCanceledException) when (ignoreOCE) { }
181+
catch (OperationCanceledException) { }
214182
}
215183
}
216184

217185
[RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
218186
[RequiresDynamicCode(RequiresDynamicCodeMessage)]
219187
private static async Task WriteAsJsonAsyncSlow<TValue>(
220-
Task startTask,
221188
PipeWriter body,
222189
TValue value,
223190
JsonSerializerOptions? options,
224-
bool ignoreOCE,
225191
CancellationToken cancellationToken)
226192
{
227193
try
228194
{
229-
await startTask;
230195
await JsonSerializer.SerializeAsync(body, value, options, cancellationToken);
231196
}
232-
catch (OperationCanceledException) when (ignoreOCE) { }
197+
catch (OperationCanceledException) { }
233198
}
234199

235200
/// <summary>
@@ -304,42 +269,30 @@ public static Task WriteAsJsonAsync(
304269

305270
response.ContentType = contentType ?? ContentTypeConstants.JsonContentTypeWithCharset;
306271

307-
var startTask = Task.CompletedTask;
308-
if (!response.HasStarted)
309-
{
310-
// Flush headers before starting Json serialization. This avoids an extra layer of buffering before the first flush.
311-
startTask = response.StartAsync(cancellationToken);
312-
}
313-
314272
// if no user provided token, pass the RequestAborted token and ignore OperationCanceledException
315-
if (!startTask.IsCompleted || !cancellationToken.CanBeCanceled)
273+
if (!cancellationToken.CanBeCanceled)
316274
{
317-
return WriteAsJsonAsyncSlow(startTask, response.BodyWriter, value, type, options,
318-
ignoreOCE: !cancellationToken.CanBeCanceled,
319-
cancellationToken.CanBeCanceled ? cancellationToken : response.HttpContext.RequestAborted);
275+
return WriteAsJsonAsyncSlow(response.BodyWriter, value, type, options,
276+
response.HttpContext.RequestAborted);
320277
}
321278

322-
startTask.GetAwaiter().GetResult();
323279
return JsonSerializer.SerializeAsync(response.BodyWriter, value, type, options, cancellationToken);
324280
}
325281

326282
[RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
327283
[RequiresDynamicCode(RequiresDynamicCodeMessage)]
328284
private static async Task WriteAsJsonAsyncSlow(
329-
Task startTask,
330285
PipeWriter body,
331286
object? value,
332287
Type type,
333288
JsonSerializerOptions? options,
334-
bool ignoreOCE,
335289
CancellationToken cancellationToken)
336290
{
337291
try
338292
{
339-
await startTask;
340293
await JsonSerializer.SerializeAsync(body, value, type, options, cancellationToken);
341294
}
342-
catch (OperationCanceledException) when (ignoreOCE) { }
295+
catch (OperationCanceledException) { }
343296
}
344297

345298
/// <summary>
@@ -367,33 +320,22 @@ public static Task WriteAsJsonAsync(
367320

368321
response.ContentType = contentType ?? ContentTypeConstants.JsonContentTypeWithCharset;
369322

370-
var startTask = Task.CompletedTask;
371-
if (!response.HasStarted)
372-
{
373-
// Flush headers before starting Json serialization. This avoids an extra layer of buffering before the first flush.
374-
startTask = response.StartAsync(cancellationToken);
375-
}
376-
377323
// if no user provided token, pass the RequestAborted token and ignore OperationCanceledException
378-
if (!startTask.IsCompleted || !cancellationToken.CanBeCanceled)
324+
if (!cancellationToken.CanBeCanceled)
379325
{
380-
return WriteAsJsonAsyncSlow(startTask, response.BodyWriter, value, type, context,
381-
ignoreOCE: !cancellationToken.CanBeCanceled,
382-
cancellationToken.CanBeCanceled ? cancellationToken : response.HttpContext.RequestAborted);
326+
return WriteAsJsonAsyncSlow(response.BodyWriter, value, type, context, response.HttpContext.RequestAborted);
383327
}
384328

385-
startTask.GetAwaiter().GetResult();
386329
return JsonSerializer.SerializeAsync(response.BodyWriter, value, type, context, cancellationToken);
387330

388-
static async Task WriteAsJsonAsyncSlow(Task startTask, PipeWriter body, object? value, Type type, JsonSerializerContext context,
389-
bool ignoreOCE, CancellationToken cancellationToken)
331+
static async Task WriteAsJsonAsyncSlow(PipeWriter body, object? value, Type type, JsonSerializerContext context,
332+
CancellationToken cancellationToken)
390333
{
391334
try
392335
{
393-
await startTask;
394336
await JsonSerializer.SerializeAsync(body, value, type, context, cancellationToken);
395337
}
396-
catch (OperationCanceledException) when (ignoreOCE) { }
338+
catch (OperationCanceledException) { }
397339
}
398340
}
399341

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

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.IO.Pipelines;
45
using System.Runtime.CompilerServices;
56
using System.Text;
67
using System.Text.Json;
78
using System.Text.Json.Serialization;
89
using System.Text.Json.Serialization.Metadata;
9-
using Microsoft.AspNetCore.InternalTesting;
10+
using Microsoft.AspNetCore.Builder;
11+
using Microsoft.AspNetCore.Http.Features;
12+
using Microsoft.AspNetCore.TestHost;
1013

1114
#nullable enable
1215

@@ -481,6 +484,71 @@ public async Task WriteAsJsonAsync_NullValue_WithJsonTypeInfo_JsonResponse()
481484
Assert.Equal("null", data);
482485
}
483486

487+
// Regression test: https://github.com/dotnet/aspnetcore/issues/57895
488+
[Fact]
489+
public async Task AsyncEnumerableCanSetHeader()
490+
{
491+
var builder = WebApplication.CreateBuilder();
492+
builder.WebHost.UseTestServer();
493+
494+
await using var app = builder.Build();
495+
496+
app.MapGet("/", IAsyncEnumerable<int> (HttpContext httpContext) =>
497+
{
498+
return AsyncEnum();
499+
500+
async IAsyncEnumerable<int> AsyncEnum()
501+
{
502+
await Task.Yield();
503+
httpContext.Response.Headers["Test"] = "t";
504+
yield return 1;
505+
}
506+
});
507+
508+
await app.StartAsync();
509+
510+
var client = app.GetTestClient();
511+
512+
var result = await client.GetAsync("/");
513+
result.EnsureSuccessStatusCode();
514+
var headerValue = Assert.Single(result.Headers.GetValues("Test"));
515+
Assert.Equal("t", headerValue);
516+
517+
await app.StopAsync();
518+
}
519+
520+
// Regression test: https://github.com/dotnet/aspnetcore/issues/57895
521+
[Fact]
522+
public async Task EnumerableCanSetHeader()
523+
{
524+
var builder = WebApplication.CreateBuilder();
525+
builder.WebHost.UseTestServer();
526+
527+
await using var app = builder.Build();
528+
529+
app.MapGet("/", IEnumerable<int> (HttpContext httpContext) =>
530+
{
531+
return Enum();
532+
533+
IEnumerable<int> Enum()
534+
{
535+
httpContext.Response.Headers["Test"] = "t";
536+
yield return 1;
537+
}
538+
});
539+
540+
await app.StartAsync();
541+
542+
var client = app.GetTestClient();
543+
544+
var result = await client.GetAsync("/");
545+
result.EnsureSuccessStatusCode();
546+
var headerValue = Assert.Single(result.Headers.GetValues("Test"));
547+
Assert.Equal("t", headerValue);
548+
549+
await app.StopAsync();
550+
}
551+
484552
public class TestObject
485553
{
486554
public string? StringProperty { get; set; }

src/Http/Http.Extensions/test/Microsoft.AspNetCore.Http.Extensions.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<Reference Include="Microsoft.AspNetCore.Http.Results" />
1919
<Reference Include="Microsoft.AspNetCore.Http.Extensions" />
2020
<Reference Include="Microsoft.AspNetCore.Mvc.Core" />
21+
<Reference Include="Microsoft.AspNetCore.TestHost" />
2122
<Reference Include="Microsoft.Extensions.DependencyInjection" />
2223
<Reference Include="Microsoft.Extensions.DependencyModel" />
2324
</ItemGroup>

src/Installers/Windows/SharedFrameworkBundle/Bundle.wxs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:dep="http://schemas.microsoft.com/wix/DependencyExtension" xmlns:bal="http://schemas.microsoft.com/wix/BalExtension">
2+
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension"
3+
xmlns:dep="http://schemas.microsoft.com/wix/DependencyExtension" xmlns:bal="http://schemas.microsoft.com/wix/BalExtension">
34
<Bundle Name="$(var.BundleName)" Version="$(var.BundleVersion)" Manufacturer="Microsoft Corporation" UpgradeCode="$(var.BundleUpgradeCode)">
45
<BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.HyperlinkLicense">
56
<bal:WixStandardBootstrapperApplication LicenseUrl="https://go.microsoft.com/fwlink/?LinkId=329770"
@@ -11,6 +12,10 @@
1112
<PayloadGroupRef Id="PG_Resources"/>
1213
</BootstrapperApplicationRef>
1314

15+
<util:RegistrySearchRef Id="RemovePreviousVersionRegistryKeySearch"/>
16+
<util:RegistrySearchRef Id="RemoveSpecificPreviousVersionRegistryKeyExistsSearch"/>
17+
<util:RegistrySearchRef Id="RemoveSpecificPreviousVersionRegistryKeySearch"/>
18+
1419
<!-- Ensure upgrades from 3.0.0 preview 1, 2, and 3. Conditioned for the 3.0.0 family. -->
1520
<?if $(var.Version)=3.0.0.0?>
1621
<?if $(var.Platform)=x86?>

0 commit comments

Comments
 (0)