Description
I think the Factor out the state preservation to a common location section has some issues that make it incomplete or problematic:
-
Prerendering Issue:
The provided example fails during prerendering because ProtectedSessionStore.GetAsync cannot be invoked from OnInitialized. To fix this, prerendering must be disabled for the example to function correctly. See this code. A possible solution is changing the render mode to something like new InteractiveServerRenderMode(prerender: false).
-
Issue with Multiple Subscribers:
The CounterStateProvider fails when more than one component subscribes to [CascadingParameter] private CounterStateProvider? CounterStateProvider. The cascading value in this case is the CounterStateProvider component instance (this), which doesn't change after being set (you could even set IsFixed="true"). Since the actual counter value (CurrentCount) changes internally, the CascadingValue component is not being re-rendered (allowing the new value to be distributed to subscribers) and updates to CurrentCount are not propagated to other components subscribed to the cascading value. (The only situation when CascadingValue component notifies the subscribers of changes is when the CascadingValue itself receives new parameters in SetParametersAsync.)
Steps to Reproduce:
- Follow the documentation steps, fixing the prerendering issue as mentioned above.
- Create a new
SubCounter.razor component, duplicating the content of Counter.razor but without the @page directive.
- Add
<SubCounter /> to Counter.razor so both components render simultaneously.
- Test the two Increment buttons (one in
Counter.razor and the other in SubCounter.razor). You'll notice that updating the counter in one component doesn't trigger updates in the other.

The current implementation of CounterStateProvider doesn't include a mechanism to notify subscribers about state changes. Without such notifications, this approach is effectively just another way to inject a service into components. If the intent is to use cascading values/parameters for proper state management, the "state" (e.g., CurrentCount) should be the cascading value distributed, not the "provider" (e.g., the CounterStateProvider component).
Suggestion for Improvement:
To use cascading values/parameters effectively for state management, we could use CascadingValueSource<T>. This pattern is used in Blazor's built-in AuthenticationStateProvider, as seen in the AuthenticationStateCascadingValueSource implementation. By propagating the actual state (CurrentCount) rather than the provider, changes would trigger proper re-renders of subscribed components.
(I can try proposing new content using the CascadingValueSource, but it'll probably take some time to prepare since I'm pretty busy with other projects right now.)
Page URL
https://learn.microsoft.com/en-us/aspnet/core/blazor/state-management?view=aspnetcore-9.0&pivots=server
Content source URL
https://github.com/dotnet/AspNetCore.Docs/blob/main/aspnetcore/blazor/state-management.md
Document ID
e5e1273b-195e-5da1-b4aa-66bbcff1425b
Article author
@guardrex
Related Issues
Description
I think the Factor out the state preservation to a common location section has some issues that make it incomplete or problematic:
Prerendering Issue:
The provided example fails during prerendering because
ProtectedSessionStore.GetAsynccannot be invoked fromOnInitialized. To fix this, prerendering must be disabled for the example to function correctly. See this code. A possible solution is changing the render mode to something likenew InteractiveServerRenderMode(prerender: false).Issue with Multiple Subscribers:
The
CounterStateProviderfails when more than one component subscribes to[CascadingParameter] private CounterStateProvider? CounterStateProvider. The cascading value in this case is theCounterStateProvidercomponent instance (this), which doesn't change after being set (you could even setIsFixed="true"). Since the actual counter value (CurrentCount) changes internally, theCascadingValuecomponent is not being re-rendered (allowing the new value to be distributed to subscribers) and updates toCurrentCountare not propagated to other components subscribed to the cascading value. (The only situation whenCascadingValuecomponent notifies the subscribers of changes is when theCascadingValueitself receives new parameters inSetParametersAsync.)Steps to Reproduce:
SubCounter.razorcomponent, duplicating the content ofCounter.razorbut without the@pagedirective.<SubCounter />toCounter.razorso both components render simultaneously.Counter.razorand the other inSubCounter.razor). You'll notice that updating the counter in one component doesn't trigger updates in the other.The current implementation of
CounterStateProviderdoesn't include a mechanism to notify subscribers about state changes. Without such notifications, this approach is effectively just another way to inject a service into components. If the intent is to use cascading values/parameters for proper state management, the "state" (e.g.,CurrentCount) should be the cascading value distributed, not the "provider" (e.g., theCounterStateProvidercomponent).Suggestion for Improvement:
To use cascading values/parameters effectively for state management, we could use
CascadingValueSource<T>. This pattern is used in Blazor's built-inAuthenticationStateProvider, as seen in the AuthenticationStateCascadingValueSource implementation. By propagating the actual state (CurrentCount) rather than the provider, changes would trigger proper re-renders of subscribed components.(I can try proposing new content using the
CascadingValueSource, but it'll probably take some time to prepare since I'm pretty busy with other projects right now.)Page URL
https://learn.microsoft.com/en-us/aspnet/core/blazor/state-management?view=aspnetcore-9.0&pivots=server
Content source URL
https://github.com/dotnet/AspNetCore.Docs/blob/main/aspnetcore/blazor/state-management.md
Document ID
e5e1273b-195e-5da1-b4aa-66bbcff1425b
Article author
@guardrex
Related Issues