Telerik blogs

Today we will learn how to write to and read from the local and session storage in a Blazor Server web application.

In modern web development, storing transient user-specific data is a common task. When the whole web application runs in the browser, we can benefit from storing data inside the browser instead of going through a request/response cycle to store the data on the server.

Examples are items in a shopping cart, the sort order of a table, storing form input before submitting, the specified currency or language or the arrangement of dashboard elements.

 

The Benefits of Using the Browser Storage

First of all, why should we care about storing data on the client in the browser storage instead of transmitting it to the server and storing it in the database?

There are a few important benefits to client-side storage that, depending on the use case, have more or less impact on the decision of where to store the information.

  1. Performance: As stated in the introduction, since we do not have the HTTP request back and forth to the server, there is no network latency and the overall server load is reduced.
  2. Reduced server cost: With less server load, we can scale the application more effectively and it requires less cost to serve the same number of users the more server-side requests we turn into client-side storage access.
  3. Reduced bandwidth usage: In the case of an application primarily used from mobile devices, you save money on the user’s mobile phone data plans or bandwidth usage by storing data on the client.
  4. Data Security: In case we want to store information tied to a specific user, we need to be careful when storing the data on the server. We need to help protect it against attacks. When we store the information on the client side, it is mostly the problem of the user. Consider the search history of an online shop where a certain product category reveals information about a person’s personal health state.

The first reason on the list is the most common. However, sometimes it’s a combination of more than one reason to use client-side storage instead of writing the information to the database.

Local Storage vs. Session Storage

Before we implement a solution, we need to understand the terms local storage and session storage. Modern browsers provide these two types of built-in browser storage.

The local storage persists data even after the browser (tab) is closed. As the name suggests, it stores it on the local machine and offers up to 10 MB of space for your data per domain. Multiple browser tabs or windows with the same domain share the same local storage.

The session storage persists data for a single session and browser tab. Multiple browser tabs have their respective session storage. Like the local storage, its size is limited to about 10 MB.

The restrictions and characteristics make local storage an ideal solution for storing settings, user preferences and column order information.

The session storage is ideal for storing items in a shopping cart or other information that should be deleted when the user leaves the website.

Writing and Data to and from the Session Storage

Let’s say we want to store a random number in the browser’s session storage.

Consider the following Blazor page:

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore

@page "/"

<PageTitle>Protected Browser Storage in Blazor Server</PageTitle>

<p>Generate your lucky number!</p>
<button type="button" @onclick="Generate">Generate</button>

Your lucky number is: @LuckyNumber

@code {
    public int LuckyNumber { get; set; }
    private Random _random = new Random();
    
    protected override async Task OnInitializedAsync()
    {
        var result = await ProtectedSessionStore.GetAsync<int>("luckyNumber");
        if (result.Success)
        {
            LuckyNumber = result.Value;
        }
    }

    public async Task Generate()
    {
        LuckyNumber = _random.Next(1, 99);
        await ProtectedSessionStore.SetAsync("luckyNumber", LuckyNumber);
    }
}

We have a button with an associated Generate method that is called when the user presses the button.

The Generate method uses an object of type Random and an object of the ProtectedSessionStorage type from the Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage namespace.

await ProtectedSessionStore.SetAsync("luckyNumber", LuckyNumber);

The API to write data to the browser’s session storage is simple. We use the asynchronous SetAsync method and provide a key and a value as its arguments.

var result = await ProtectedSessionStore.GetAsync<int>("luckyNumber");
if (result.Success)
{
    LuckyNumber = result.Value;
}

Reading data from the browser’s session storage is a two-step process. First, we use the asynchronous GetAsync method and provide a type to specify the result type.

Next, we check the Success property and access the value using the Value property.

The code looks the same when using the local storage instead of the session storage. The only difference is that instead of injecting an instance of the ProtectedSessionStorage, you would use the ProtectedLocalStorage.

Hint: To make this simplified example work, we need to disable prerendering. The reason is that we cannot execute JavaScript during prerendering because there is no websites in the browser yet, and we need to call the JavaScript API to access the browser’s storage APIs.

To disable prerendering, we need to configure the HeadOutlet and the Routes components in the App.razor files accordingly:

<HeadOutlet @rendermode="new InteractiveServerRenderMode(prerender: false)" />
<Routes @rendermode="new InteractiveServerRenderMode(prerender: false)" />

Handle Prerendering in Blazor Server

