AppStateServer
This is a demonstration of how to use a Cascading Parameter to share state between components in a .NET Blazor Web App using InteractiveServer mode
Install / Use
/learn @carlfranklin/AppStateServerREADME
Managing Application State in .NET 8/9 Blazor Web Apps
This repo shows how to manage application state in a .NET 8 or .NET 9 Blazor Web App using Interactive Server RenderMode.
See these other repos for demos using the other two render modes:
- https://github.com/carlfranklin/AppStateWasm (Wasm)
- https://github.com/carlfranklin/AppStateAuto (Auto)
Watch the BlazorTrain YouTube video here
What is Application State?
Application State (or app state) represents collectively all of the variables (objects, lists, etc.) that you keep alive in your application while in use. The default Blazor Sample App templates keep all variables in code blocks inside of pages. The problem with this approach is that those variables get reinitialized every time the page is navigated to or refreshed.
The key to app state is to move it to a component that lives outside of the pages so that
- variables persist between navigations and page refreshes
- variables can be shared by all pages and components
Goals of an AppState Component
- We want UI to automatically update via binding whenever any of the properties update.
- We may want to get control when the state gets mutated.
Create a new Blazor Web App called AppStateServer


Make sure you specify the following options:

Interactive render mode: Server
Interactivity Location: Global
Include sample pages: yes
Cascading Component
The approach we will take is to use a Cascading component. This is essentially an object reference that can be accessed by any component in the render tree below where it is defined. If we want all pages and components to have access to it, we can wrap it around the Router in Routes.razor, but I'm getting ahead of myself.
Observe the default behavior
Run the app (F5)
Go to the Counter page and increment the counter, now navigate to the Home page (Home) and back to Counter. Notice that the counter has been reset to zero!
This is because the counter value itself (currentCount) is defined within the page.
@page "/counter"
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
Whenever you navigate to a page, it is reinitialized. All of the module-level variables (such as currentCount) get reset to default values.
Let's fix that on our way to provide state to the entire app.
Add a new Razor Component called CascadingAppState.razor
<CascadingValue Value="this">
@ChildContent
</CascadingValue>
@code {
[Parameter]
public RenderFragment ChildContent { get; set; }
/// <summary>
/// Implement property handlers like so
/// </summary>
private string message = "";
public string Message
{
get => message;
set
{
message = value;
StateHasChanged(); // Optionally force a re-render
}
}
private int count = 0;
public int Count
{
get => count;
set
{
count = value;
StateHasChanged();
}
}
protected override void OnInitialized()
{
Message = "Initial Message";
}
}
What is a RenderFragment?
A RenderFragment represents a chunk of Razor markup that can then be rendered by the component. Razor components can capture their child content as a RenderFragment and render that content as part of the component rendering. To capture child content, define a component parameter of type RenderFragment and name it ChildContent.
Using a RenderFragment named ChildContent tells the Blazor component engine that ChildContent is everything in the render tree below this component.
The property handlers must be explicit so that we can call StateHasChanged() when values are set. That tells the rendering engine that something has changed, and a redraw is necessary.
For more information on RenderFragments see https://learn.microsoft.com/en-us/aspnet/core/blazor/components/?view=aspnetcore-8.0#child-content-render-fragments:
Wrap the entire contents of Routes.razor in an instance of CascadingAppState
<CascadingAppState>
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
</Router>
</CascadingAppState>
Add a Razor Component to the Components folder called Toolbar.razor
<div style="height:42px;">
<span style="font-size:x-large">@AppState.Message</span>
</div>
@code {
[CascadingParameter]
public CascadingAppState AppState { get; set; }
}
The Toolbar will go across the top of the page.
Note that we grab the reference to CascadingAppState with the [CascadingParameter] attribute. It's almost the same as injection, except that it's optimized for use in Blazor components and pages.
Now we can refer to AppState and it's properties anywhere in the component. The values of those properties exist OUTSIDE the page, and will still be there if we reload the component.
Modify Layout\MainLayout.razor to show the Toolbar
@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
<Toolbar/>
</div>
<article class="content px-4">
@Body
</article>
</main>
</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
Modify Components\Pages\Home.razor
@page "/"
<PageTitle>Home</PageTitle>
<p>
This is a demonstration of how to use a Cascading Parameter to share state between components
in a .NET Blazor Web App using InteractiveServer mode.
</p>
<button class="btn btn-primary" @onclick="UpdateMessageButtonClicked">Update Message</button>
<br />
<br />
<h3>@AppState.Message</h3>
@code
{
[CascadingParameter]
public CascadingAppState AppState { get; set; }
void UpdateMessageButtonClicked()
{
AppState.Message = $"Message Updated At {DateTime.Now.ToLongTimeString()}";
}
}
Again, we're grabbing that cascading reference to CascadingAppState. Now we have two components with access to it, and they can both get and set it's properties.
Modify Counter.razor
@page "/counter"
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @AppState.Count</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
[CascadingParameter]
public CascadingAppState AppState { get; set; }
private void IncrementCount()
{
// This is the only place AppState.Count is incremented
AppState.Count++;
}
}
Here we have replaced currentCount with AppState.Count.
Go to the Counter page, and click the button. Now navigate to the Home page and back to the Counter page.
Notice that our counter page remembers the value between navigations. That's because it is being stored in the CascadingAppStateProvider!
Click the Update Message button, and notice that the text in the Toolbar changes automatically.
Getting control when an AppState variable is changed
So far we have handled the situation where multiple components that access AppState properties can update their UI whenever one of those components changes an AppState property value.
What if a component needs to take action when a property value changes? For example, the Toolbar might want to send a message, make an API call, or the like.
One option is just to handle this inside the CascadingAppState.razor component's property setters. However, we may not want to give the AppState component such power over the application. In general, we should adhere to the Single Responsibility Principle.
In previous versions of this code, I created a pub/sub mechanism by which each component/page can be notified when a property changes.
This demo turned out to have memory leaks, because I didn't provide a way to unsubscribe (UnRegister). One fix would be to implement IDisposable to unhook the event callback. It works fine, but I wanted an easier solution that didn't require un-registering.
So, on January 17. 2025, I greatly simplified this process. CascadingAppState.GetCopy() returns a copy of all the properties via an Interface. Any component can determine what changed by hooking OnAfterRender and comparing the AppState values to the saved values.
Monitor AppState Property Changes in the Toolbar
First, we are going to separate the properties we want to persist into an interface.
That will allow us to serialize just those properties to JSON, allowing us to make a deep copy, and also persist the values, which we will do in a few minutes.
Add the following class to the project:
IAppState.cs
namespace AppStateServer;
public interface IAppState
{
string Message { get; set; }
int Count { get; set; }
}
System.Text.Json will not deserialize to an interface, so we have to create a class that implements the interface:
namespace AppStateServer;
public class AppState : IAppState
{
public string Message { get; set; } = string.Empty;
public int Count { get; set; }
Related Skills
node-connect
352.0kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
111.1kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
352.0kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
352.0kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
