Determining an 'active' user count of an ASP.NET site
Asked Answered
R

9

6

On an ASP.NET site, what are some techniques that could be used to track how many users are logged in to the site at any given point in time?

So for example, I could produce a report showing something like this:

        10:00am  11:00am  12:00pm  1:00pm  2:00pm ....
 3/25      25      32       45      40      37
 3/26      31      38       50      57      40
 3/27      28      37       46      35      20
 etc. 

EDIT: No, we're not using ASP.NET Membership provider, it uses a home-grown/hacky session based method of determining whether or not a user is in 'logged in' status.

Rawdan answered 26/3, 2009 at 14:12 Comment(0)
D
9

Does the website log the user in? If it does, then you can update a "Last Visit" field in the user table every time they request a new page, and then every hour, just do a SQL query that grabs everybody who has a "Last Visit" timestamp within the last 15 minutes or so (assumed to currently be on the site).

If you're not having people log in, you could just as easily do it by IP address instead of username. With this method, though, you may run into some proxy issues (ie multiple users from the same corporate network may all come from a single IP address, so they only count as one user in your totals), but that should be minimal.

Dipteran answered 26/3, 2009 at 14:24 Comment(2)
And, if the site is public-facing, there's the big proxies like AOL and the military.Elva
Adding a 'Last Visit' field an updating it frequently is probably the approach we'll end up taking for this project. Thanks for the idea.Rawdan
C
3

This sort of depends on your site. If your using the ASP.Net Membership Providers there is a method: Membership.GetNumberOfUsersOnline() which can tell you how many logged in users there are. I believe there are also performance counters. The notion of a logged in user is a user who did something within the last x minutes where x is configurable.

You could also use performance counters to track incoming requests if you want to get a sense of how much activity there is.

Edit

Just so you know the SQL ASP Membership providers implements this by recording an activity date on a field in the DB. It when just queries it for all activity within x minutes.

I added a client side polling function which hits our server every 2 minutes, so while a user is sitting on the page I know they are there even if there is no activity. This also let me force the user out of the system, provides a method deliver other system messages. Kind of nice.

Cyrillic answered 26/3, 2009 at 14:23 Comment(3)
This is good info, so I'll +1 you, but for my purposes the idea rwmnau provided is a better fit for my particular scenario.Rawdan
I'll +1 as well - I hadn't considered something that polls client side to actually see if they're still around. I've always found that most recent page refresh is a pretty reliable indicator, but if you've got the bandwidth to burn, this method is even more accurate.Dipteran
My poller actually is method to limit simultaneous logins...last person in stays in scenario. We have a per user licensing scheme for companies...kind of cool...Cyrillic
P
3

If using SQL Server for Session storage (i.e. <sessionState> mode is "SQLServer" in web.config), you can't use a solution that relies on Session_End in global.asax, as the method will never be called.

However, assuming that SQL Agent is running the DeleteExpiredSessions job correctly (which it should be by default when SQL State was installed), you can just run the following SQL against the Session Db:

SELECT COUNT(SessionId) FROM ASPStateTempSessions
Psychic answered 27/5, 2010 at 13:59 Comment(1)
What is SessionId ? is HttpContext.Current.Session.SessionID value ?Radicand
S
3

On Global.asax

protected void Application_Start(object sender, EventArgs e)
        {
            Application["SessionCount"] = 0;
        }

        protected void Session_Start(object sender, EventArgs e)
        {
            Application.Lock();
            Application["SessionCount"] = Convert.ToInt32(Application["SessionCount"]) + 1;
            Application.UnLock();
        }

        protected void Session_End(object sender, EventArgs e)
        {
            Application.Lock();
            Application["SessionCount"] = Convert.ToInt32(Application["SessionCount"]) - 1;
            Application.UnLock();
        }

Get Application["SessionCount"] on the page you want

Survey answered 16/12, 2016 at 20:55 Comment(2)
Application.Lock/UnLock is quite important here indeed, since sessions may be created/terminated in parallelCassandry
btw, in ASP.net Razor can access it via @HttpContext.Current.Application["SessionCount"]Cassandry
G
1

I think what I've used in the past was the Global.asax functions mainly centering around the Session_Start and Session_End, increasing a count with the Start, and then the end is a bit tricky because of the session timeout. If you are not concerned a lot with how exactly accurate the count is then you can stop here.

If not then you'd probably use a combination of the javascript onUnload event with some sort of ajax request to invalidate the session and subtract the user. The event would have to see if the user was actually leaving the page or just going to another page on the site.

