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
Controllare gli accessi IP alle app con Azure Container Apps
Ottenere il contenuto di una cartella FTP con la libreria FluentFTP
Utilizzare QuickGrid di Blazor con Entity Framework
Implementare il throttling in ASP.NET Core
Sfruttare i KeyedService in un'applicazione Blazor in .NET 8
Creare form tipizzati con Angular
Migrare una service connection a workload identity federation in Azure DevOps
Ottimizzare il mapping di liste di tipi semplici con Entity Framework Core
Eseguire query manipolando le liste contenute in un oggetto mappato verso una colonna JSON
Routing statico e PreRendering in una Blazor Web App
Utilizzare un service principal per accedere a Azure Container Registry
Inizializzare i container in Azure Container Apps
I più letti di oggi
- Utilizzare Docker Compose con Azure App Service
- Utilizzare QuickGrid di Blazor con Entity Framework
- Modernizzare le applicazioni WPF e Windows Forms con Blazor
- ASP 3 per esempi
- annunciato #netstandard 2.1. .NET Core lo supporterà a partire da #netcore3, così come le prossime versione di #xamarin, #mono e #unity.il supporto per #netfx 4.8, invece, non ci sarà. https://aspit.co/bq2