Thursday, January 3, 2013

Display an online user count without a database or XML using the Global.asax in ASP.Net

Hack the Gibson

So here’s the idea, there are many websites on the web that show how many users are currently on the site.  The coolest one I ever saw, you could see an alias and which page that user was on.  Here we are going to keep it simple and then you can add to it to fit your needs.  The quick and dirty of it is that, at any given time there are many users on your site, or there could be none.  As a measure to gauge that (at a quick glance) you may want to see how many users are online.  This quick glance could be used to determine if you want to take your application offline for maintenance or such.  Plus it is always universally cool to display to other users that you have 500 users on your site while they are looking at it.
How do we accomplish this seemingly impossible task with ease?  Why with the Global.asax file of course.  The Global.asax file is a file that is not necessary for ASP.Net framework websites/applications to function properly, but allows the developer to add customized functionality to certain aspects of the site.  It is used to handle customized Application and Session level events and objects.
There are four events in the Global.asax that will make up our logic to display the number of current users on the site.  Before we go into that, we need to talk about the Application lifecycle as it pertains to this context.

An application is considered “started” when its app_pool is running.  This being the case, if you bounce a server, the web application will not start until the app_pool service for that particular website is up and running.  Session start will fire upon the first request from a client.  A session is considered to be of “state”, which means one party of the client/server relationship needs to retain a current “state” of the session.  In our case, both parties keep information based on session.  Upon the initial request from the server, the server will assign a session token to the client.  This is essentially an encrypted key.  As apart of the header communication between the two parties, the client will add this token so that the server knows from which client the communication came from.   However, the session information itself is stored on the server in the ASP.Net framework (which essentially equates to a version of SQL Lite that runs behind the scenes).  Thus, you can store session variables on the server and distribute them to the client on a “as-needed” basis. 
This is in direct contrast with “ViewState”.  ViewState is used to maintain Form level (or Page) information and is sent to the client in encrypted form with every page request after the Page has been rendered to the client.  There is also a security consideration when using ViewState, since it is bi-directional, it is acceptable to assume that it can be tampered with.  There is a ViewState validation in the Page lifecycle, however it is not 100% fool proof.
So in the hierarchy of the application:

  • Application variables are persistent for all instances of the application (through the app_pool).
  • Session variables are persistent for each session instance of the application.
  • ViewState are persistent for each Page (It is actually a little more complicated than that, but as a general rule…)

Now that we have an understanding of a global view of the application state, we understand why and how the Global.asax works. 

In our web application/site if we right click our solution name in the Solution Explorer and click Add New Item… We can add a Global Application Class (Global.asax).
Note: Once you add a Global.asax file to your solution, when you try to add a new item, it is no longer in the list.  Thus, you can only have one (single) Global.asax class per application/site.

Now that we have a Global.asax file apart of our solution, it will automagically open with the following templated events:

Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' Code that runs on application startup
End Sub

Application_End(ByVal sender As Object, ByVal e As EventArgs)
' Code that runs on application shutdown
End Sub

Application_Error(ByVal sender As Object, ByVal e As EventArgs)
' Code that runs when an unhandled error occurs
End Sub

Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' Code that runs when a new session is started
End Sub

Session_End(ByVal sender As Object, ByVal e As EventArgs)
' Code that runs when a session ends.
' Note: The Session_End event is raised only when the sessionstate mode
' is set to InProc in the Web.config file. If session mode is set to
' or SQLServer, the event is not raised.
End Sub

Now that we understand the different type of events, it is easy to understand where each event exists in the lifecycle of the application.  If you are still a little confused, Microsoft has templated comments into the events to give us a little help.

So thinking about what we are trying to accomplish, we need to create a variable that will persist against all instances of our application because we want a total count of users that are using the application.  So we’ll need to use the Application_Start event like so:

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

Here we are creating an Application level variable once the application is started.  Then we initialize the variable to be zero (we just started the application so there should be no users online).  If you do not create the variable and initialize it, you will end up having a NULL exception thrown as the variable is not in memory.

Now we need to increment the count.  Since every client is given a session token (regardless if they have logged in or not) we can increment the variable in the Session_Start event, like so:

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

When we call the Application.Lock method, it keeps any other instance of the application from updating the application variables until we unlock it again.  In certain instances, it is feasible to assume that you could get a “double count” and thus, have data integrity problems with the count. 
The Application(“UserOnlineCount”) += 1 is where we actually increment the number.  This could also be written as Application(“UserOnlineCount”) = Application(“UserOnlineCount”) + 1, thus incrementing the count by one per new session.   Then we perform some cleanup with unlocking the application variables.

This is a good start, but how about decrementing the count when a user is no longer online:

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

Obviously, we are performing the exact reverse action of the Session_Start here.  Now the Session_Timeout can either be set in the Application_Start, web.config, or IIS Configuration Manager (It is beyond the context of this article to set the Session_Timeout variable).  The Session_Timeout variable controls how long after the last request from the client the server will keep the current Session active.  This defaults to 15 minutes.  However, depending on the type of application/site you are developing this might need to be changed (for instance banking institutions usually set it to something like 5 minutes).

Now we need to clear the variable as clean up when the application stops, like so:

Sub Application_End(ByVal sender As Object, ByVal e As EventArgs)
Application("UserOnlineCount") = Nothing
End Sub

There are several reasons that the application will start/end.  Obviously, if you bounce the physical server.  Starting or stopping the HTTP service of the server, bouncing the specific app_pool, or making changes to the web.config. 

Now this is all special and everything, but we have to display this to the user still.  So next go to your Default.aspx page.  We can accomplish this many different ways.  I would suggest either one of the two following methods:

Directly referencing the variable:

<%= Application("UserOnlineCount").ToString() %> users online.

Or you can add a Server Control (Label or Literal) and populate its Text parameter in the Page_Load method (in code behind):

<asp:Label ID="lblOnlineCount" runat="server" />

(Code Behind):

Protected Sub Page_Load(sender As Object, e As System.EventArgs) _
Handles Me.Load
lblOnlineCount.Text = _
Application("UserOnlineCount").ToString() & " users online."
End Sub

And when rendered – gives us our user count:



Now you can see how Application level variables can be useful for displaying information.  It is also easy to see how we can manipulate the code to get the total number of users online and then break it down to which users are Members (using the .Net Framework).  We can further edit the code to show which users by user name/alias/handle are currently online.

But… I’ll leave that to ya’all to do….

Håþþ¥ .ñꆆïñg…

1 comment:

  1. `The Session_Start/Session_End way has the problem that Session_End is only called for "InProc" sessions, not if the sessions are stored in StateServer or

    Session_End solution for sessions stored in SqlServer?
    Performance with 4500 user in webapp, concurrent users maybe 500 or more?