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
Effettuare il log delle chiamate a function di GPT in ASP.NET Web API
Ottimizzare il mapping di liste di tipi semplici con Entity Framework Core
Potenziare Azure AI Search con la ricerca vettoriale
Usare le navigation property in QuickGrid di Blazor
Autenticarsi in modo sicuro su Azure tramite GitHub Actions
Ottimizzare la latenza in Blazor 8 tramite InteractiveAuto render mode
Esporre i propri servizi applicativi con Semantic Kernel e ASP.NET Web API
Implementare l'infinite scroll con QuickGrid in Blazor Server
Eseguire query manipolando liste di tipi semplici con Entity Framework Core
Aggiornare a .NET 9 su Azure App Service
L'evoluzione di Blazor in .NET 8
Supportare il sorting di dati tabellari in Blazor con QuickGrid
I più letti di oggi
- pronti per #netconfit? ci vediamo domani alle 9:30 in #microsofthouse
- Introduzione al nuovo tipo TimeOnly di .NET
- Webcast 'Visual Web Developer Express 2005: costruire applicazioni con ASP.NET 2.0'
- Creare un overload del metodo Sum di LINQ che somma i TimeSpan
- Chiamare direttamente un numero di telefono con HTML5