Tracciare gli utenti connessi con ASP.NET Identity

di Moreno Gentili, in ASP.NET Identity,

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

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi