g.pickardou
g.pickardou

Reputation: 35973

HttpContext.Session in Blazor Server Application

I am trying to use HttpContext.Session in my ASP.NET Core Blazor Server application (as described in this MS Doc, I mean: all correctly set up in startup)

Here is the code part when I try to set a value:

var session = _contextAccessor.HttpContext?.Session;
if (session != null && session.IsAvailable)
{
    session.Set(key, data);
    await session.CommitAsync();
}

When this code called in Razor component's OnAfterRenderAsync the session.Set throws following exception:

The session cannot be established after the response has started.

I (probably) understand the message, but this renders the Session infrastructure pretty unusable: the application needs to access its state in every phase of the execution...

Question

Should I forget completely the DistributedSession infrastructure, and go for Cookies, or Browser SessionStorage? ...or is there a workaround here still utilizing HttpContext.Session? I would not want to just drop the distributed session infra for a way lower level implementation...

(just for the record: Browser's Session Storage is NOT across tabs, which is a pain)

Upvotes: 7

Views: 12082

Answers (3)

Gaurang Thakkar
Gaurang Thakkar

Reputation: 31

  1. SessionStorege has more space than cookies.
  2. Syncing (two ways!) the sessionStorage is impossible correctly I think you are thinking that if it is on the browser, how can you access that in C#? Please see some examples. It actually read from the browser and transfers (use) on the server side.
  3. sessionstorage and localstorage in blazor are encrypted. We do not need to do extra for encryption. The same applies for serialization.

Upvotes: 0

user3163495
user3163495

Reputation: 3672

Note: I know this answer is a little old, but I use sessions with WebSockets just fine, and I wanted to share my findings.

Answer

I think this Session.Set() error that you're describing is a bug, since Session.Get() works just fine even after the response has started, but Session.Set() doesn't. Regardless, the workaround (or "hack" if you will) includes making a throwaway call to Session.Set() to "prime" the session for future writing. Just find a line of code in your application where you KNOW the response hasn't sent, and insert a throwaway call to Session.Set() there. Then you will be able to make subsequent calls to Session.Set() with no error, including ones after the response has started, inside your OnInitializedAsync() method. You can check if the response is started by checking the property HttpContext.Response.HasStarted.

Try adding this app.Use() snippet into your Startup.cs Configure() method. Try to ensure the line is placed somewhere before app.UseRouting():

...
...
app.UseHttpsRedirection();
app.UseStaticFiles();

//begin Set() hack
app.Use(async delegate (HttpContext Context, Func<Task> Next)
{
    //this throwaway session variable will "prime" the Set() method
    //to allow it to be called after the response has started
    var TempKey = Guid.NewGuid().ToString(); //create a random key
    Context.Session.Set(TempKey, Array.Empty<byte>()); //set the throwaway session variable
    Context.Session.Remove(TempKey); //remove the throwaway session variable
    await Next(); //continue on with the request
});
//end Set() hack

app.UseRouting();


app.UseEndpoints(endpoints =>
{
    endpoints.MapBlazorHub();
    endpoints.MapFallbackToPage("/_Host");
});
...
...

Background Info

The info I can share here is not Blazor specific, but will help you pinpoint what's happening in your setup, as I've come across the same error myself. The error occurs when BOTH of the following criteria are met simultaneously:

Criteria 1. A request is sent to the server with no session cookie, or the included session cookie is invalid/expired.

Criteria 2. The request in Criteria 1 makes a call to Session.Set() after the response has started. In other words, if the property HttpContext.Response.HasStarted is true, and Session.Set() is called, the exception will be thrown.

Important: If Criteria 1 is not met, then calling Session.Set() after the response has started will NOT cause the error.

That is why the error only seems to happen upon first load of a page--it's because often in first loads, there is no session cookie that the server can use (or the one that was provided is invalid or too old), and the server has to spin up a new session data store (I don't know why it has to spin up a new one for Set(), that's why I say I think this is a bug). If the server has to spin up a new session data store, it does so upon the first call to Session.Set(), and new session data stores cannot be spun up after the response has started. On the other hand, if the session cookie provided was a valid one, then no new data store needs to be spun up, and thus you can call Session.Set() anytime you want, including after the response has started.

What you need to do, is make a preliminary call to Session.Set() before the response gets started, so that the session data store gets spun up, and then your call to Session.Set() won't cause the error.

Upvotes: 3

Chris Pratt
Chris Pratt

Reputation: 239440

Blazor is fundamentally incompatible with the concept of traditional server-side sessions, especially in the client-side or WebAssembly hosting model where there is no server-side to begin with. Even in the "server-side" hosting model, though, communication with the server is over websockets. There's only one initial request. Server-side sessions require a cookie which must be sent to the client when the session is established, which means the only point you could do that is on the first load. Afterwards, there's no further requests, and thus no opportunity to establish a session.

The docs give guidance on how to maintain state in a Blazor app. For the closest thing to traditional server-side sessions, you're looking at using the browser's sessionStorage.

Upvotes: 8

Related Questions