Skip to content

Fix TempData and SupplyParameterFromSession persistence for streaming SSR case#66832

Open
dariatiurina wants to merge 1 commit into
dotnet:mainfrom
dariatiurina:66745-streamingssr-tempdata-sessiondata
Open

Fix TempData and SupplyParameterFromSession persistence for streaming SSR case#66832
dariatiurina wants to merge 1 commit into
dotnet:mainfrom
dariatiurina:66745-streamingssr-tempdata-sessiondata

Conversation

@dariatiurina
Copy link
Copy Markdown
Contributor

@dariatiurina dariatiurina commented May 25, 2026

Fix TempData and SupplyParameterFromSession persistence for streaming SSR case

Description

Fixes a bug where [SupplyParameterFromSession] and session-based TempData values were not persisted correctly during streaming SSR. Both mechanisms previously relied on Response.OnStarting callbacks to save their state, which fires right before the response begins — meaning any values modified during async/streaming rendering were lost.

This PR replaces the OnStarting-based persistence with explicit calls in RazorComponentEndpointInvoker, placed after all rendering (including streaming) completes.

Changes

  • RazorComponentEndpointInvoker.cs: Added explicit calls to SessionCascadingValueSupplier.PersistAllValues() and TempDataProviderServiceCollectionExtensions.PersistTempData() after rendering completes (after SendStreamingUpdatesAsync / EmitInitializersIfNecessary), but before persisted component state is emitted and before BufferedTextWriter.FlushAsync().

  • SessionCascadingValueSupplier.cs: Removed the _onStartingRegistered field and the Response.OnStarting(PersistAllValues) callback registration from CreateSubscription. Persistence is now triggered externally by the endpoint invoker.

  • TempDataProviderServiceCollectionExtensions.cs: Removed the Response.OnStarting callback from GetOrCreateTempData. Added a new PersistTempData(HttpContext) static method that saves TempData on demand, called by the endpoint invoker at the right time.

  • Tests updated: SessionCascadingValueSupplierTest and SessionSubscriptionTest updated to reflect the new explicit persistence model — tests now call PersistAllValues() directly instead of FireOnStartingAsync().

How persistence timing works

The key to understanding this change is the BufferedTextWriter used by RenderComponentCore. This writer accumulates all HTML output in memory and only flushes to Response.Body (starting the HTTP response) when FlushAsync() is explicitly called at the very end of the method.

Non-streaming case

In the non-streaming path, the persistence calls and the old OnStarting approach are functionally equivalent — both execute before Response.HasStarted:

htmlContent.WriteTo(bufferWriter)        → in-memory buffer only
EmitInitializersIfNecessary(bufferWriter) → in-memory buffer only
PersistAllValues() / PersistTempData()   → session/cookie writes ← response NOT started
PrerenderPersistedStateAsync(bufferWriter) → in-memory buffer only
bufferWriter.FlushAsync()                → headers + body sent to client NOW

Cookie-based TempData works here because Set-Cookie headers can still be added before the flush.

Streaming case

In the streaming path, SendStreamingUpdatesAsync flushes the buffer internally, starting the response mid-render. The explicit persistence calls happen after streaming completes:

DisableBuffering()
htmlContent.WriteTo(bufferWriter)        → in-memory buffer
SendStreamingUpdatesAsync()              → flushes buffer, response starts, headers sent
                                          ...streaming renders async components...
PersistAllValues() / PersistTempData()   → Response.HasStarted = true

This is why the fix matters for streaming:

  • Old behavior (OnStarting): Fired before the response started, so it ran before streaming components finished — values modified during streaming were silently lost.
  • New behavior (explicit call): Runs after streaming completes, so all component values are captured.

Cookie-based TempData cannot work in the streaming case regardless of approach — HTTP headers (Set-Cookie) cannot be set after the response body has started. This is not a regression; cookies are fundamentally incompatible with streaming SSR. Session-based persistence ([SupplyParameterFromSession] and session-based TempData) works correctly because session state is stored server-side and doesn't depend on response headers.

Testing

Existing unit tests updated:

  • SessionCascadingValueSupplierTest.SetRequestContext_DoesNotPersist_UntilExplicitlyCalled — verifies values aren't persisted until PersistAllValues() is explicitly invoked
  • SessionSubscriptionTest.CreateSubscription_RegistersValueCallbackAndReturnsSubscription — updated to use PersistAllValues() instead of FireOnStartingAsync()

Fixes #66745

@github-actions github-actions Bot added the area-blazor Includes: Blazor, Razor Components label May 25, 2026
@dariatiurina dariatiurina self-assigned this May 25, 2026
@dariatiurina dariatiurina added this to the 11.0-preview6 milestone May 25, 2026
@dariatiurina dariatiurina marked this pull request as ready for review May 26, 2026 09:33
Copilot AI review requested due to automatic review settings May 26, 2026 09:33
@dariatiurina dariatiurina requested a review from a team as a code owner May 26, 2026 09:33
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes persistence timing for [SupplyParameterFromSession] and TempData during streaming server-side rendering (SSR) by moving persistence from HttpResponse.OnStarting callbacks to explicit persistence calls made after rendering/streaming completes.

Changes:

  • Added explicit post-render persistence calls for session-supplied cascading values and TempData in RazorComponentEndpointInvoker.
  • Removed Response.OnStarting-based persistence registration from SessionCascadingValueSupplier and TempData creation.
  • Updated session-related unit tests to call PersistAllValues() explicitly.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs Adds explicit session + TempData persistence after rendering/streaming completes.
src/Components/Endpoints/src/SessionCascadingValueSupplier.cs Removes OnStarting registration so persistence is driven externally.
src/Components/Endpoints/src/TempData/TempDataProviderServiceCollectionExtensions.cs Removes OnStarting persistence and adds PersistTempData(HttpContext) helper.
src/Components/Endpoints/test/Session/SessionSubscriptionTest.cs Updates subscription test to use explicit persistence instead of firing OnStarting.
src/Components/Endpoints/test/Session/SessionCascadingValueSupplierTest.cs Renames/updates test to validate explicit persistence behavior.

Comment on lines +176 to +183
// Persist TempData and Session values after all components (including streaming)
// have finished rendering, so that values modified during async rendering are captured.
if (context.RequestServices.GetService<SessionCascadingValueSupplier>() is { } sessionSupplier)
{
await sessionSupplier.PersistAllValues();
}
TempDataProviderServiceCollectionExtensions.PersistTempData(context);

internal static void PersistTempData(HttpContext httpContext)
{
if (httpContext.Items.TryGetValue(HttpContextItemKey, out var tempDataObj) && tempDataObj is TempData tempData)
{
{
_supplier.RegisterValueCallback("key", () => "value");

var httpContext = CreateHttpContextWithSession(out var responseFeature);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-blazor Includes: Blazor, Razor Components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix TempData and SupplyParameterFromSession to support streaming SSR

2 participants