The easy way is to use Dependency Injection. On Blazor server, scoped, almost means "one instance by connection":
The Blazor Server hosting model supports the Scoped lifetime across HTTP requests but not across SignalR connection/circuit messages among components that are loaded on the client. The Razor Pages or MVC portion of the app treats scoped services normally and recreates the services on each HTTP request when navigating among pages or views or from a page or view to a component. Scoped services aren't reconstructed when navigating among components on the client, where the communication to the server takes place over the SignalR connection of the user's circuit, not via HTTP requests. In the following component scenarios on the client, scoped services are reconstructed because a new circuit is created for the user:
- The user closes the browser's window. The user opens a new window and navigates back to the app.
- The user closes a tab of the app in a browser window. The user opens a new tab and navigates back to the app.
- The user selects the browser's reload/refresh button.
If it's acceptable for you to regard a lifetime SignalR connection/circuit as a "connected user", the process is quite straightforward. I explain the process step by step:
1 - Creating structs to store list of connected users
Create classes to store ConnectedUser
data and a ConnectedUserList
:
public class ConnectedUser
{
public string Name { get; set; } = Guid.NewGuid().ToString();
}
public class ConnectedUserList
{
public List<ConnectedUser> Users { get; set; } = new();
}
2 - Inject classes with the right scope
On program.cs
inject both classes on services: ConnectedUser
(scoped) and ConnectedUserList
(singleton) that will contain a list of ConnectedUser
.
// Program.cs
builder.Services.AddScoped<ConnectedUser>();
builder.Services.AddSingleton<ConnectedUserList>();
3 - Capture connected user and add to global list
On Shared/MainLayout.razor
, get by injection both objects and add ConnectedUser
to ConnectedUserList
(on login process or events, you can update ConnectedUser.Username
to the logged username)
@implements IDisposable
@inject ConnectedUser _ConnectedUser
@inject ConnectedUserList _ConnectedUserList
@code {
protected override void OnInitialized()
{
_ConnectedUserList.Add(_ConnectedUser )
}
// Don't forget to remove `ConnectedUser` from
// `ConnectedUserList` on dispose.
public void Dispose()
{
_ConnectedUserList.Remove(_ConnectedUser )
}
4 - Show all connected users
At any moment you can get ConnectedUserList
and check the list. For example index.razor
:
@page "/"
@inject ConnectedUser _ConnectedUser
@inject ConnectedUserList _ConnectedUserList
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
@foreach (var user in _ConnectedUserList.Users)
{
<p>@user.Name</p>
}
Notes
- To keep the answer simple, I used a
List
struct to store connected users, but List
is not thread safe.