Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to store session data in server-side blazor

In a server-side Blazor app I'd like to store some state that is retained between page navigation. How can I do it?

Regular ASP.NET Core session state does not seem to be available as most likely the following note in Session and app sate in ASP.NET Core applies:

Session isn't supported in SignalR apps because a SignalR Hub may execute independent of an HTTP context. For example, this can occur when a long polling request is held open by a hub beyond the lifetime of the request's HTTP context.

The GitHub issue Add support to SignalR for Session mentions that you can use Context.Items. But I have no idea how to use it, i.e. I don't know hot to access the HubConnectionContext instance.

What are my options for session state?

like image 293
Codo Avatar asked Dec 24 '18 12:12

Codo


People also ask

How do I use session storage in Blazor WebAssembly?

localStorage and sessionStorage can be used in Blazor WebAssembly apps but only by writing custom code or using a third-party package. Generally, sessionStorage is safer to use. sessionStorage avoids the risk that a user opens multiple tabs and encounters the following: Bugs in state storage across tabs.

Are sessions stored server-side?

Cookies are client-side files that are stored on a local computer and contain user information. Sessions are server-side files that store user information. Cookies expire after the user specified lifetime.

How does Blazor store data in local storage?

In Blazor WebAssebmly, you have to use JavaScript Interop – or you can reference a third-party NuGet package that wraps that JavaScript Interop code for you. In the following code snippet I use the IJSRuntime to access the localStorage JavaScript API of the browser to store data.


2 Answers

Note: This answer is from December 2018 when an early version of Server-side Blazor was available. Most likely, it is no longer relevant.

The poor man's approach to state is a hinted by @JohnB: Use a scoped service. In server-side Blazor, scoped service as tied to the SignalR connection. This is the closest thing to a session you can get. It's certainly private to a single user. But it's also easily lost. Reloading the page or modifying the URL in the browser's address list loads start a new SignalR connection, creates a new service instance and thereby loses the state.

So first create the state service:

public class SessionState {     public string SomeProperty { get; set; }     public int AnotherProperty { get; set; } } 

Then configure the service in the Startup class of the App project (not server project):

public class Startup {     public void ConfigureServices(IServiceCollection services)     {         services.AddScoped<SessionState>();     }      public void Configure(IBlazorApplicationBuilder app)     {         app.AddComponent<Main>("app");     } } 

Now you can inject the state into any Blazor page:

@inject SessionState state   <p>@state.SomeProperty</p>  <p>@state.AnotherProperty</p> 

Better solutions are still super welcome.

like image 89
Codo Avatar answered Sep 22 '22 04:09

Codo


Steve Sanderson goes in depth how to save the state.

For server-side blazor you will need to use any storage implementation in JavaScript that could be cookies, query parameters or for example you can use local/session storage.

There currently NuGet packages implementing that via IJSRuntime like BlazorStorage or Microsoft.AspNetCore.ProtectedBrowserStorage

Now the tricky part is that server-side blazor is pre-rendering pages, so your Razor view code will be run and executed on a server before it's even displayed to the client's browser. This causes an issue where IJSRuntime and thus localStorage is not available at this time. You will need to either disable prerendering or wait for the server generated page to be sent to the client's browser and estabilish a connection back to the server

During prerendering, there is no interactive connection to the user's browser, and the browser doesn't yet have any page in which it can run JavaScript. So it's not possible to interact with localStorage or sessionStorage at that time. If you try, you'll get an error similar to JavaScript interop calls cannot be issued at this time. This is because the component is being prerendered.

To disable prerendering:

(...) open your _Host.razor file, and remove the call to Html.RenderComponentAsync. Then, open your Startup.cs file, and replace the call to endpoints.MapBlazorHub() with endpoints.MapBlazorHub<App>("app"), where App is the type of your root component and "app" is a CSS selector specifying where in the document the root component should be placed.

When you want to keep prerendering:

@inject YourJSStorageProvider storageProvider      bool isWaitingForConnection;      protected override async Task OnInitAsync()     {         if (ComponentContext.IsConnected)         {             // Looks like we're not prerendering, so we can immediately load             // the data from browser storage             string mySessionValue = storageProvider.GetKey("x-my-session-key");         }         else         {             // We are prerendering, so have to defer the load operation until later             isWaitingForConnection = true;         }     }      protected override async Task OnAfterRenderAsync()     {         // By this stage we know the client has connected back to the server, and         // browser services are available. So if we didn't load the data earlier,         // we should do so now, then trigger a new render.         if (isWaitingForConnection)         {             isWaitingForConnection = false;             //load session data now             string mySessionValue = storageProvider.GetKey("x-my-session-key");             StateHasChanged();         }     } 

Now to the actual answer where you want to persist the state between pages you should use a CascadingParameter. Chris Sainty explains this as

Cascading values and parameters are a way to pass a value from a component to all of its descendants without having to use traditional component parameters.

This would be a parameter which would be a class that holds all your state data and exposes methods that can load/save via a storage provider of your choice. This is explained on Chris Sainty's blog, Steve Sanderson's note or Microsoft docs

Update: Microsoft has published new docs explaining Blazor's state management

Update2: Please note that currently BlazorStorage is not working correctly for server-side Blazor with the most recent .NET SDK preview. You can follow this issue where I posted a temporary workaround

like image 41
Konrad Bartecki Avatar answered Sep 22 '22 04:09

Konrad Bartecki