Reputation: 63
I need an equivalent of Blazor-Server Server-Side session support, that behaves just like Cookie sessions in razor pages and works across multiple tabs. How I can achieve this? Is this so unique that I really have to roll my own?
This question comes up often, and the answers "don't" or "can't". I'm not really satisfied with that because it is possible (albeit not easily) and my customers want and expect it.
My Customer Need: I want a simple option, very similar to "theme Dark Mode" in the FluentBlazor demo. When changed on page, all open pages/tabs/windows from that client update immediately. The scope for sharing state + synchronization is exactly the scope offered by cookies.
What I have looked into: I understand the technical architecture of Blazor, the http pipelines, the SignalR circuits, the browser storage using sessionStorage etc. The Microsoft Learn pages cover a lot of details but none of them provide a usable solution. I also understand well the DI scopes in blazor and their limitations.
The sessionStorage is quite limited because it doesn't work until JSInterop is up. It is way more complicated than just sharing a cookie.
I understand the implications of scaling-out. But this is a real project and we need quick solutions now, with knowledge we can restructure the solution to use distributed cache and some message when (if) we grow.
My Planned Solution: This is what I am about to write, but it seems excessive and I'm surprised it is not already done:
Write some custom middleware, early in the pipeline, to find (or create) a SessionID
cookie.
Write a custom circuit handler to transfer that SessionID
over to the SignalR circuit handler. I think I need to store it in the curcuit.Items
dictionary.
Create an interface to be injected into each page to access the options
IMySessionOptions { public bool IsDarkMode{get;set;} }
Create a class MySessionOptions. This will somehow obtain the session ID from either the httpContext or the SignalR circuit. I think one of those should always be available.
The MySessionOptions can then use some standard in-memory cache & event notification service to synchronize between scopes/circuits. It just needs the shared session key.
Upvotes: 0
Views: 41
Reputation: 63
I have come up with a solution that seems to work.
The miss-information around httpContext
and SignalR made the problem seem harder. It appears (from testing in my environment) that Blazor Server applications behave like this:
httpContext
is handed over from the initial http request to be available throughout the circuit (and hence page) lifetime.If my understanding is right, then I think this would be really nice in the blazor documentation:
"Inside a blazor component, the httpContext should not be thought of a the current httpContext, but as the httpContext that was used for the initial request, if any. Under normal Blazor Server, it should always be available, and remain available for the lifetime of the scope/circuit/page."
I'm happy to share the code, but it is a lot of boilerplate code for not much function. So here is the summary:
SessionIdCookieMiddleware
. That gets or creates a cookie in the early in the request pipeline. It puts the Guid SessionID
into httpContext.Items[]
.ISessionIdCookieProvider
, with implementation that pulls the SessionID
from the httpContextProvider.httpContext?.Items[]
.SessionIdCookieProvider
actually caches the SessionID, just in case (as some documentation states) httpContext becomes unavailable during the circuit lifetime.I think this is a lot of code, but it seems to work well. I don't think we will ever need to scale out - but moving to IDistributedMemoryCache will be that solution (although session affinity seems nicer).
The key question is memory leak / garbage collection.
I think I am okay - as long as I don't forget to Dispose the change subscription which is a problem with any subscription architecture.
Upvotes: 0