Welcome to today’s post.
In today’s post I will be discussing how state is managed within an ASP.NET Core Blazor Server application. As part of the discussion, I will explain how state management differs from an ASP.NET Core Web application.
I will go through the different ways in which state and data is shared within a Blazor application:
- Sharing data from services created with a scoped and singleton lifetime.
- Sharing data within components using service transient lifetime.
- Passing data with parameters between Razor components.
- Persisting application state using browser storage.
In the previous post I discussed how to manage service instance lifetimes in .NET applications. In the sections that follow, we will see how that is applicable to state management within ASP.NET Core Blazor Server applications.
Sharing Data in Components with Service Transient Lifetimes
Within the context of a component, the ability to store the state of a component in between the re-rendering of the component is one situation where we make use of a service that has a transient lifetime. The service itself stores the state of the component, and its modified properties determine what to display or hide within the component during rendering.
I demonstrated the scenario with an example in a previous post, where I showed how to use custom dialogs in ASP.NET Core Blazor applications and showed how to inject a transient service within a component to control the visibility of a custom modal dialog.
The essence of the implementation follows.
The service we used to store the component state of the custom dialog is below (with partial code omitted for brevity):
public class BorrowState
{
public bool ShowingConfigureDialog => Loan is not null;
public LoanViewModel? Loan { get; set; }
…
public void ShowConfigureLoanDialog(BookViewModel book)
{
this.LoanProgress = false;
Loan = new()
{
…
};
}
}
The service was then injected into the Razor component:
@inject BorrowState BorrowState
Within the component markup, the visibility of the dialog was determined conditionally from the properties of the service as shown:
@if (BorrowState.ShowingConfigureDialog)
{
<ConfigureLoanDialog Loan="BorrowState.Loan"
OnCancel="CancelLoan"
OnConfirm="ConfirmLoan" />
}
Sharing Data from Services created with a Scoped and Singleton Lifetime
When a HTTP web request is made in an ASP.NET Core Web application, it is run within its own thread, and its own memory space. Each service created within the request pipeline shares data between services through services that have scoped or singleton lifetimes. No two requests can share data from services that have scoped or transient lifetimes. Services with scoped or transient lifetimes are disposed of once the request ends. Data context services that are created within a request are by default in a scoped lifetime. This means that any two calls in the same request to data within the same context will need to use asynchronous blocking calls (async and await) to prevent corruption or data inconsistencies occurring.
When a browser session is established within an ASP.NET Core Blazor Server application, this creates a user session, or circuit, where created services are in a scoped or transient lifetime. When the user session ends with the browser tab being closed or the browser itself closed, the services are disposed of. The data within each user session’s scoped services cannot be shared with data within another user session’s scoped session. Services create with a singleton lifetime can shared data with any user session.

Data context services that are created within a user session are also defaulted to a scoped lifetime. What this means is that when we try to inject the data context into a Razor component, it will still be in a scoped context. The issue we have with the maintaining of the scoped lifetime of data contexts is that when two components that use the data context are called at the same time, there is a risk of data corruption. To avoid this situation, we create each instance of the data context on demand, then dispose when finished.
In the previous post (Blog 434 – Managing Service Instance Lifetimes in .NET Core Applications) I showed an example of how this was done by injecting an IDbContextFactory<Type> service into the Razor component, then creating an instance of a DbContext service using DbFactory.CreateDbContext(). This strategy of creating and disposing of the data context when it is needed avoids having to keep an instance active during the entire user session.