The solution shown in the previous chapter works, but doesn’t support prerendering.

In this chapter, we will implement a solution that correctly handles prerendering, allowing us to use one of the key benefits of using Blazor Server.

With prerendering enabled, our component code looks slightly different:

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore

@page "/"

<PageTitle>Protected Browser Storage in Blazor Server</PageTitle>

@if (IsConnected)
{
    <p>Generate your lucky number!</p>
    <button type="button" @onclick="Generate">Generate</button>

    <p>Your lucky number is: @LuckyNumber</p>
}
else
{
    <p>Loading...</p>
}

@code {
    public int LuckyNumber { get; set; }
    public bool IsConnected { get; set; }
    private Random _random = new Random();

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            IsConnected = true;

            var result = await ProtectedSessionStore.GetAsync<int>("luckyNumber");
            if (result.Success)
            {
                LuckyNumber = result.Value;
            }

            StateHasChanged();
        }
    }

    public async Task Generate()
    {
        LuckyNumber = _random.Next(1, 99);
        await ProtectedSessionStore.SetAsync("luckyNumber", LuckyNumber);
    }
}

The Generate method looks the same. However, the component template now has a condition and checks for the IsConnected property to decide whether to render loading information or the button.

The most significant changes happen in the component’s code.

First, we move the code that accesses the session store when loading the component from the OnInitializedAsync to the OnAfterRenderAsync lifecycle method.

The reason is that the OnAfterRenderAsync method is triggered when the page has been rendered and provides an argument about whether it is the first component render cycle.

We then check for the firstRender argument and set the IsConnected property to true, which triggers the button rendering in the component code. In this case, we need to call the StateHasChanged method to trigger a rerender of the page.

With those small changes applied, we introduced some nesting to our code, but on the upside, we now correctly handle Blazor Server prerendering.

ASP.NET Core Protected Browser Storage

When using types from the Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage namespace, such as the ProtectedLocalStorage and the ProtectedSessionStorage, we use a protected storage, as the name implies.

ASP.NET Core Protected Browser Storage uses data protection to encrypt the information stored in the browser. It limits the access to the information to the application and prevents outside access, such as using developer tools and manipulating the data.

When running the example code discussed previously in this article, the browser storage looks like this:

Browser dev tools showing the session storage content with a single key/value pair with the key 'luckyNumber' and an encrypted value.

As you can see, the key is stored in plain text. It makes total sense since we identify the data by its key. However, the value is encrypted, and the random number that we store is stored in the session storage as a string with 134 characters.

Gotchas When Working with Browser Storage

There are a few pitfalls lurking when working with browser storage.

  • Similar to storing data on the server, the browser APIs for accessing the browser storage are asynchronous. This introduces some complexity to the web application.
  • Related to the first pitfall, we cannot access the browser storage during Blazor prerendering because prerendering happens on the server. Therefore, a website that can access browser APIs doesn’t yet exist.

And, of course, we need to consider the data size limitation when deciding whether to store the information client-side or send it to the server.

Browser Storage in Blazor WebAssembly

The example shown in this article uses Blazor Server.

The ASP.NET Core Protected Browser Storage API is a server-only API and, therefore, can only be used for Blazor Server.

When it comes to Blazor WebAssembly, there are two solutions:

  1. You can manually implement a wrapper around the JavaScript interop to access the browser’s session storage and local storage APIs.
  2. You use one of the existing third-party open-source Blazor libraries, such as LocalStorage from Blazored.

Conclusion

Writing data client-side instead of server-side has different advantages based on the use case. The most important are reduced server load and better responsiveness (no network latency).

Using the ASP.NET Core Protected Browser Storage, implementing a solution for Blazor Server is simple. When done properly, it also supports Blazor Server prerendering.

For Blazor WebAssembly, we need to implement JavaScript Interop wrapper or rely on a third-party solution to access the browser’s storage API.

You can access the code used in this example on GitHub.

If you want to learn more about Blazor development, you can watch my free Blazor Crash Course on YouTube. And stay tuned to the Telerik blog for more Blazor Basics.


About the Author

Claudio Bernasconi

Claudio Bernasconi is a passionate software engineer and content creator writing articles and running a .NET developer YouTube channel. He has more than 10 years of experience as a .NET developer and loves sharing his knowledge about Blazor and other .NET topics with the community.

Related Posts

Comments

Comments are disabled in preview mode.