Nel precedente script abbiamo visto come usare un CircuitHandler per conteggiare il numero di utenti collegati ad un'applicazione Blazor Server.
(https://www.aspitalia.com/script/1360/Conteggiare-Connessioni-SignalR-Aperte-Blazor-Server.aspx)
In questo script facciamo un passo in più e vediamo come il CircuitHandler possa essere usato per accedere al contesto HTTP, così da ottenere il nome dell'utente per aggiungerlo a un elenco di utenti loggati.
Il ciclo di vita di un Circuit
In precedenza abbiamo usato gli override OnCircuitOpenedAsync e OnCircuitClosedAsync per eseguire codice all'inizio e alla fine della vita di un Circuit, che è la rappresentazione logica di un client connesso all'applicazione.Inoltre, possiamo usare gli override OnConnectionDownAsync e OnConnectionUpAsync che invece sono legati ad una connessione fisica WebSockets, come illustrato dall'immagine seguente. Eseguire codice in questi metodi è probabilmente più indicativo dell'effettivo stato di connessione di un utente.

Un CircuitHandler per tracciare gli utenti loggati
Il CircuitHandler che realizzeremo, a differenza di quello che abbiamo visto nello script precedente, ha una dipendenza da IServiceProvider. Grazie ad esso possiamo accedere ai servizi registrati per la dependency injection, tra cui l'IHttpContextAccessor che ci permette di ottenere il contesto HTTP e perciò il nome dell'utente loggato.Vediamolo nel seguente esempio.
public class LoggedInUsersCircuitHandler : CircuitHandler, ILoggedInUserTracker { //Usiamo un ConcurrentDictionary per mantenere un elenco degli utenti loggati in maniera thread-safe public ConcurrentDictionary<string, DateTime> loggedInUsers = new ConcurrentDictionary<string, DateTime>(); //Questo CircuitHandler dipende da un IServiceProvider, ci servirà per ottenere un //riferimento al contesto HTTP corrente da cui otteniamo l'identità dell'utente private readonly IServiceProvider serviceProvider; public LoggedInUsersCircuitHandler(IServiceProvider serviceProvider) { this.serviceProvider = serviceProvider; } //Connessione al client stabilita public override Task OnConnectionUpAsync(Circuit circuit, CancellationToken cancellationToken) { //Recuperiamo il suo username string username = GetAuthenticatedUserName(); if (username != null) { //Se l'utente era loggato, allora aggiungiamo il suo username al ConcurrentDictionary if (loggedInUsers.TryAdd(username, DateTime.Now)) { //E solleviamo un evento per notificare i sottoscrittori UserLoggedIn?.Invoke(this, username); } } //Invochiamo il metodo base del CircuitHandler return base.OnConnectionUpAsync(circuit, cancellationToken); } //Connessione al client chiusa public override Task OnConnectionDownAsync(Circuit circuit, CancellationToken cancellationToken) { //Otteniamo lo username dell'utente string username = GetAuthenticatedUserName(); if (username != null) { //Se era loggato, lo rimuoviamo dall'elenco if (loggedInUsers.TryRemove(username, out DateTime dateTime)) { //E solleviamo un evento per notificare i sottoscrittori UserLoggedOut?.Invoke(this, username); } } //Invochiamo il metodo base return base.OnConnectionDownAsync(circuit, cancellationToken); } private string GetAuthenticatedUserName() { //Usando il ServiceProvider otteniamo il contesto HTTP corrente var httpContextAccessor = serviceProvider.GetService<IHttpContextAccessor>(); //E dal contesto HTTP otteniamo l'identità dell'utente IPrincipal user = httpContextAccessor.HttpContext.User; if (user.Identity.IsAuthenticated) { //Se era autenticato, restituiamo lo username return user.Identity.Name; } return null; } #region Implementazione di ILoggedInUserTracker public event EventHandler<string> UserLoggedIn; public event EventHandler<string> UserLoggedOut; public ICollection<string> CurrentLoggedInUsers => loggedInUsers.Keys; #endregion }
Implementando l'interfaccia ILoggedInUserTracker possiamo esporre dei membri pubblici che saranno poi usati da un Razor Component per interagire con il CircuitHandler. Ecco di seguito la definizione di tale interfaccia.
public interface ILoggedInUserTracker { event EventHandler<string> UserLoggedIn; event EventHandler<string> UserLoggedOut; ICollection<string> CurrentLoggedInUsers { get; } }
Il CircuitHandler deve essere registrato così nella classe Startup, usando il ciclo di vita Singleton. Inoltre, facciamo in modo che la stessa istanza venga registrata per l'interfaccia ILoggedInUserTracker.
services.AddSingleton<CircuitHandler, LoggedInUsersCircuitHandler>(); //La stessa istanza viene riutilizzata anche quando si richiede il servizio ILoggedInUserTracker services.AddSingleton<ILoggedInUserTracker>(provider => provider.GetService<CircuitHandler>() as ILoggedInUserTracker); //Registriamo il servizio IHttpContextAccessor, che usiamo per recuperare l'identità dell'utente services.AddHttpContextAccessor();
Visualizzare l'elenco degli utenti un Razor Component
Ora possiamo consumare il CircuitHandler da un Razor Component, così da visualizzare l'elenco degli utenti loggati. Inseriamo il seguente codice in un file .razor, ad esempio /Pages/Users.razor.@page "/users" @inject ILoggedInUserTracker userTracker @implements IDisposable <h1>Utenti attualmente loggati (@currentLoggedInUsers.Count)</h1> <ul> @foreach (var user in currentLoggedInUsers) { <li>@user</li> } </ul> @code { HashSet<string> currentLoggedInUsers; protected override void OnInitialized() { //Otteniamo il valore attuale dalla proprietà CurrentLoggedInUsers currentLoggedInUsers = userTracker.CurrentLoggedInUsers.ToHashSet(); //E ci sottoscriviamo agli eventi per ricevere i futuri cambiamenti userTracker.UserLoggedIn += AddUserToList; userTracker.UserLoggedOut += RemoveUserFromList; } private void AddUserToList(object sender, string username) { //Usiamo InvokeAsync per far eseguire questo codice //al thread del renderer, altrimenti avremmo un'eccezione InvokeAsync(() => { //Aggiungiamo alla lista il nome dell'utente loggato currentLoggedInUsers.Add(username); StateHasChanged(); }); } private void RemoveUserFromList(object sender, string username) { InvokeAsync(() => { //Rimuoviamo l'utente dalla lista currentLoggedInUsers.Remove(username); StateHasChanged(); }); } public void Dispose() { //Rimuoviamo le sottoscrizioni quando //il Razor Component non serve più userTracker.UserLoggedIn -= AddUserToList; userTracker.UserLoggedOut -= RemoveUserFromList; } }
Avviando l'applicazione in debug, vedremo l'elenco degli utenti loggati aggiornarsi in tempo reale. Se usiamo ASP.NET Core Identity, possiamo facilmente provarlo registrando vari utenti e facendo il login da browser diversi.

Dato che stiamo tracciando l'attività degli utenti, è sempre importante valutare se a livello legale sia necessario acquisire preventivamente il loro consenso, che può essere memorizzato su un cookie. Dovremo quindi subordinare il tracciamento dell'utente alla presenza di tale cookie, che possiamo verificare da httpContextAccessor.HttpContext.Request.Cookies.
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Utilizzare QuickGrid di Blazor con Entity Framework
Documentare i servizi REST con Swagger e OpenAPI con .NET 9
Utilizzare Locust con Azure Load Testing
Eseguire una ricerca avanzata per recuperare le issue di GitHub
Migliorare la scalabilità delle Azure Function con il Flex Consumption
Utilizzare il metodo IntersectBy per eseguire l'intersection di due liste
Creare una libreria CSS universale: Nav menu
Aggiornare a .NET 9 su Azure App Service
Il nuovo controllo Range di Blazor 9
La gestione della riconnessione al server di Blazor in .NET 9
Utilizzare un numero per gestire la concorrenza ottimistica con SQL Server ed Entity Framework
Proteggere le risorse Azure con private link e private endpoints
I più letti di oggi
- .NET Conference Italia 2018 - Milano
- Tutorial ASP.NET
- Seconda preview per i Dynamic Data Control 4.0
- Disponibile al download la versione finale di Mozilla Firefox 4
- Microsoft Security Bulletin MS02-044
- Shared Source Initiative per gli MVP
- Mono 1.1.10 per un po' di ASP.NET 2.0
- Webcast 'ASP.NET 2.0 HttpRuntime'