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
Ospitare n8n su Azure App Service
Definire il metodo di rilascio in .NET Aspire
Il nuovo controllo Range di Blazor 9
Integrazione di Copilot in .NET Aspire
Evidenziare una porzione di testo in un pagina dopo una navigazione
Montare Azure Blob Storage su Linux con BlobFuse2
Utilizzare i variable font nel CSS
Arricchire l'interfaccia di .NET Aspire
Effettuare un clone parziale di un repository di GitHub
Gestire progetti .NET + React in .NET Aspire
Ridimensionamento automatico input tramite CSS
I più letti di oggi
- Effettuare il multi-checkout in linea nelle pipeline di Azure DevOps
- Sfruttare una CDN con i bundle di ASP.NET
- Alleggerire le applicazioni WPF sfruttando gli oggetti Freezable
- Esaminare documenti XML con namespace utilizzando LINQ to XML
- Le DirectInk API nella Universal Windows Platform
- Gli oggetti CallOut di Expression Blend 4.0
- Inserire le news di Punto Informatico nel proprio sito


