How to Count Online Users While Using Session State Server (tutorial)

Share |

Here in this post I’ll show you how you to count online users while using state server or SQL server for session state. When you use state server you are not able to catch the session_end event on the global.asax, there for you may not be able to drop the user from you count!. I’ll show you a way to count users. the tutorial will be split  into 3 parts;

  1. Class creation.
  2. Web page side
  3. side

Part 1:

Create a Class named SessionChecker as below;

 

Code Snippet
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.Web;
  6.  
  7. public class SessionChecker
  8. {
  9.     private static Dictionary<int, DateTime> dictSession;
  10.  
  11.     public static Dictionary<int, DateTime> CreatSessionDictionary()
  12.     {
  13.         var objToLock = new Object();
  14.         var dictControl = HttpRuntime.Cache["UsersCountSession"] as Dictionary<int, DateTime>;
  15.         try
  16.         {
  17.             if (dictControl == null)
  18.             {
  19.                 lock (objToLock)
  20.                 {
  21.                     dictSession = new Dictionary<int, DateTime>();
  22.                     HttpRuntime.Cache.Insert("UsersCountSession", dictSession, null, DateTime.Now.AddMinutes(10100),
  23.                     System.Web.Caching.Cache.NoSlidingExpiration,
  24.                     System.Web.Caching.CacheItemPriority.NotRemovable, null);
  25.                    
  26.                 }
  27.             }
  28.         }
  29.         catch (Exception ex)
  30.         {
  31.             EventLog.WriteEntry("Application", "exception at CreatSessionDictionary: " + ex.Message, EventLogEntryType.Error);
  32.         }
  33.         return HttpRuntime.Cache["UsersCountSession"] as Dictionary<int, DateTime>;
  34.     }
  35.     public static void UpadteInsertSessionDictionary(int pUserId)
  36.     {
  37.         try
  38.         {
  39.             var objElseLock = new object();
  40.             var dic = HttpRuntime.Cache["UsersCountSession"] as Dictionary<int, DateTime>;
  41.             if (dic != null)
  42.             {
  43.  
  44.                 if (dic.ContainsKey(pUserId))
  45.                 {
  46.                     if ((DateTime.Now - dic[pUserId]).Minutes > 2) // to reduce the lock load
  47.                         lock (((IDictionary)dic).SyncRoot)
  48.                         {
  49.                             dic[pUserId] = DateTime.Now.AddMinutes(1);
  50.                         }
  51.                 }
  52.                 else // insert
  53.                 {
  54.                     lock (objElseLock)
  55.                     {
  56.                         dic.Add(pUserId, DateTime.Now.AddMinutes(1));
  57.                         HttpRuntime.Cache["UsersCountSession"] = dic;
  58.                     }
  59.                 }
  60.             }
  61.             else
  62.                 CreatSessionDictionary();
  63.         }
  64.         catch (Exception ex)
  65.         {
  66.             EventLog.WriteEntry("Application", "exception at UpadteInsertSessionDictionary: " + ex.Message, EventLogEntryType.Error);
  67.         }
  68.     }
  69.  
  70.     public static int GetOnlineUsersCount()
  71.     {
  72.         if (null != HttpRuntime.Cache["UsersCountSession"])
  73.         {
  74.             return (HttpRuntime.Cache["UsersCountSession"] as Dictionary<int, DateTime>).Count;
  75.         }
  76.         return 0;
  77.     }
  78. }

Here is the explanation of the class;

  • all methods in the class must be Static.
  • at line 9 create a dictionary that will hold the client Id and the login time of that client. ( here in my case i know that each user is registered in my Db and have got an ID, but you may use session ID for registered and anonymous users.)
  • at line 11 here is the method that will be entered once when the application started .
  • line 14 check if the dictionary object is available in the cache object or not. if its not available, a dictionary object is created and added to cache.( the cache here is 1 week cache, you may use unlimited cache).  You can see that there is a lock on dictionary create, that is to create just one instance of the dictionary if dictControl is null.
  • the second method UpadteInsertSessionDictionary is used to insert and update user. This method can be divided in to two parts;
        1- Update Part : this part starts from line 44. this part check if the dictionary contains the user id or not, if its true then the next step is  check the datetime of the user, i use this step to reduce the lock over head, because when the object is locked one user at the time can enter it. you may ask “why we should lock the dictionary object?” this is because dictionary is unsafe object in threading. So if DateTime.now - user time > 2 min the process enters the lock and updates the user time; which means that user is still online and navigating.(I’ll show you later how and where to implement and call this class on web page part) .
          2- Insert Part: if the dictionary does not contain the user id; the user id inserted into the dictionary the cache must be updated after that.
  • the methods GetOnlineUsersCount will return the user count any time you call it if the cache object is not null, otherwise user count will be 0;

