web-dev-qa-db-fra.com

Liste de toutes les sessions ASP.NET actives

Comment répertorier (et parcourir) toutes les sessions ASP.NET en cours?

43
Alex

Vous pouvez collecter des données sur les sessions dans les événements global.asax Session_Start et Session_End (uniquement dans les paramètres in-proc):

private static readonly List<string> _sessions = new List<string>();
private static readonly object padlock = new object();

 public static List<string> Sessions
 {
       get
       {
            return _sessions;
       }
  }

  protected void Session_Start(object sender, EventArgs e)
  {
      lock (padlock)
      {
          _sessions.Add(Session.SessionID);
      }
  }
  protected void Session_End(object sender, EventArgs e)
  {
      lock (padlock)
      {
          _sessions.Remove(Session.SessionID);
      }
  }

Vous devez envisager d'utiliser certaines collections simultanées pour réduire la surcharge de synchronisation. ConcurrentBag ou ConcurrentDictionary. Ou ImmutableList

https://msdn.Microsoft.com/en-us/library/dd997373 (v = vs.110) .aspx

https://msdn.Microsoft.com/en-us/library/dn467185.aspx

34
Jan Remunda

http://weblogs.asp.net/imranbaloch/archive/2010/04/05/reading-all-users-session.aspx

Comment cela fonctionne:

Les données de session InProc sont stockées dans le cache interne de HttpRuntime dans une implémentation de ISessionStateItemCollection qui implémente ICollection. Dans ce code, tout d'abord, j'ai obtenu la propriété statique CacheInternal de la classe HttpRuntime, puis avec l'aide de cet objet, j'ai obtenu le membre privé _entries qui est de type ICollection. Énumérer ensuite simplement ce dictionnaire et ne prendre que des objets de type System.Web.SessionState.InProcSessionState et enfin obtenir SessionStateItemCollection pour chaque utilisateur.

Sommaire :

Dans cet article, je vous montre comment obtenir toutes les sessions utilisateur actuelles. Cependant, une chose que vous trouverez lors de l'exécution de ce code est qu'il n'affichera pas la session utilisateur actuelle qui est définie dans le contexte de demande actuel car la session sera enregistrée après tous les événements de page ...

19
user711040

Il ne semble pas juste qu'aucune classe ou méthode ne fournisse ces informations. Je pense qu'il est agréable d'avoir une fonctionnalité pour SessionStateStoreProvider, d'avoir une méthode qui renvoie la session active actuelle, de sorte que nous n'ayons pas à suivre activement la vie de la session dans session_start et session_end comme mentionné par Jan Remunda.

Comme je n'ai trouvé aucune méthode prête à l'emploi pour obtenir la liste de toutes les sessions, et que je ne voulais pas suivre la vie de la session comme mentionné par Jan, je me retrouve avec cette solution, qui fonctionnait dans mon cas.

