Skip to content

Commit 148cba6

Browse files
surayya-MSjaviercn
andauthored
Fix SSR page rendering intermediate state instead of the end state of components (#52823) (#52943)
* finish all non streaming pending tasks before rendering ssr page * fix tests * refactor fix for the tests * call Dispatcher.AssertAccess() in AddPendingTask() * add e2e test * fix post request await all non streaming pending tasks; add e2e test * move NonStreamingPendingTasks class to another file * save WaitForNonStreamingPendingTasks into a variable * Update src/Components/test/testassets/Components.TestServer/RazorComponents/Components/ChildComponentThatDelaysLoading.razor * Update src/Components/test/testassets/Components.TestServer/RazorComponents/Components/ParentComponentThatDelaysLoading.razor --------- Co-authored-by: Javier Calvarro Nelson <[email protected]>
1 parent 79e8659 commit 148cba6

File tree

10 files changed

+108
-7
lines changed

10 files changed

+108
-7
lines changed

src/Components/Components/src/RenderTree/Renderer.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -593,7 +593,11 @@ protected virtual void AddPendingTask(ComponentState? componentState, Task task)
593593
{
594594
// The pendingTasks collection is only used during prerendering to track quiescence,
595595
// so will be null at other times.
596-
_pendingTasks?.Add(task);
596+
if (_pendingTasks is { } tasks)
597+
{
598+
Dispatcher.AssertAccess();
599+
tasks.Add(task);
600+
}
597601
}
598602

599603
internal void AssignEventHandlerId(int renderedByComponentId, ref RenderTreeFrame frame)

src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ await EndpointHtmlRenderer.InitializeStandardComponentServicesAsync(
107107
return;
108108
}
109109

110-
await Task.WhenAll(_renderer.NonStreamingPendingTasks);
110+
await _renderer.WaitForNonStreamingPendingTasks();
111111
}
112112
catch (NavigationException ex)
113113
{

src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.Prerendering.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,28 @@ private async Task WaitForResultReady(bool waitForQuiescence, PrerenderedCompone
161161
}
162162
else if (_nonStreamingPendingTasks.Count > 0)
163163
{
164-
// Just wait for quiescence of the non-streaming subtrees
165-
await Task.WhenAll(_nonStreamingPendingTasks);
164+
await WaitForNonStreamingPendingTasks();
165+
}
166+
}
167+
168+
public Task WaitForNonStreamingPendingTasks()
169+
{
170+
return NonStreamingPendingTasksCompletion ??= Execute();
171+
172+
async Task Execute()
173+
{
174+
while (_nonStreamingPendingTasks.Count > 0)
175+
{
176+
// Create a Task that represents the remaining ongoing work for the rendering process
177+
var pendingWork = Task.WhenAll(_nonStreamingPendingTasks);
178+
179+
// Clear all pending work.
180+
_nonStreamingPendingTasks.Clear();
181+
182+
// new work might be added before we check again as a result of waiting for all
183+
// the child components to finish executing SetParametersAsync
184+
await pendingWork;
185+
}
166186
}
167187
}
168188

src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ protected override void AddPendingTask(ComponentState? componentState, Task task
131131
}
132132

133133
// For tests only
134-
internal List<Task> NonStreamingPendingTasks => _nonStreamingPendingTasks;
134+
internal Task? NonStreamingPendingTasksCompletion;
135135

136136
protected override Task UpdateDisplayAsync(in RenderBatch renderBatch)
137137
{

src/Components/Endpoints/test/RazorComponentResultTest.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -336,10 +336,11 @@ public async Task StreamingRendering_IsOffByDefault_AndCanBeEnabledForSubtree()
336336
{
337337
// Arrange
338338
var testContext = PrepareVaryStreamingScenariosTests();
339-
var initialOutputTask = Task.WhenAll(testContext.Renderer.NonStreamingPendingTasks);
339+
var initialOutputTask = testContext.Renderer.NonStreamingPendingTasksCompletion;
340340

341341
// Act/Assert: Even if all other blocking tasks complete, we don't produce output until the top-level
342342
// nonstreaming component completes
343+
Assert.NotNull(initialOutputTask);
343344
testContext.WithinNestedNonstreamingRegionTask.SetResult();
344345
await Task.Yield(); // Just to show it's still not completed after
345346
Assert.False(initialOutputTask.IsCompleted);
@@ -368,10 +369,11 @@ public async Task StreamingRendering_CanBeDisabledForSubtree()
368369
{
369370
// Arrange
370371
var testContext = PrepareVaryStreamingScenariosTests();
371-
var initialOutputTask = Task.WhenAll(testContext.Renderer.NonStreamingPendingTasks);
372+
var initialOutputTask = testContext.Renderer.NonStreamingPendingTasksCompletion;
372373

373374
// Act/Assert: Even if all other nonblocking tasks complete, we don't produce output until
374375
// the component in the nonstreaming subtree is quiescent
376+
Assert.NotNull(initialOutputTask);
375377
testContext.TopLevelComponentTask.SetResult();
376378
await Task.Yield(); // Just to show it's still not completed after
377379
Assert.False(initialOutputTask.IsCompleted);

src/Components/test/E2ETest/ServerRenderingTests/RenderingTest.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,21 @@ public async Task CanUseHttpContextRequestAndResponse()
5050
var response = await new HttpClient().GetAsync(Browser.Url);
5151
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
5252
}
53+
54+
[Fact]
55+
public void RendersEndStateOfComponentsOnSSRPage()
56+
{
57+
Navigate($"{ServerPathBase}/ssr-page-that-delays-loading");
58+
Browser.Equal("loaded child", () => Browser.Exists(By.Id("child")).Text);
59+
}
60+
61+
[Fact]
62+
public void PostRequestRendersEndStateOfComponentsOnSSRPage()
63+
{
64+
Navigate($"{ServerPathBase}/forms/post-form-with-component-that-delays-loading");
65+
66+
Browser.Exists(By.Id("submit-button")).Click();
67+
68+
Browser.Equal("loaded child", () => Browser.Exists(By.Id("child")).Text);
69+
}
5370
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<p id="child">@childString</p>
2+
3+
@code {
4+
private string childString = "initial child";
5+
6+
protected override async Task OnInitializedAsync()
7+
{
8+
await Task.Yield();
9+
10+
childString = "loaded child";
11+
}
12+
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
@if (_loaded)
2+
{
3+
<ChildComponentThatDelaysLoading />
4+
}
5+
6+
@code {
7+
private bool _loaded;
8+
9+
protected override async Task OnInitializedAsync()
10+
{
11+
await base.OnInitializedAsync();
12+
_loaded = await Load();
13+
}
14+
15+
private async Task<bool> Load()
16+
{
17+
await Task.Yield();
18+
19+
return true;
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
@page "/forms/post-form-with-component-that-delays-loading"
2+
@using Microsoft.AspNetCore.Components.Forms
3+
4+
<h3>Post Form With Component That Delays Loading</h3>
5+
6+
@if (_render)
7+
{
8+
<ParentComponentThatDelaysLoading />
9+
}
10+
11+
<form @onsubmit="@(() => _render = true)" @formname="myform" method="post">
12+
<AntiforgeryToken />
13+
<button id="submit-button">Submit</button>
14+
</form>
15+
16+
@code
17+
{
18+
bool _render;
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@page "/ssr-page-that-delays-loading"
2+
<h1>SSR page that delays loading</h1>
3+
4+
<ParentComponentThatDelaysLoading />
5+

0 commit comments

Comments
 (0)