Quando iniziamo ad usare ASP.NET Identity migrando dalla tradizionale Membership API, potremmo accorgerci dell'assenza di alcune funzionalità accessorie ma utili, come il LastActivityDate, una proprietà del MembershipUser che ci consentiva di conoscere l'ultima data e ora in cui l'utente era stato visto online sulla nostra applicazione. Eventualmente, tale proprietà ci consentiva anche di determinare il numero totale di utenti connessi all'applicazione.
Anche se questa non è una funzionalità disponibile out-of-the-box in ASP.NET Identity, possiamo comunque implementarla con poco sforzo.
Da un'applicazione ASP.NET MVC, iniziamo aggiungendo la proprietà LastActivityDate alla classe ApplicationUser. Se abbiamo iniziato a costruire il progetto dal template offerto da Visual Studio 2013, troveremo questa classe definita nel file di codice /Models/IdentityModels.cs.
Contemporaneamente, aggiungiamo anche un metodo TrackActivity che useremo per aggiornare il valore di LastActivityDate in modo controllato, così da proteggerla da assegnazioni a valori arbitrari.
// La proprietà è nullable per ammettere il caso // di un utente che non abbia ancora effettuato alcun // accesso dopo la registrazione public DateTime? LastActivityDate { get; // il setter protected impedirà assegnazioni // arbitrarie dall'esterno della classe protected set; } public void TrackActivity() { LastActivityDate = DateTime.UtcNow; }
Il nostro scopo è quello di invocare il metodo TrackActivity ad ogni visita dell'utente. Il modo ideale per implementare questo tipo di requisiti non funzionali consiste nell'usare un action filter da configurare come filtro globale, così che vada in esecuzione contestualmente ad ogni action nel nostro progetto.
Aggiungiamo dunque la seguente classe all'interno di una nuova cartella /Filters.
public class UserActivityActionFilter : ActionFilterAttribute { // Questa logica andrà in esecuzione subito prima dell'action public override void OnActionExecuting(ActionExecutingContext filterContext) { // Otteniamo l'identità dell'utente dal contesto HTTP corrente IIdentity identity = filterContext.RequestContext .HttpContext.User.Identity; // Se si tratta di un utente anonimo, non lo tracciamo // e quindi terminiamo l'esecuzione immediatamente if (!identity.IsAuthenticated) return; // Otteniamo un riferimento allo user manager // dal contesto OWIN (configurato dal file /App_Start/Startup.Auth.cs) ApplicationUserManager userManager = filterContext.RequestContext.HttpContext.GetOwinContext() .GetUserManager<ApplicationUserManager>(); // Estraiamo l'utente cercandolo con il suo username // Nota: ASP.NET MVC 5 non supporta action filter asincroni, // quindi siamo costretti ad usare i metodi dello user manager // in maniera sincrona, cioè senza poterci avvalere // delle parole chiave async/await ApplicationUser user = userManager.FindByEmailAsync(identity.Name).Result; if (user != null) { // Finalmente, invochiamo il metodo TrackActivity user.TrackActivity(); // Persistiamo le modifiche. Ancora una volta, siamo costretti a // bloccare il thread corrente nell'attesa che // l'esecuzione del metodo asincrono sia completa. userManager.UpdateAsync(user).Wait(); } } }
Non resta che configurare il nostro UserActivityActionFilter come filtro globale. Apriamo il file /App_Start/FilterConfig.cs ed aggiungiamo la seguente istruzione al metodo RegisterGlobalFilters.
filters.Add(new UserActivityActionFilter());
A questo punto, l'applicazione è in grado di tracciare gli accessi degli utenti e di persisterne la data e l'ora. Questo significa che possiamo già rivolgere una query LINQ alla collezione Users dello user manager, per filtrare gli utenti in base al valore di LastActivityDate.
Tuttavia, volendo seguire un approccio più idiomatico, possiamo incapsulare la logica di filtraggio in un extension method, affinché sia facilmente riutilizzabile in vari punti dell'applicazione e resti comunque componibile con altre espressioni LINQ.
Aggiungiamo al progetto una nuova classe statica e definiamo al suo interno un extension method denominato, ad esempio, Online.
public static class UserExtensions { // Questo extension method è utilizzabile in tutte le // espressioni che restituiscono IQueryable<ApplicationUser>, // come ad esempio la collezione userManager.Users public static IQueryable<ApplicationUser> Online( this IQueryable<ApplicationUser> users, TimeSpan? timeout = null) { DateTime fromDate = DateTime.UtcNow.Subtract( timeout ?? TimeSpan.FromMinutes(20)); // Applichiamo un filtro in modo da ottenere solo gli // utenti per cui si è registrata attività da una certa data // in poi (configurabile mediante il parametro timeout // di questo metodo e per default impostato a 20 minuti) return users.Where(user => user.LastActivityDate > fromDate); } }
Ora siamo pronti ad utilizzare il nostro extension method da una qualsiasi action della nostra applicazione ASP.NET MVC.
// Otteniamo il riferimento allo user manager var userManager = HttpContext.GetOwinContext() .GetUserManager<ApplicationUserManager>(); // Elenchiamo gli utenti online List<ApplicationUser> onlineUsers = userManager.Users.Online().ToList(); // Oppure ne calcoliamo il totale int onlineUsersCount = userManager.Users.Online().Count();
In ultima analisi, siamo stati in grado di centralizzare la logica di tracciamento degli utenti grazie ad un action filter configurato globalmente. Inoltre, spostando la logica di filtraggio in un extension method abbiamo mantenere il codice di estrazione dei risultati estremamente leggibile e compatto.
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Supportare il sorting di dati tabellari in Blazor con QuickGrid
Estrarre dati randomici da una lista di oggetti in C#
Esportare ed analizzare le issue di GitHub con la CLI e GraphQL
Creazione di plugin per Tailwind CSS: espandere le Funzionalità del Framework
Utilizzare Azure AI Studio per testare i modelli AI
Disabilitare automaticamente un workflow di GitHub
Registrare servizi multipli tramite chiavi in ASP.NET Core 8
Usare un KeyedService di default in ASP.NET Core 8
Eseguire una query su SQL Azure tramite un workflow di GitHub
Effettuare il log delle chiamate a function di GPT in ASP.NET Web API
Sostituire la GitHub Action di login su private registry
Creare gruppi di client per Event Grid MQTT