Skip to content

Commit e1f66a2

Browse files
Fix streaming into nonstreaming outlet (#50843)
# Fix streaming into nonstreaming outlet Makes an edge case with streaming SSR and SectionOutlet work correctly ## Description A bug was discovered when combining streaming SSR and `<SectionOutlet>` in a particular way (i.e., streaming *directly* into the `<SectionContent>`, not via any child component, when connected to a `<SectionOutlet>` that itself is nonstreaming). In this case the streamed content would not appear in the UI. This PR makes this situation behave the same as other uses of streaming/sections, including the existing case of streaming via a child component to a nonstreaming outlet which is what our E2E tests already covered and already worked correctly. Fixes #50804 ## Customer Impact Without this fix, developers wanting to use sections with streaming SSR would find in some cases that it doesn't work. A workaround exists (move the streaming content to a child component) but with this fix, it simply works as intended in the first place. ## Regression? - [ ] Yes - [x] No [If yes, specify the version the behavior has regressed from] ## Risk - [ ] High - [ ] Medium - [x] Low It's only a change to how we decide which streamed components to emit content for, so it can only affect this one functional area. The code change is just to account for a case we hadn't considered before. ## Verification - [x] Manual (required) - [x] Automated ## Packaging changes reviewed? - [ ] Yes - [ ] No - [x] N/A
1 parent 79b23f4 commit e1f66a2

File tree

4 files changed

+62
-0
lines changed

4 files changed

+62
-0
lines changed

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,17 @@ private void SendBatchAsStreamingUpdate(in RenderBatch renderBatch, TextWriter w
127127
continue;
128128
}
129129

130+
// Of the components that updated, we want to emit the roots of all the streaming subtrees, and not
131+
// any non-streaming ancestors. There's no point emitting non-streaming ancestor content since there
132+
// are no markers in the document to receive it. Also we don't want to call WriteComponentHtml for
133+
// nonstreaming ancestors, as that would make us skip over their descendants who may in fact be the
134+
// roots of streaming subtrees.
135+
var componentState = (EndpointComponentState)GetComponentState(componentId);
136+
if (!componentState.StreamRendering)
137+
{
138+
continue;
139+
}
140+
130141
// This format relies on the component producing well-formed markup (i.e., it can't have a
131142
// </template> at the top level without a preceding matching <template>). Alternatively we
132143
// could look at using a custom TextWriter that does some extra encoding of all the content

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,4 +268,11 @@ public async void StopsProcessingStreamingOutputFromPreviousRequestAfterEnhanced
268268
// Tidy up
269269
await new HttpClient().GetAsync(endResponseUrl);
270270
}
271+
272+
[Fact]
273+
public void CanStreamDirectlyIntoSectionContentConnectedToNonStreamingOutlet()
274+
{
275+
Navigate($"{ServerPathBase}/streaming-with-sections");
276+
Browser.Equal("This is some streaming content", () => Browser.Exists(By.Id("streaming-message")).Text);
277+
}
271278
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
@page "/streaming-with-sections"
2+
@using Microsoft.AspNetCore.Components.Sections
3+
4+
<h1>Streaming with sections</h1>
5+
6+
<p>
7+
If a section outlet is rendered from a nonstreaming component, but the section content comes from a streaming component,
8+
then we should still see streaming updates arriving in the section outlet.
9+
</p>
10+
11+
<p>
12+
This component is not streaming, but it has a child that does stream, and supplies content back here to the parent.
13+
</p>
14+
15+
<fieldset>
16+
<legend>Section outlet in nonstreaming component</legend>
17+
<SectionOutlet SectionName="streaming-outlet" />
18+
</fieldset>
19+
20+
<StreamingWithSectionsContentSupplier />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
@using Microsoft.AspNetCore.Components.Sections
2+
@attribute [StreamRendering]
3+
4+
@*
5+
For the test to be relevant, it's important that the streaming output comes from *this component* and not some descendant.
6+
This is to ensure we correctly represent issue https://github.com/dotnet/aspnetcore/issues/50804
7+
If it's a descendant that streams, the descendant simply lives within the SectionOutlet and encapsulates its own streaming,
8+
and sections aren't really involved. But if the streaming output goes *directly* into SectionContent, then we're in a more
9+
challenging situation because we need the SectionOutletContentRenderer to become streaming.
10+
*@
11+
12+
<SectionContent SectionName="streaming-outlet">
13+
<span id="streaming-message">@message</span>
14+
</SectionContent>
15+
16+
@code {
17+
string message = "Starting...";
18+
19+
protected override async Task OnInitializedAsync()
20+
{
21+
await Task.Delay(1000);
22+
message = "This is some streaming content";
23+
}
24+
}

0 commit comments

Comments
 (0)