There are services that can be shared between multiple user sessions, and these can only be instantiated with a singleton lifetime. The most common usage for services with a singleton lifetime is application configurations that do not change during the execution of the application server. When a service is created with a singleton lifetime, there is only one instance that is visible to all user sessions. This means that the properties and methods within the singleton service are in memory and accessible throughout all user sessions. When we run another user session and attempt to instantiate the same singleton service that is already created, it will return an existing instance. When the application server closes, then so will the singleton service and its memory disposed of.
With the above explanations, we understand how the service lifetimes determine how application state and its associated memory is managed.
Not all the application state is dependent on the lifetime of services created within the application. There are also requirements where we will need to read security credentials that are stored outside of in-memory services. Examples of these include the authentication credentials including tokens within the browser session or cookies. As Blazor applications run outside of the ASP.Net Core request pipeline, the HttpContext and HttpContextAccessor services cannot be directly injected into Razor components or services running within a user session. For this reason, the recommended approach when dealing with security contexts coming from a browser request header is to store them within a scoped service on application startup. In addition, when user credentials change during an application session, the authentication and authorization for the application resources and the user security will need to be refreshed to allow the user to continue using the application.
Passing Data with Parameters between Razor Components
The next method in which we can maintain state within a Blazor application is by connecting components with the use of parameters. As we have seen in a previous post I showed how we used page navigation and routing to allow us to navigate from one Razor component to another component.
By passing different parameters into a component, we were able to retrieve data using Entity Framework into the component, and so we were able to develop a data-aware application, where we developed data services that were injected into components and used the retrieved data. With this strategy we can control the data that is selectively rendered within components and use conditional logic within components to determine what navigation options to present to an end-user.
An example of how we navigate from a list of books was to use a URL with a query string as shown:
<a href="viewbook/?id=@book.ID">View</a>
Then within the Razor component’s code segment we declare an input parameter for the book identifier:
[Parameter]
[SupplyParameterFromQuery]
public int ID { get; set; }
The component then renders the book from backend data retrieved from a data service. The use of parameters has ensured that the record corresponding to the selected book can be persisted to subsequent components within the application.
Similarly, the above can be achieved using the NavigationManager service, which can be used by injecting within a component as shown:
@inject NavigationManager PageNavigation
Then page navigation is executed sing the NavigateTo() method as shown:
PageNavigation.NavigateTo($"statuspage/?id={bookId}");
With most data-aware applications, the persistence of application data itself is maintained and stored within a data storage, such as a SQL database. The application itself loads data when required, selected what is needs, then presents it accordingly, and with the parametrization of components, allows subsets of data to be presented within each component. In addition, application logic within components user interfaces allows data to be created, updated, or removed from the data store. Modified data when retrieved and refreshed within the application causes the internal state to change.
Persisting Application State using Browser Storage
The final method I will show how state can be persisted is through using browser storage.
As we have seen with in-memory based state storage, when the browser session or browser tab session is closed, the memory that was allocated to services that were created during the browser session are deallocated and the properties within those services disappear. Even services that are of singleton lifetime, are deallocated from memory on closing of the browser session.
To resolve the above problem, we can rely on an additional technique which involves storing data within the browser session.
With the use of a browser session store, we can store application state within properties within in-memory services that are in a scoped or singleton state. We can then store the same properties from the in-memory services within browser session key-values.
From within each Razor component, we can store user-driven data (item selections, entered text etc.) into in-memory services, then retrieve the corresponding user data from the in-memory services and store them into browser session key-values.
In addition, in-memory services can be used as a cache of retrieved browser session key-values, which can save us network bandwidth by reading in-memory copies of retrieved browser session key-values. Below is the scenario we are given when maintaining state between in-memory services, razor components and browser session storage:

I will demonstrate an example where I store the last three recently selected books to the browser storage. In other scenarios, you might be storing a more complex structure of application states or just single values representing key value pairs.
Below is a class that implements an in-memory storage of the three most recently selected items. It does this by queuing each item when assigned to the Property property setter and lists all items using the Items getter property.
AppBrowsingState.cs:
namespace BookLoanBlazorServerApp.Services
{
public class AppBrowsingState
{
Queue<string> _itemStrings = new Queue<string>();
public List<string>? Items
{
get { return _itemStrings?.Take<string>(3).ToList(); }
}
public string Property
{
set
{
if (!_itemStrings.Contains(value))
{
_itemStrings.Enqueue(value);
if (_itemStrings.Count > 3)
_itemStrings.Dequeue();
}
}
}
}
}
Below is an excerpt from debugging while queuing items to the list:

