List all active ASP.NET Sessions

2019-01-06 10:53发布

问题:

How can I list (and iterate through) all current ASP.NET sessions?

回答1:

You can collect data about sessions in global.asax events Session_Start and Session_End (only in in-proc settings):

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);
      }
  }

You should consider use some of concurrent collections to lower the synchronization overhead. ConcurrentBag or ConcurrentDictionary. Or ImmutableList

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

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



回答2:

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

How this works :

InProc session data is stored in the HttpRuntime’s internal cache in an implementation of ISessionStateItemCollection that implements ICollection. In this code, first of all i got CacheInternal Static Property of HttpRuntime class and then with the help of this object i got _entries private member which is of type ICollection. Then simply enumerate this dictionary and only take object of type System.Web.SessionState.InProcSessionState and finaly got SessionStateItemCollection for each user.

Summary :

In this article, I show you how you can get all current user Sessions. However one thing you will find when executing this code is that it will not show the current user Session which is set in the current request context because Session will be saved after all the Page Events...



回答3:

It does not seems right that there is not any class or method that provides this information. I think, it is a nice to have feature for SessionStateStoreProvider, to have a method which returns current active session, so that we don't have to actively track session life in session_start and session_end as mention by Jan Remunda.

Since I could not find any out of box method to get all session list, And I did not wanted to track session life as mentioned by Jan, I end up with this solution, which worked in my case.

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;
                }
            }
        }
    }
}


回答4:

I really like ajitdh's answer. Do upvote him. Here's another reference to that solution:

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

This got me close, but it failed to achieve my personal goal of finding the session for a particular session Id that I knew. So, for my purposes, I just added the sessionid as a session item (say Session["SessionId"] = session.SessionId on Session Start.) Then I just looked for a session with a matching value... I would have preferred to actually pull out this entry by indexing into one of the collections, but this did get it working at least.

Of course, this is all for In-Proc sessions, which I am indeed considering moving away from.

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:

Here's a WebForms/Identity example that gets a list of the logged on users active within the last 30 minutes.

The answer below works for a single web server, and the cache will be lost if the application restarts. If you want to persist the data and share it between servers in a web farm, this answer might be of interest.

In Global.asa.cs:

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

and

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);
        }
    }
}

The add these couple of 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";
        }
    }
}

Finally, in the page where you want to display the active users, display the results:

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


回答6:

I've been looking around for an equivalent of @ajitdh's answer for later versions of ASP.net - couldn't find anything, so thought I'd update this thread with solution for v4.6.2... Haven't tested with later versions of .net, but it doesn't work with v4.5. I am guessing it will be compatible with v4.6.1 onward.

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!
            }
        }
    }
}


回答7:

To the best of my knowledge, with standard in-memory sessions you cannot. This is something I was grappling with last week and I came to the determination that it isn't possible unless you are using a session state server. Seems rather odd from a design standpoint if you ask me. :/



回答8:

you can also try this as something basic, this is only so you can understand the concept

protected void Page_Load(object sender, EventArgs e)
    {
        if (Convert.ToInt32(Session["islogged"]) == 0)
        {
            Label1.Text = "you are not logged";
        }

    }

    protected void logged(object sender, EventArgs e)

/* add it to a button so the looged in button when it is clicked this will appear*/ {

            Label1.Text = "you are  Logged in "; 


    }


回答9:

Try the following code

 for (int i = 0; i < Session.Keys.Count - 1; i++)
        {
            Label1.Text += Session.Keys.Get(i) + " - " + Session[i].ToString()+"<br/>";
        }