I'm building a web app using .net 7.0 and Blazor.
Step back - basic requirement
After a couple of hints and tips, I asume, that I'm choosen the wrong way to achive my requirements.
Therefore I want to explain a little more details about the basic situation and requirement - maybe this helps a little to clarify.
The overall connection to data source is managed by instances of IMoxClientConnection
.
As these objects contains the authentication information of the user, there needs to be a new instance for every user accessing the web page (but not just "scoped" as the user should not have to login on every page he opens).
So I implemented a ConnectionHandler
which basicaly is a Dictionary of the MoxSessionId and the corresponding IMoxClientConnection
instance.
(Which internaly checks if its already present or a new one needs to be created)
This ConnectionHandler
is added as a SingletonService so it persists along application runtime.
All the services requires such a IMoxClientConnection
to access the data source.
The ConnectionHandler
provides a function GetClientConnectorForSession(string moxSessionId)
for the services to retrieve the correct IMoxClientConnection
to use.
Maybe this explaination helps a littel to understand the main idea behind.
Requirement
I need to get any kind of session id for every client accessing the web app. This session id needs to be persistent as long as the browser tab is open and in use. At the moment it doesn't matter if the data persists when the browser/the tab is closed or not.
Idea/Tried so far
My basic idea was to store my own session id (as a string) in a cookie or similar. As far as I found out this is not that easy in Blazor anymore as it was on "old" asp.net.
I need to access this session id from within my base-service class. So I tried to use SessionStorage.
I added
builder.Services.AddScoped<ProtectedSessionStorage>();
to the Program.cs and used DI to make the object available in my service class.
In my base-service class there is the following function to access my session id:
private string CreateOrGetSessionId()
{
var sessionId = _sessionStorage.GetAsync<string>("MoxSessionId").Result.Value;
if (string.IsNullOrWhiteSpace(sessionId))
{
sessionId = Guid.NewGuid().ToString();
_sessionStorage.SetAsync("MoxSessionId", sessionId);
}
return sessionId;
}
Problem
But when I run the app nothing happens after the first row is executed (GetAsync). On some tutorials I found hints, that there also should be a function like GetItem or so, which runs synchronusly - but none of these examples seems to work for me.
Question
Can anybody help me out with a working example how I could achieve my initial requirement. I used the ProtectedSessionStorage as I saw in an example - but basicaly there is no need for protection/encryption.
Thanks in advance
As you say, it isn't quite as simple as it may seem because you need to deal with:
You also can't do it synchronously for the reasons above.
Your code block:
var sessionId = _sessionStorage.GetAsync<string>("MoxSessionId").Result.Value;
blocks the thread. You're trying to shoehorn a piece of async code into a synchronous method.
Here's a Provider service to manage the value. Note you can only get the ID by calling GetSessionIDAsync
. There no read property as property getters can only be sync and getting the value is async.
public class MoxSessionProvider
{
private ProtectedSessionStorage _protectedSessionStorage;
private Guid _sessionUid = Guid.Empty;
public MoxSessionProvider(ProtectedSessionStorage protectedSessionStorage)
=> _protectedSessionStorage = protectedSessionStorage;
public const string MoxSessionId = "MoxSessionId";
public async Task<Guid> GetSessionIDAsync()
{
if (_sessionUid != Guid.Empty)
return _sessionUid;
await GetAsync();
return _sessionUid;
}
private async Task GetAsync()
{
try
{
var result = await _protectedSessionStorage.GetAsync<Guid>(MoxSessionProvider.MoxSessionId);
if (result.Success)
_sessionUid = result.Value;
if (!result.Success)
{
_sessionUid = Guid.NewGuid();
await _protectedSessionStorage.SetAsync(MoxSessionProvider.MoxSessionId, _sessionUid);
}
}
catch
{
_sessionUid = Guid.Empty;
}
}
}
This is registered along with the HttpContextAccessor
:
builder.Services.AddScoped<MoxSessionProvider>();
builder.Services.AddHttpContextAccessor();
We can now build a SessionUidCascade
component to cascade the value.
@inject ProtectedSessionStorage _storage
@inject MoxSessionProvider _moxSessionState
@inject IHttpContextAccessor HttpContextAccessor
<CascadingValue Name="SessionUid" Value="this.GetSessionUidTask" IsFixed=true>
@ChildContent
</CascadingValue>
@code {
[Parameter] public RenderFragment? ChildContent { get; set; }
private bool _isClientRender;
private TaskCompletionSource<Guid?> _taskCompletionSource = new();
public Task<Guid?> GetSessionUidTask => _taskCompletionSource.Task;
protected override async Task OnInitializedAsync()
{
// Check if we are actually rendering not server side pre-rendering
_isClientRender = HttpContextAccessor.HttpContext is not null && HttpContextAccessor.HttpContext.Response.HasStarted;
// Yields which will ensure the component is rendered for the first time
await Task.Yield();
// Pre Rendering so return null
if (!_isClientRender)
_taskCompletionSource.SetResult(null);
// Rendering so get the value
if (_isClientRender)
{
var uid = await _moxSessionState.GetSessionIDAsync();
_taskCompletionSource.SetResult(uid);
}
}
}
Add it to App
so everyone can capture the cascade.
<SessionUidCascade>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, theres nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</SessionUidCascade>
You can consume it like this in an component - SessionUidComponent
.
<div class="bg-dark text-white p-2 m-2">
<pre>Session ID: @(_sessionUid is null ? "Retrieving" : _sessionUid) </pre>
</div>
@code {
[CascadingParameter(Name = "SessionUid")] public Task<Guid?> SessionUidTask { get; set; } = default!;
private Guid? _sessionUid = Guid.Empty;
protected override async Task OnInitializedAsync()
{
ArgumentNullException.ThrowIfNull(SessionUidTask);
_sessionUid = await SessionUidTask;
}
}
@page "/"
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />
<SessionUidComponent />