The lifetime of the above class is specified within Program.cs as shown:
builder.Services.AddScoped<AppBrowsingState>();
Within the Razor components we will still need to add items to the browser storage. We can do this by using the ProtectedSessionStorage class. To use this within a Razor component, we will need to add the following two lines at the top of the source file:
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore
After injecting the ProtectedSessionStorage class, we will have access to store and retrieve data in the browser’s sessionStorage collection.
On thing to bear in mind is that the data will be scoped to the current browser tab. The data is disposed of when the user closes the browser tab or the browser.
There are two useful methods that are accessible to allow storage and retrieval of data from the browser session storage.
GetAsync<TValue>(String)
This method asynchronously retrieves the data of the specified key.
SetAsync(String, Object)
This method asynchronously stores the specified object into the specified key.
To demonstrate the browser session data storage, below is an excerpt of the ViewBook.razor component with the markup omitted. To track the items being added from our component, we inject the BrowsingState service class into the component.
ViewBook.razor:
@page "/viewbook"
@using Microsoft.AspNetCore.Components
@using BookLoanBlazorServerApp.Services
@using BookLoanBlazorServerApp.Models
@inject IBookService LibraryBookService
@inject ILoanService LibraryLoanService
@inject BorrowState BorrowState
@inject NavigationManager PageNavigation
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore
@inject AppBrowsingState? BrowsingState
…
@code {
[Parameter]
[SupplyParameterFromQuery]
public int ID { get; set; }
private BookViewModel book = new();
protected override async Task OnInitializedAsync()
{
book = await LibraryBookService.GetBook(ID);
BrowsingState!.Property = ID.ToString();
int i = 1;
BrowsingState.Items!.ForEach(v =>
{
ProtectedSessionStore.SetAsync("selected_book_item_" + i, v.ToString());
i++;
});
}
…
}
Within the OnInitializedAsync() method, we set the currently book as the latest browsed item. At this point, the item is stored within the BrowsingState in-memory object, with the last item beyond the third item dequeued and the latest item enqueued. We then iterate through all items currently within the BrowsingState object and store each item within the browser session storage.
Below is how we see the data stored within the browser storage session from the development tools window:

Once our items have been stored within the browser session storage, we can retrieve them and display their details from within another component, LastViewedBooks.razor.
LastViewedBooks.razor:
@page "/lastviewedbooks"
@using Microsoft.AspNetCore.Components
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@using BookLoanBlazorServerApp.Services
@using BookLoanBlazorServerApp.Models
@inject IBookService LibraryBookService
@inject ProtectedSessionStorage ProtectedSessionStore
@inject AppBrowsingState? BrowsingState
<PageTitle>Last Viewed Books</PageTitle>
<h3>Last Viewed Books</h3>
@if (books == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
<th>Year Published</th>
<th>Genre</th>
<th colspan="2">Action</th>
</tr>
</thead>
<tbody>
@foreach (var book in books)
{
<tr>
<td>@book.Title</td>
<td>@book.Author</td>
<td>@book.ISBN</td>
<td>@book.YearPublished</td>
<td>@book.Genre</td>
<td>
<a href="editbook/?id=@book.ID">Edit</a>
</td>
<td>
<a href="viewbook/?id=@book.ID">View</a>
</td>
</tr>
}
</tbody>
</table>
}
@code
{
private List<BookViewModel>? books = new List<BookViewModel>();
protected override async Task OnInitializedAsync()
{
List<string>? lastViewed = new List<string>();
BrowsingState.Items!.ForEach(v =>
{
lastViewed.Add(v.ToString());
});
List<BookViewModel>? tempList = await LibraryBookService.GetBooks();
foreach (var book in tempList)
if (lastViewed.Contains(book!.ID.ToString()))
books!.Add(book);
}
}
The recent records from the above are shown below:

The browsing state service has come in handy when retrieving the books that we had stored during the viewing of each book record. Without the use of the in-memory browsing state service, we would have had to rely on directly reading each book identifier stored within the browser storage session. This would have made the retrieval code less clean:
protected override async Task OnInitializedAsync()
{
List<string>? lastViewed = new List<string>();
for (int i = 0; i <= 3; i++)
{
ProtectedBrowserStorageResult<string> val =
await ProtectedSessionStore.GetAsync<string>("selected_book_item_" + i);
if (val.Success) {
string actualVal = val!.Value.ToString();
if (actualVal != null)
lastViewed.Add(val.Value);
}
}
List<BookViewModel>? tempList = await LibraryBookService.GetBooks();
foreach (var book in tempList)
if (lastViewed.Contains(book!.ID.ToString()))
books!.Add(book);
}
}
Retrieving browser session storage data using the GetAsync() method returns the typed value structure ProtectedBrowserStorageResult<TValue>.
It has two useful properties:
Success Gets whether the operation succeeded.
Value Gets the result value of the operation.
Where the Success property is a true or false that determines if the specified key parameter value corresponds to an existing browser session storage value. If the value is found, then it is readable from the Value property as a non-null value.
We have seen from the above discussion and overview the variety of methods in which application state is maintained within a .NET Core Blazor Server application.
The use of data stores and browser session storage has been demonstrated to persist application state outside browser sessions (persistent data) and within user browser sessions (browser session storage).
In addition, the use of page navigation, routing and in-memory state has been demonstrated to persist application state between Razor components and pages within the application.
That is all for today’s post.
I hope you have found this post useful and informative.

Andrew Halil is a blogger, author and software developer with expertise of many areas in the information technology industry including full-stack web and native cloud based development, test driven development and Devops.