By the time we arrive at that article, we have already dove deep into what Blazor is. Both from a technical and business perspective. If you’ve missed any of the previous articles, browse through our blog and have a read. Let’s expand Blazor definition even further here. Blazor is what’s called a stateful app framework. What does “state” mean exactly?
What is an application state?
As an application user, you’ve most likely never wondered how come your cart is always there when you come back to the shop page. Somehow your Facebook page remembers who you are when you visit the page. Some sites will even remember your language preference and load in dark mode, if that is what you chose on your previous visit.
Everything that is generated for a specific user in the application will be called its state. That would be everything from user data, user settings, latest user inputs, all the way to the hierarchy of component instances and their render trees. Where it starts and ends depends on business needs and developer implementation.
Does state even matter?
To envision that, imagine an application where the state is not kept properly. You’re filling out a tax-form, 6 pages long, calculations in the background, and somehow your Internet connection fails and disconnects you for a second. With that, your entire form gets refreshed so you have to re-fill it again.
In another case – you have to log in to see protected or sensitive data. But as the state is not managed the right way, every time you reload the page – you have to log in again.
We won’t be using that application for long, would we now? That’s why managing a state correctly in a stateful application is crucial for its business success. Nowadays state is a critical part of all applications where user interaction will be happening.
What about state in Blazor?
As we’ve already discussed in the article: “How to start Blazor Project right”, there are major architectural differences between Blazor Server and Client-side. It’s also reflected in the management of the application state.
In Blazor Server, the state is mostly held server-side, in memory, in what’s called a circuit. This could become a problem when network latency is high or the connection gets lost user-side. By default, the Blazor framework will try to reconnect to the same circuit. However, if the connection delay gets too high, a new circuit will be created and the user’s state would be lost. One remedy would be to preserve the state outside of server memory. Yes, we’re covering that below.
In Blazor WebAssembly, managing state is a bit different, as it’s fully hosted in the user’s browser. With no dependency on server memory, latency or network connection problems would not affect application integrity. However, reloading the application or re-visiting the page would result in refreshing the application to its initial state. So persisting would make sense here as well. And yes, we’re going to cover that too.
The simplest approach to state management – CascadingValue
What’s that? CascadingValue allows for cascading sharing of a specified object, from parent component to any level of child components (if you’d like to learn all the details – here’s where you can). What if that object is state storage? And what if you wrap it around all the components you need to have the state shared with? Taking it another step further, you can even wrap CascadingValue around an entire application context. The value that it carries could then be accessed by all the components within that application.
First, let’s create a class that would be responsible for holding state information. It’s a much cleaner approach and easier to maintain, having all the state variables within the same class.
public class UserApplicationState
{
public string AuthenticationToken { get; set; }
public CartState Cart { get; set; }
//….
}
We have a state object. Let’s jump to components that derrive from LayoutComponentBase. Those will expose a RenderFragment called Body. Here’s where wrapping will happen, with the help of CascadingValue and our UserApplicationState instance.
<CascadingValue Value="@UserApplicationState">
@Body
</CascadingValue>
@code {
protected UserApplicationState UserApplicationState = new();
}
With these couple of lines, we’ve enabled all components that would be a part of an application context to utilize our custom state management class. To retrieve it, declare a CascadingParamter in the utilizing component.
[CascadingParameter] protected UserApplicationState UserApplicationState { get; set; }
That’s the simplest way to go about state sharing within a Blazor app. Note that with CascadingValue we might introduce states for specific parts of our application, not necessarily the global one. There’s no limitation on how many objects of this type can be declared.
Sexier approach to state management – dependency injection
A much more advanced and architecturally sane approach to state management is through dependency injection. To make that work, we would introduce a container service. This object is going to store not only state properties, but also methods to manipulate them. Building on our previous example, here’s how such a wrapper could be implemented.
public class UserApplicationState : IUserApplicationState
{
public string AuthenticationToken { get; private set; }
public CartState Cart { get; private set; }
//...
public void StoreAuthenticationToken(string token) => AuthenticationToken = token;
public void StashCart(CartState cart) => Cart = cart;
//...
}
The next step will be adding that service to an application dependencies container. For that, we have to jump over to the Program.cs file, where DI is configured.
var builder = WebApplication.CreateBuilder(args);
//...
builder.Services.AddScoped<IUserApplicationState, UserApplicationState>();
var app = await builder.Build();
Rather than passing the state object as a parameter, as it was in the case of CascadingValue, we can inject it into components or even enable it for entire areas by injecting it into _Imports.razor file.
[Inject] protected IUserApplicationState UserApplicationState { get; set; }
As the name suggests, our service is responsible for managing the application state at the user level. With that, it makes the most sense to register it as a Scoped type. That’s also a hint. For a state shared in the entire application, we could register it as a singleton.
Persisting state server-side but outside the application
Knowing how to approach state management is only half of the success. With a reload of a WebAssembly application or a new Circuit connection for Server-side Blazor, the state object would be reinitialized. How could we persist it either way?
Depending on the approach to storing state information, a couple of valid options are available. As a baseline, we should consider persisting as JSON. Serialization and deserialization of those objects are not only universal, but also highly efficient. For now, let’s assume we would like to have it stored somewhere in the cloud. We could utilize any kind of file storage (as blobs for Azure or buckets at Google Cloud Platform) and save JSONs with the state as simple files. But that’s not efficient as a read-write option. A better way would be to do it with the support of some kind of database. It’s not only more secure but also provides much better performance.
Be aware that at some point of the application’s lifetime, data that is persisted is no longer a state but rather becomes application data. Treating a cart as a state object makes total sense until it’s paid for. Don’t overextend what “state” is. Safe landmark hint: if data does not need to still be available after 24 hours of user inactivity, it can be treated as a state and persisted. In other cases, think of it as application data and store it.
Browser storage – SessionStorage and LocalStorage
When persisting a state in the cloud, we’re taking on another maintenance task. Even though state management is not critical, it should be monitored and maintained to ensure a smooth user experience. On top of that, having it secured could be a challenge in and of itself. There’s another way of persisting that takes away all that potential struggles.
Every modern browser makes parts of memory available for web applications to use. These would not store a vast amount of data but just enough for state management needs. On top of that, browser storage handling in Blazor comes with an encryption feature out of the box. Persisted data is thus not human-readable and any attempts to tamper with the values will result in full reinitiation of the state.
Browser storage allows for two different lifetimes. Session storage, as the name suggests, would be kept as long as an application session is active. On the other hand, Local storage would be persisted as long as the user doesn’t clear it manually.
Using both storages in Blazor solutions is extremely simple. As they’re instantiated by the framework by default, it comes down to injecting their services into components requiring them.
[Inject] protected ProtectedLocalStorage LocalStorage { get; set; }
[Inject] protected ProtectedSessionStorage SessionStorage { get; set; }
Still working with our UserApplicationState class, here’s how to persist and read it from browser storage.
await SessionStorage.SetAsync(
nameof(UserApplicationState), System.Text.Json.JsonSerializer.Serialize(state)
);
var state = await SessionStorage.GetAsync<UserApplicationState>(nameof(UserApplicationState));
And just a brief look into the storage itself to present what the stored value looks like when encrypted with the default algorithm.
Should you choose to utilize browser storage in your application, I’d highly recommend that you still implement a container service. Injecting storage directly into all components could turn out to be a code maintenance nightmare down the road. If you wrap it up – you’re limiting the scope of possible code updates or code failures to that one service class.
New section: where to learn more!
We’ve received some feedback on our series! With that, we’ve decided to start adding a new section to some articles under the title: where to learn more! It will be a section with links to YouTube videos or other sources on the Internet, where you can explore topics from the article.
Here we go:
- a section in official Blazor documentation: here
- Chris Sainty’s walkthrough: here
- session on dotNET channel: here
- an episode of Blazor Train on state management: here
What’s next?
Now that we’ve covered the basics of state management and how to persist it, we can build on that! Let’s drop some buzz words to get you and Google search interested – unilateral communication, decoupling components, redux pattern, and global event/state handling. See you at the next one?