public static IEnumerable<SessionStateItemCollection> GetActiveSessions()
{
    object obj = typeof(HttpRuntime).GetProperty("CacheInternal", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null, null);
    object[] obj2 = (object[])obj.GetType().GetField("_caches", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(obj);

    for (int i = 0; i < obj2.Length; i++)
    {
        Hashtable c2 = (Hashtable)obj2[i].GetType().GetField("_entries", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(obj2[i]);
        foreach (DictionaryEntry entry in c2)
        {
            object o1 = entry.Value.GetType().GetProperty("Value", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(entry.Value, null);
            if (o1.GetType().ToString() == "System.Web.SessionState.InProcSessionState")
            {
                SessionStateItemCollection sess = (SessionStateItemCollection)o1.GetType().GetField("_sessionItems", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(o1);
                if (sess != null)
                {
                    yield return sess;
                }
            }
        }
    }
}
18
ajitdh

J'aime vraiment la réponse d'Ajitdh. Faites-lui voter. Voici une autre référence à cette solution:

http://weblogs.asp.net/imranbaloch/reading-all-users-session

Cela m'a rapproché, mais cela n'a pas atteint mon objectif personnel de trouver la session pour un identifiant de session particulier que je connaissais. Donc, pour mes besoins, je viens d'ajouter le sessionid en tant qu'élément de session (disons Session ["SessionId"] = session.SessionId au démarrage de la session.) Ensuite, j'ai juste cherché une session avec une valeur correspondante ... J'aurais préféré pour réellement retirer cette entrée en l'indexant dans l'une des collections, mais cela l'a au moins fait fonctionner.

Bien sûr, c'est tout pour les sessions In-Proc, dont j'envisage effectivement de m'éloigner.

private static SessionStateItemCollection GetSession(string sessionId)
{
    object obj = typeof(HttpRuntime).GetProperty("CacheInternal", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null, null);
    object[] obj2 = (object[])obj.GetType().GetField("_caches", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(obj);

    for (int i = 0; i < obj2.Length; i++)
    {
        Hashtable c2 = (Hashtable)obj2[i].GetType().GetField("_entries", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(obj2[i]);
        foreach (DictionaryEntry entry in c2)
        {
            object o0 = entry.Value.GetType().GetProperty("Value", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(entry.Key, null);
            object o1 = entry.Value.GetType().GetProperty("Value", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(entry.Value, null);
            if (o1.GetType().ToString() == "System.Web.SessionState.InProcSessionState")
            {
                SessionStateItemCollection sess = (SessionStateItemCollection)o1.GetType().GetField("_sessionItems", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(o1);
                if (sess != null)
                {
                    if (sess["SessionId"] != null && ((string)sess["SessionId"]) == sessionId)
                    {
                        return sess;
                    }
                }
            }
        }
    }

    return null;
}
5
Greg

Voici un exemple WebForms/Identity qui obtient une liste des utilisateurs connectés actifs au cours des 30 dernières minutes.

La réponse ci-dessous fonctionne pour un seul serveur Web et le cache sera perdu si l'application redémarre. Si vous souhaitez conserver les données et les partager entre les serveurs d'une batterie de serveurs Web, cette réponse pourrait être intéressant.

Dans Global.asa.cs:

public static ActiveUsersCache ActiveUsersCache { get; } = new ActiveUsersCache();

et

protected void Application_PreRequestHandlerExecute(object sender, EventArgs e)
{
    if (User != null && User.Identity.IsAuthenticated)
    {
        // Only update when the request is for an .aspx page
        if (Context.Handler is System.Web.UI.Page)
        {
            ActiveUsersCache.AddOrUpdate(User.Identity.Name);
        }
    }
}

Ajoutez ces deux classes:

public class ActiveUsersCache
{
    private readonly object padlock = new object();
    private readonly Dictionary<string, DateTime> cache = new Dictionary<string, DateTime>();
    private DateTime lastCleanedAt;

    public int ActivePeriodInMinutes { get; } = 30;
    private const int MinutesBetweenCacheClean = 30;

    public List<ActiveUser> GetActiveUsers()
    {
        CleanCache();

        var result = new List<ActiveUser>();
        lock (padlock)
        {
            result.AddRange(cache.Select(activeUser => new ActiveUser {Name = activeUser.Key, LastActive = activeUser.Value}));
        }
        return result;
    }

    private void CleanCache()
    {
        lastCleanedAt = DateTime.Now;
        var cutoffTime = DateTime.Now - TimeSpan.FromMinutes(ActivePeriodInMinutes);
        lock (padlock)
        {
            var expiredNames = cache.Where(au => au.Value < cutoffTime).Select(au => au.Key).ToList();
            foreach (var name in expiredNames)
            {
                cache.Remove(name);
            }
        }
    }

    public void AddOrUpdate(string userName)
    {
        lock (padlock)
        {
            cache[userName] = DateTime.Now;
        }

        if (IsTimeForCacheCleaup())
        {
            CleanCache();
        }
    }

    private bool IsTimeForCacheCleaup()
    {
        return lastCleanedAt < DateTime.Now - TimeSpan.FromMinutes(MinutesBetweenCacheClean);
    }
}

public class ActiveUser
{
    public string Name { get; set; }

    public DateTime LastActive { get; set; }

    public string LastActiveDescription
    {
        get
        {
            var timeSpan = DateTime.Now - LastActive;
            if (timeSpan.Minutes == 0 && timeSpan.Seconds == 0)
                return "Just now";
            if (timeSpan.Minutes == 0)
                return timeSpan.Seconds + "s ago";
            return $"{timeSpan.Minutes}m  {timeSpan.Seconds}s ago";
        }
    }
}

Enfin, dans la page où vous souhaitez afficher les utilisateurs actifs, affichez les résultats:

UserRepeater.DataSource = Global
    .ActiveUsersCache.GetActiveUsers().OrderByDescending(u => u.LastActive);
UserRepeater.DataBind();
1
Phil Haselden

Je cherchais un équivalent de la réponse de @ ajitdh pour les versions ultérieures d'ASP.net - je n'ai rien trouvé, alors j'ai pensé mettre à jour ce fil avec la solution pour v4.6.2 ... Je n'ai pas testé avec plus tard versions de .net, mais cela ne fonctionne pas avec la v4.5. Je suppose qu'il sera compatible avec la v4.6.1 à partir de maintenant.

Cache cache = HttpRuntime.Cache;
MethodInfo method = typeof( System.Web.Caching.Cache ).GetMethod( "GetInternalCache", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy );

object objx = method.Invoke( cache, new object[] { false } );

FieldInfo field = objx.GetType().GetField( "_cacheInternal", BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Instance );
objx = field.GetValue( objx );

field = objx.GetType().GetField( "_cachesRefs", BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Instance );
objx = field.GetValue( objx );

IList cacherefs = ( (IList)objx );
foreach( object cacheref in cacherefs )
{
    PropertyInfo prop = cacheref.GetType().GetProperty( "Target", BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance );

    object y = prop.GetValue( cacheref );

    field = y.GetType().GetField( "_entries", BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Instance );

    Hashtable c2 = (Hashtable)field.GetValue( y );
    foreach( DictionaryEntry entry in c2 )
    {
        object o1 = entry.Value.GetType().GetProperty( "Value", BindingFlags.NonPublic | BindingFlags.Instance ).GetValue( entry.Value, null );

        if( o1.GetType().ToString() == "System.Web.SessionState.InProcSessionState" )
        {
            SessionStateItemCollection sess = (SessionStateItemCollection)o1.GetType().GetField( "_sessionItems", BindingFlags.NonPublic | BindingFlags.Instance ).GetValue( o1 );

            if( sess != null )
            {
                // Do your stuff with the session!
            }
        }
    }
}
1
Allumearz

À ma connaissance, avec les sessions en mémoire standard, vous ne pouvez pas. C'est quelque chose avec lequel je me débattais la semaine dernière et j'ai décidé que ce n'est pas possible à moins que vous n'utilisiez un serveur d'état de session. Cela semble plutôt étrange du point de vue du design si vous me demandez. : /

0
Nathan Taylor