Part 2:

       Web site part;

  • Inherit your web pages from one base page class like below;

 

Code Snippet
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Web;
  5. using System.Web.UI;
  6.  
  7. /// <summary>
  8. /// Summary description for WebBasepage
  9. /// </summary>
  10. public class WebBasepage : Page
  11. {
  12.     protected override void OnPreInit(EventArgs e)
  13.     {
  14.         base.OnPreInit(e);
  15.     }
  16.     protected override void OnLoad(EventArgs e)
  17.     {
  18.         SessionChecker.UpadteInsertSessionDictionary(1234);
  19.         base.OnLoad(e);
  20.     }
  21. }

 

  • in line 18 call you static method UpadteInsertSessionDictionary and pass the user id as parameter.
  • in this way you registered your user to the dictionary in cache.

Part 3;

      part is the most important part in this operation; you may check how to create the time from here, but here I’ll show you how to use the timer methods to continue the user count process.

the time class should be like this;

Code Snippet
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Configuration;
  4. using System.Diagnostics;
  5. using System.Linq;
  6. using System.Threading;
  7. using System.Web;
  8.  
  9.  
  10. public class GlobalTimer : IDisposable
  11. {
  12.     private static Timer timer;
  13.     private static int interval = 5 * 60000;
  14.     

  15.     public static void StartGlobalTimer()
  16.     {
  17.             if (null == timer)
  18.             {
  19.                 SessionChecker.CreatSessionDictionary();
  20.                 timer = new Timer(new TimerCallback(DropUsers), HttpContext.Current, 0, interval);
  21.  
  22.             }
  23.     }
  24.    
  25.     private static void DropUsers(object sender)
  26.     {
  27.         HttpContext context = (HttpContext)sender;
  28.         var dict = HttpRuntime.Cache["UsersCountSession"] as Dictionary<int, DateTime>;
  29.         if (dict != null)
  30.         {
  31.             if (dict.Count > 0)
  32.             {
  33.  
  34.                 try
  35.                 {
  36.                     var q = (from p in dict.AsQueryable()
  37.                              where (DateTime.Now - p.Value).Minutes >= 25
  38.                              select p).ToList();
  39.                     if (q.Count > 20)
  40.                     {
  41.                         q.ForEach(p => dict.Remove(p.Key));
  42.                         HttpRuntime.Cache["UsersCountSession"] = dict;
  43.                     }
  44.                 }
  45.                 catch (Exception ex)
  46.                 {
  47.                     EventLog.WriteEntry("Application", "exception at DropUsers: " + ex.Message, EventLogEntryType.Error);
  48.                 }
  49.             }
  50.         }
  51.         else
  52.             SessionChecker.CreatSessionDictionary(); // for some resone if the cache is not filled.
  53.     }
  54.  
  55.     #region IDisposable Members
  56.  
  57.     public void Dispose()
  58.     {
  59.         timer = null;
  60.     }
  61.  
  62.     #endregion
  63.  
  64. }

 

 

 

 

  • add this line to your global.asax at the application_start method
    Code Snippet
    1. void Application_Start(object sender, EventArgs e)
    2. {
    3.     GlobalTimer.StartGlobalTimer();
    4.  
    5. }

    in is way the timer will be started once as soon as the application start to work and well never stop!. Please read for more information about timer exceptions and IIS recycle.
  • Now the DropUsers method that is hooked with the timer tick event do this;
    checks if the cache is not null and the dictionary user count is bigger then 0, after that make a select statement to collect the users that datetime period is bigger then 25 minutes (which means that user did not navigate any page for more then 25 minutes).
  • after collecting inactive users; we drop them from the dictionary and reinsert dictionary to cache.
  • any exception could happen inside this process will be logged to event log.
  • call sessionchecker.GetOnlineUsersCount() to get the online user count any time you like.

 

Hereby this tutorial i tried to explain how to count users if you are using Session state server. I know that there is many ways to do that. This example is working very fine on an enterprise website which has more then 23000 users and more then 65000 daily logins.

Hope this helps

my twitter address changed: http://twitter.com/MuhanadY

5 Comments

Comments have been disabled for this content.