Prerequisites
The code in this article uses Sessions in ASP.NET, so you'll need to have them enabled on your server, by configuring the
<sessionState> element in the
Web.config file. Refer to the References section at the end of this article for more details.
You'll also need to have access to a file called
Global.asax in the root of your site. If you run your own Web server, this is not a problem; you can simply create the file yourself. If you are using an ISP, you'll need to check with them if they support the use of the
Global.asax file as, unfortunately, not all ISPs allow this.
You'll also need to be able to write to a text file from within the
Global.asax file. This means that the account your Web site is running under (usually, the ASPNET account) needs sufficient permissions to write to the file.
Finally, this article assumes you're using Visual Studio .NET 2002 or 2003, as it shows code and ASPX pages using the Code Behind model. Don't worry if you don't have Visual Studio .NET; it should be relatively easy to use the code in your own Code Editor like Macromedia Dreamweaver MX or Notepad.
Counting Users
Just as in the article where the counters were stored in the
Global class, you can make use of the
Session_Start event, defined in the
Global.asax file to keep track of your users. This event is fired for every user whenever they request the first page in your Web site. This way, you have the ability to count each unique visitor only once during their visit. As long as the Session remains active on the server, the user won't be counted again. After the Session has timed out (it will automatically time out after a certain interval when no new pages have been requested) or has been explicitly ended, a request to a page will create a new Session,
Session_Start will fire and the user will be counted again.
To keep track of the total number of users that have visited your site since you started the Web server, you can increase a counter for each request a user makes. Let's call this counter
totalNumberOfUsers. You can store that counter in a static variable in the
Global class, so you can retrieve and display it on other pages in your site. You can also create a second counter, called
currentNumberOfUsers for example, that counts the number of active Sessions on your server. Just as with
totalNumberOfUsers, you increase the value of this counter whenever a new Session is started. However, you should decrease its value again when the Session ends. so you can keep track of the number of users that are currently visiting your site.
Besides storing these values in the
Global class, you can also write the value of
totalNumberOfUsers to a text file, so its values will survive server restarts. When your server is restarted, code in the
Global.asax file will read in the old value from the text file, so your counter will continue with the value it had before the restart. Don't underestimate the problem of restarting your Web server. Even if you have your stable Windows 2003 box running for months without a reboot, the new
IIS Process Recycling feature may restart your Web application every day or even every few hours.
You should start by making sure you have a file called
Global.asax (note that the extension is different from ordinary
aspx pages) in the root of your Web site. Usually, when you create a new Visual Studio ASP.NET Web Application, the
Global.asax file is already there, and the skeleton for important events like
Session_Start and
Session_End are already present in its Code Behind file.
If you don't have the file, open the Visual Studio .NET Solution Explorer (
Ctrl+Alt+L), right-click your Web project and choose
Add | Add New Item... from the context menu. Scroll down the list with Web Project Items until you see Global Application Class. Alternatively, expand Web Project Items and then click Utility to limit the list of Web items. Select the
Global Application Class and click
Open to add the
Global.asax file to your Web site project.
The page will open in Design View so you'll need to click the link "
click here to switch to code view" to view the Code Behind file for the
Global.asax file. You'll see some default
using statements, followed by the definition for the
Global class. I'll show you how to expand this class in the remainder of this article by adding a few private variables for the two hit counters. These private variables will then be made accessible through
public properties. Using properties instead of public fields helps keeping your code cleaner and more stable. Calling code is not allowed to just arbitrarily change the field's value; it has to change the value through a public
Set method. In this example, you'll make the code even a bit more safe by removing the
Set method altogether. This makes it impossible for calling code to change the value of the counter; all it can do is read its value.
Modifying Global.asax
- Locate the code that starts with public class Global : System.Web.HttpApplication and add the following shaded lines of code:
namespace HitCounters
{
/// <summary>
/// Summary description for Global.
/// </summary>
public class Global : System.Web.HttpApplication
{
private static int totalNumberOfUsers = 0;
private static int currentNumberOfUsers = 0;
/// <summary>
/// Required designer variable.
- The code that writes the counters to a file, makes use of the StreamWriter class. This class is located in the System.IO namespace, so you'll need to add a reference to this namespace by adding a using statement, at the top of the page somewhere below the other using statements:
using System.ComponentModel;
using System.Web;
using System.Web.SessionState;
using System.IO;
- The next step is to add code to the Session_Start event. This event will fire once when a user requests the first page in your Web application, so this place is perfect for your hit counter. Inside this event, the values of the two hit counters are increased; one for the total number of users and one for the current number of users. Once the values are increased, the value of totalNumberOfUsers is written to a text file.
Locate the skeleton for the Session_Start event and add the following code:
protected void Session_Start(Object sender, EventArgs e)
{
totalNumberOfUsers += 1;
currentNumberOfUsers += 1;
string counterFile = Server.MapPath("/Counter.txt");
if (File.Exists(counterFile))
{
File.Delete(counterFile);
}
StreamWriter sw = File.CreateText(counterFile);
sw.WriteLine ("Total\t" + totalNumberOfUsers);
sw.Close();
}
- Just as with the Session_Start event, you'll need to write some code for the Session_End event. However, instead of increasing the counters, you should decrease the counter for the current number of users only. This means that whenever a user Session times out (usually 20 minutes after they requested their last page), the counter will be decreased, so it accurately holds the number of current users on your site. You should leave the counter for the total number of users untouched.
Locate the Session_End event, and add this line of code:
protected void Session_End(Object sender, EventArgs e)
{
currentNumberOfUsers -= 1;
}
How It Works
The code that runs in the
Session_Start event performs a few important steps; first of all, the values for the two hit counters are increased. Next, the code checks whether the counter file is already present. If it is, it gets deleted by calling the static
Delete method of the
File class. Then a new text file is created and the value of the
totalNumberOfUsers is written to this new file, preceded by the word Total and a Tab character.
The code that runs in the Session_End event simply decreases the value of the current number of users. There is no need to touch the counter for the total number of users.
Making Your Counters Accessible by Other Pages
Since the hit counters are stored in the
Global class, you need some way to get them out of there, so you can display them on a management page for example. The easiest way to do this, is to create two public properties. Because the
Global class is in many respects just an ordinary class, it is easy to add public properties to it.
To add the properties, locate the skeleton for the
Application_End event, and add the following code just below it:
protected void Application_End(Object sender, EventArgs e)
{
}
public static int TotalNumberOfUsers
{
get
{
return totalNumberOfUsers;
}
}
public static int CurrentNumberOfUsers
{
get
{
return currentNumberOfUsers;
}
}
With these two read-only properties in place, your calling code is now able to access the values of your hit counters. For example, to retrieve the number of users browsing your site right now, you can use this code:
HitCounters.Global.CurrentNumberOfUsers where
HitCounters is the default namespace for your Web application as defined on the Property Pages for the Web project in Visual Studio .NET.
Reading the Counter When the Web Server Starts
As long as the Web server keeps running, this code will run fine. For each new Session that is created, the counters are increased by one and the value of
totalNumberOfUsers is written to the file. If, however, the Web server stops unexpectedly, or your restart or reboot the Web server yourself, the values for
totalNumberOfUsers and
currentNumberOfUsers are lost. But because the value for
totalNumberOfUsers has also been stored in a text file, it's easy to retrieve the counter again from that file when the Web server starts. To read in the value from the
Counter.txt file, you'll need to add some code to the
Application_Start event that is also defined in the
Global.asax file:
protected void Application_Start(Object sender, EventArgs e)
{
int counterValue = 0;
string counterFile = Server.MapPath("/Counter.txt");
if (File.Exists(counterFile))
{
using (StreamReader sr = new StreamReader(counterFile))
{
string result = sr.ReadLine();
if (result != null)
{
string delimStr = "\t";
char [] delimiter = delimStr.ToCharArray();
string [] arrResult = null;
arrResult = result.Split(delimiter);
if (arrResult.Length == 2)
{
counterValue = Convert.ToInt32(arrResult[1]);
}
}
}
}
totalNumberOfUsers = counterValue;
}
This code may look a bit complicated, but it's actually rather simple. A
StreamReader object (
sr) is used to read in a file. The StreamReader class defines a method
ReadLine to read in a file line by line. Since all you're interested in is the first line, this method is called just once. If the file is empty, this method returns
null, so you'll need to check for that. If line equals
null, the code in the
if block will not run, and the counter will get its default value of 0 (from the local variable
counterValue). If
ReadLine does return a string, the code in the if block
will run.
The string
result is then split into two elements by splitting on a Tab character (
\t). If you look at the code that writes to the file in the beginning of this article, you can see that the word Total, then a Tab and then the value of the counter are written to the file. Since all you're interested in is the value for the counter, you'll need to split the line in two elements; both sides of the Tab character. Because the
Split method of the String class requires a char array that defines the character to split on, you'll need the additional step of converting
delimStr to the array
delimiter which can then be passed to the
Split method of the string
result.
If the
Length of the array equals 2 you can be sure you read in a valid line. The second element,
arrResult[1], is then used as the value for the local variable counterValue, by converting it to an
int using
Convert.ToInt32.
At the end of the code block, the static variable
totalNumberOfUsers gets the value of counterValue, which holds either 0 or the value retrieved from the Counter file.
It would have been easier to leave out the TOTAL in the text file, so there is no need to split the line of text you retrieved from the file. I did include it in this example though, because it can be useful if you want to store multiple counters in the same text file. This way, you can save individual counters as TOTALUSERS, TOTALDOWNLOADS and so on all in the same file, while you can still see which value means what.
Testing it Out
To test out your hit counters, create a new Web form and call it
HitCounter.aspx. You can save the form anywhere in your site. In Design View, add two labels to the page and call them
lblTotalNumberOfUsers and
lblCurrentNumberOfUsers respectively. Add some descriptive text before the labels, so it's easy to see what value each label will display. You should end up with something similar to this in Code View:
<body>
<form id="frmHitCounter" method="post" runat="server">
Total number of users since the Web server started:
<asp:label id="lblTotalNumberOfUsers" runat="server"></asp:label><br />
Current number of users browsing the site:
<asp:label id="lblCurrentNumberOfUsers" runat="server"></asp:label><br />
</form>
</body>
Press
F7 to view the Code Behind for the hit counter page, and add the following code to the
Page_Load event:
private void Page_Load(object sender, System.EventArgs e)
{
int currentNumberOfUsers = HitCounters.Global.CurrentNumberOfUsers;
int totalNumberOfUsers = HitCounters.Global.TotalNumberOfUsers;
lblCurrentNumberOfUsers.Text = currentNumberOfUsers.ToString();
lblTotalNumberOfUsers.Text = totalNumberOfUsers.ToString();
}
The first two lines of code retrieve the total number of visitors and the current number of visitors from the
Global class. The next two lines simply display the values for the counters on the appropriate labels on the page.
To test it out, save the page and open it in your browser. You'll see there is one current user. Also, note that the total number of users is 1. Open another browser (don't use
Ctrl+N, but start a fresh instance or use an entirely different brand of browser) and open the counter page. You'll see there are two current users, and two users in total. Wait until both the Sessions have timed out (the default timeout defined in
Web.config is 20 minutes) and open the hit counter page again. You'll see there is one current user, but the total number of users has been maintained and should be 3.
Summary
This article demonstrated how to create a hit counter that keeps track of the current and total number of users to your site. It stores these counters in static variables in the
Global class so they are available in each page in your Web site. It also stores the value for the total number of users in a text file so its value won't be lost when the Web server restarts.
A disadvantage of using text files is that it's difficult to scale out your Web site to lots of users. Only one user has access to the file at any given moment, so if you have a real busy site, you'll soon run into problems.
Take a look at the following article to see how you can improve the counter page even further by using a database instead of a text file.
Related Articles
References
Download Files