Anyway, start there. I remember having to do with ASP sites so there is definately some information out there on this method.

Gillies answered 26/3, 2009 at 15:20 Comment(0)
W
1

There are performance monitor stats for Sessions Active within the ASP.NET performance objects, and you can track all instances, or individual apps. You can access these stats through Admin ToolsPerformance, or programmatically via WMI.

A very basic PowerShell script to get such a total of such counters:

(Get-WmiObject Win32_PerfRawData_ASPNET_ASPNETApplications SessionsActive).SessionsActive

A filter should be able to get the stat for a specific site.

Wither answered 26/3, 2009 at 15:30 Comment(1)
Any sample code using programmatically via WMI to performance monitor ?Radicand
O
0

First set Session timeout for 1 minute.

Create a simple heartbeat.aspx page with no HTML content just the following javascript code:

<html>
  <head>
  <script language="javascript">
  function reloadPage()
  {
    window.navigate("heartbeat.aspx");
  }
  </script>
  </head>
<body onload="window. setTimeout(‘reloadPage()’, 30000)">
</body>
</html>

This will re-request itself every 30 seconds and keep session alive.

Put heatbeat.aspx page in a hidden frame.

To get user count just get session count by using static counter in Session_Start and Session_End events in Global.asax.

Overtly answered 26/3, 2009 at 18:15 Comment(1)
Why not just have an asynchornous client side request (AJAX) hit a service on the back or an ashx...why have yet another frame.Cyrillic
D
0

If you are using InProc session state, you can do it like this in global.asax

Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
    Application("ActiveSessionCount") = 0
End Sub

Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
    Application("ActiveSessionCount") += 1
End Sub

Sub Session_End(ByVal sender As Object, ByVal e As EventArgs)
    Application("ActiveSessionCount") -= 1
End Sub

To access the value on a web form is just as simple

Dim Count as Integer = Application("ActiveSessionCount")
Dunford answered 26/3, 2009 at 18:22 Comment(4)
i have tried this and on every refresh the counter is incrementingHunan
if you open a new tab the counter should increment, on refresh probably depends on the browser. If you close new tabs, after a while it will show the counter decremented if you visitCassandry
Note that += and -= are not atomic operations (at least on VB.net have had a discussion on that with the dev team on MS Connect years back, but think on C# it should be the same). Not sure if they produce any faster code at all also, mostly useful for readability and to avoid typos, rename stuff in one place etc. So you need to still lock around the += and -=, since they're two operations and other thread can work on the same data at the same time between read, increment/decrement op and write back (data race). Could use Application.Lock/Application.UnLock, see other reply aboveCassandry
btw, the Application default/indexed property is a dictionary, thus it contains Objects, not Integers, so if you use Option Strict (support.microsoft.com/en-au/help/311329/…) which is best practice in my opinion, you should do conversion to Integer, for example via Convert.ToInt32 like the C# example in other answer above (in which case you won't be able to use += and -=, unless you first cast to Integer that is)Cassandry
B
0
public class ActiveUsers
{
    private static List<LastUserActivity> users;
    private static object syncroot = new object();
    private static DateTime lastPruned;

    public static int SessionLength = 20;

    static ActiveUsers()
    {
        users = new List<LastUserActivity>();
        lastPruned = DateTime.UtcNow;
    }

    public void RecordUserActivity(int userId)
    {
        lock (syncroot)
        {
            var user = users.FirstOrDefault(x => x.UserId == userId);
            if (user == null)
            {
                user = new LastUserActivity() { UserId = userId };
                users.Add(user);
            }
            user.UtcTime = DateTime.UtcNow;

            if (lastPruned.AddMinutes(5) < DateTime.UtcNow)
            {
                Prune();
                lastPruned = DateTime.UtcNow;
            }
        }
    }

    private static void Prune()
    {
        users.RemoveAll(x => x.UtcTime.AddMinutes(SessionLength) < DateTime.UtcNow);
    }

    public int GetActiveUsers()
    {
        return users.Count;
    }
}
public class LastUserActivity
{
    public int UserId { get; set; }
    public DateTime UtcTime { get; set; }
}

Add a call to ActiveUsers into a method in global.asax (eg. BeginRequest, AcquireRequestState).

Brewhouse answered 26/11, 2016 at 19:41 Comment(2)
why do you need a lock(syncroot) just to read a value (users.Count) in GetActiveUsers?Cassandry
You are right, the lock in GetActiveUsers is not needed, I've updated the answer.Brewhouse

© 2022 - 2024 — McMap. All rights reserved.