Howto Create a Hit Counter Using a Text File in ASP.NET 1.x

If you have a live Web site on the World Wide Web, you may be interested in how many people are visiting your site. You can of course analyze the log files of your Web server but that information is usually difficult to read. The log files contain information for each and every request a visitor has made to your site, including resources like images, Flash movies and so on. This makes it near impossible to extract information about individual users. It would be a lot easier if you could count the number of individual users that have visited you since you started your site. It would also be useful if you could see the number of users that are currently browsing your site.

This article will show you how to accomplish these two tasks by storing the hit counters in static variables in the Global class and in a text file using code in the Global.asax file. This article extends the ideas from a previous article where the values of the counters were just stored in static variables. By writing the counters to a file you can maintain their values, even when you restart the Web server.

There is also a Classic ASP version of this article available.

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


  1. 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.
  1. 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;
  1. 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();
}
  1. 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


Where to Next?

Wonder where to go next? You can read existing comments below or you can post a comment yourself on this article .


Consider making a donation
Please consider making a donation using PayPal. Your donation helps me to pay the bills so I can keep running Imar.Spaanjaars.Com, providing fresh content as often as possible.



Feedback by Other Visitors of Imar.Spaanjaars.Com

On Monday, November 08, 2004 5:39:13 AM tavor said:
yeah,that's a good idea.
but i have some trouble. i use the web.config instead of a text file.
when i read the count use the bellow code:
in application_start
int count=int.Parse(ConfigurationSettings.AppSettings["SiteCount"]);
Application["site_count"]=count;
in session_start,i wrote these codes:

Application.Lock();
Application["site_count"]=(int)Application["site_count"]+1;
Application.UnLock();

in application_end,i wrote these codes:

string xmlPath=Server.MapPath("web.config");;
DataSet ds=new DataSet();
try
{
ds.ReadXml(xmlPath);
}
catch
{
}
if(ds.Tables.Count>1&&ds.Tables[1].Rows.Count>9&&ds.Tables[1].Rows[9][0].ToString()=="SiteCount")
{
Application.Lock();
ds.Tables[1].Rows[9][1]=Application["site_count"];
Application.UnLock();
ds.AcceptChanges();
try
{
ds.WriteXml(xmlPath);
}
catch{}
}
ds.Clear();

but when i close the iis web server,it can't refresh the Web.config file.

i'm hoping your reply,thank you very much!
On Wednesday, March 22, 2006 9:30:13 PM rg said:
one of the best articles i have read so far, especially for beginners :)
On Wednesday, March 22, 2006 9:41:52 PM Imar Spaanjaars said:
Hi rg,

Thanks. Spread the word, spread the word.... ;-)

Imar
On Friday, August 11, 2006 8:51:37 AM Budi said:
it's nice
but I want write this script wit vb, how can I do???
On Friday, August 11, 2006 8:58:45 AM Imar Spaanjaars said:
Hi Budi,

Take a look here: http://www.spaanjaars.com/QuickDocId.aspx?quickdoc=261

This article is in VB and is closely related to this one.

Cheers,

Imar
On Tuesday, September 05, 2006 8:47:44 AM Klugscheisser said:
Replace: Server.MapPath("/Counter.txt");
With:      Server.MapPath("./Counter.txt");

On Friday, September 15, 2006 2:07:12 PM Imar Spaanjaars said:
Hi Klugscheisser,

The syntax you need to use depends on where the file is located. /Counter.txt points to a file in the root, while ./Counter doesn't always necessarily do that, depending on the location of the calling code. So, if your counter file is located in the root, you're better off using "/Counter.txt".

Cheers,

Imar
On Tuesday, May 29, 2007 2:56:59 PM motemape said:
Hei,

I am struggling to integrate this code into my ASP.Net 2.0 project.

Where do we find the Namespace page property of the project?
In most of my files, I have like:

namespace KT.DataNet.UI
{

and

namespace KT.DataNet
{


When trying to run the Hitcounter.aspx, I get the following error:
Error 1 Could not load type 'KT.DataNet.UI.Global'.
On Tuesday, May 29, 2007 7:20:59 PM Imar Spaanjaars said:
Hi motemape,

This code is specific to ASP.NET 1.x as the title suggests.

In .NET 2, the code you write in the Global.asax should be available in other code by default, provided you write it in the same namespace or import the correct namespace.

Imar
On Saturday, June 23, 2007 12:17:54 PM Deepak said:
This is best idea for Hit Counter .
On Friday, May 30, 2008 8:41:23 AM raghav said:
hi this is an article which is very helpful to me thanx
On Tuesday, October 13, 2015 11:45:36 PM Ajodo Godsown said:
Good day sir! I can't Locate the code that starts with public class Global : System.Web.HttpApplication in my global.asax, am using visual studio 2012 and where should i insert my namespace hit counter. thanks!!
On Wednesday, October 14, 2015 6:52:44 AM Imar Spaanjaars said:
Hi there,

This article is 12 years old and deals with ASP.NET 1.1, so a lot of stuff has changed.

That said, if you create a Web application in Visual Studio you should have (or add) a file called Global.asax where you can add this code.

Cheers,

Imar

Talk Back! Comment on Imar.Spaanjaars.Com

I am interested in what you have to say about this article. Feel free to post any comments, remarks or questions you may have about this article. The Talk Back feature is not meant for technical questions that are not directly related to this article. So, a post like "Hey, can you tell me how I can upload files to a MySQL database in PHP?" is likely to be removed. Also spam and unrealistic job offers will be deleted immediately.

When you post a comment, you have to provide your name and the comment. Your e-mail address is optional and you only need to provide it if you want me to contact you. It will not be displayed along with your comment. I got sick and tired of the comment spam I was receiving, so I have protected this page with a simple calculation exercise. This means that if you want to leave a comment, you'll need to complete the calculation before you hit the Post Comment button.

If you want to object to a comment made by another visitor, be sure to contact me and I'll look into it ASAP. Don't forget to mention the page link, or the QuickDocId of the document.

For more information about the Talk Back feature, check out this news item.