Introduction
It is sometimes useful to display the number of visits a page on a Web site has experienced using a counter that's displayed as part of the page's content. While adding a counter to a page written in ASP or ASP.NET is relatively straight-forward, adding a counter to a plain HTML page (no scripting of any kind) is a little different.
This article describes a counter I created (see the following figure) using ASP.NET. The ASP.NET code generates an image using the Graphics
and Bitmap
classes, making it easy to use on plain HTML pages without client side scripting. The code also maintains its own little "database" so that you don't have to FTP to your Web server to create a new counter. I use and designed this counter for my eBay listings and various other pages; as a result, it's designed for speed so it does not use a database or XML (but it can easily be upgraded to do that).
Overview of JScript .NET
JScript .NET is one of the programming languages available on the .NET Framework and is Microsoft's version of ECMAScript - the language that's forms the basis of JavaScript. JScript .NET is very similar to C# and C++ in many ways, so if you know either language you'll immediately feel at home with JScript .NET.
JScript .NET is an object oriented language that supports classes, inheritance, polymorphism, and all other popular OO features. You can also use JScript .NET to create Windows Forms-based applications (applications that have a GUI), Windows Services, console applications, Web Services, and ASP.NET pages and applications.
Using the Counter
The following listing demonstrates how easily it is to use the counter with plain HTML:
<img src="EWCounter.aspx?item=CodeProjectArticle">
The HTML img
tag's src
attribute refers to an ASP.NET page that handles the details of retrieving the counter's current value from a file, updating the counter, and returning an image that contains, among other things, the value of the counter. You can add a link to the image, making the counter click-able, as shown in the following listing:
<a href="http://www.designs2solutions.com" target="_blank">
<img src="EWCounter.aspx?item=CodeProjectArticle" border="0">
</a>
Managing the Counter's Data
The counter keeps track of the value of a counter using simple text files that it accesses using the File
, FileStream
, StreamReader
, and StreamWriter
classes. Each file contains the value of a single counter creating a one-to-one relationship between a counter and its data file, making counter reads and updates fast. The following listing shows how the counter creates a new file for a counter that does not exist:
var itm :String = Request.QueryString("item");
createCounter(itm);
function createCounter(nameOfItem:String)
{
var sw : StreamWriter;
var filePath:String = Server.MapPath("data\\") + nameOfItem + ".txt";
if(!File.Exists(filePath)) {
sw=File.CreateText(filePath);
sw.WriteLine("99");
sw.Close();
}
}
The initial value of the counter is 99, to make it look like the first visitor to a page is the hundredth visitor (gotta be optimistic). To create a new counter, specify a new, unique value for the item
on the QueryString as shown in the preceding HTML code.
The code that retrieves and optionally updates the counter's value is straightforward. The code does not perform any random access in the file and simply reads the value using a StreamReader
and optionally writes the updated value using a StreamWriter
, as shown in the following listing:
function getCounterValue(nameOfItem : String, readOnly : Boolean) : String
{
var fileIn:StreamReader =
File.OpenText(Server.MapPath("data\\" + nameOfItem + ".txt"),
FileMode.Open);
var current : String = new String("");
current = fileIn.ReadLine().ToString();
fileIn.Close();
if(readOnly)
return current;
var curInt : int = Convert.ToInt32(current);
curInt++;
var fs : FileStream =
new FileStream(Server.MapPath("data\\" + nameOfItem + ".txt"),
FileMode.Open,FileAccess.Write);
var sw : StreamWriter;
sw = new StreamWriter(fs);
sw.WriteLine(Convert.ToString(curInt));
sw.Close();
fs.Close();
return Convert.ToString(curInt);
}
The code reads the current value of the counter using the StreamReader.ReadLine
method and optionally updates it using the StreamReader.WriteLine
method - simple and quick.
Rendering the Counter
The counter wouldn't be all that interesting if it's displayed as a simple text string, so the code uses the Bitmap
and Graphics
classes to create an in-memory representation of an image that's streamed directly to the client; as a result, the counter appears to be an image even though it's just s stream of bits coming that are sent to the client through the Response object.
The first thing the code does is create a Bitmap
object that's streamed to the client and manipulated using the Graphics
object, as shown in the following listing:
function drawCounter(nameOfItem : String, readOnly : Boolean)
{
var height : int = 20;
var width : int = 100;
var bmp : Bitmap= new Bitmap(width, height);
var g:Graphics = Graphics.FromImage(bmp);
g.FillRectangle(new SolidBrush(Color.DarkBlue), 0, 0, width, height);
g.FillRectangle(new SolidBrush(Color.LightGreen), 0, 0, 26, 20);
The preceding listing establishes the two objects it needs to work with and creates the dark blue box that the counter's value appears in, and the light green box on the left side of the counter that hosts the counter's logo.
The counter's logo is made up of three characters: "D2S". You can add text to a bitmap using the Graphics.DrawString
method. Before you do that, however, you need to establish what color to use to draw the text and the text's font and the last thing you need to do is position the text within the box, as shown in the following listing:
var maroonBrush:SolidBrush= new SolidBrush(Color.Maroon);
var blackBrush:SolidBrush= new SolidBrush(Color.Black);
var counterByFont:Font = new Font("Verdana", 7, FontStyle.Bold);
g.DrawString("D", counterByFont,blackBrush ,0,5);
g.DrawString("2", counterByFont,maroonBrush ,8,2);
g.DrawString("S", counterByFont,blackBrush ,16,5);
The logo's text was easy to add to the bitmap because its content does not change. The counter's value does change, so you need to pay a little more attention to how you position it within the enclosing rectangle. As in the preceding listing, the code uses the Font
and SolidBursh
objects as shown:
var counterFont:Font = new Font("Verdana", 8, FontStyle.Bold);
var whiteBrush:SolidBrush= new SolidBrush(Color.White);
When you position something on a bitmap, you place it by defining its location in terms of the top, left pixel of the object you want to draw. Since the counter's length can vary (as the value of the counter increases), the code measures the length of the counter's value as it appears in the enclosing box and positions it so that all of the numbers are visible, as shown in the following listing:
var counterText : SizeF;
counterText = g.MeasureString(counterValue,counterFont);
g.DrawString(counterValue, counterFont, whiteBrush ,
(bmp.Width)-((counterText.Width)+5),3);
There's a lot going on in that short listing: it begins by measuring the counter's value based on the font it will be rendered in using the Graphics.MeasureString
method and SizeF
object. The code then renders the string using the Graphics.DrawString
and positions the text to appear 3 pixels from the edge of the enclosing box.
With the counter ready to be presented, the last thing the code does is deliver it to the client in the form of a JPG image using the following line:
bmp.Save(Response.OutputStream, ImageFormat.Jpeg);
Limiting Counter Updates
The code you have seen so far isn't very selective when it comes to updating the counter: when a user refreshes a page, the counter gets updated. While this is okay in most cases, in some cases you want to ensure that you don't have users sitting around refreshing the page every few seconds to 'dope' the value of the counter (cause it to misstate the number of visitors by artificially inflating the value).
One solution to this problem is to use a cookie that expires after a set period of time. If the cookie hasn't expired, the counter's value does not get updated but it still gets displayed. Working with cookies in ASP.NET is relatively straight-forward as long as you're aware of the fact that an expired cookie is equivalent to a null
/ nonexistent cookie. The following listing shows the code that manages the cookie and sets the value of the readOnly
which determines if the counter is updated or just displayed:
var cookie:HttpCookie;
cookie = Request.Cookies("LastVisit");
var readOnly:Boolean = (cookie!=null);
drawCounter(itm,readOnly);
if(!readOnly) {
cookie = new HttpCookie("LastVisit",DateTime.Now.AddSeconds(120));
cookie.Expires = DateTime.Now.AddSeconds(60);
Response.AppendCookie(cookie);
}
The code queries for a cookie called "LastVisit" - if the cookie does not exist or has expired it is null
. After the counter is displayed and updated (depending on the value of readOnly
), the code again evaluates readOnly
to generate a new cookie, in case this is the user's first visit or if the cookie has expired. In this case, the code sets the cookie to expire in two minutes. Note that this solution may not work in all cases, depending on the user's browser security settings (users can configure their browsers to reject cookies from third party sites); however, this solution probably stops the majority of users from 'doping' the value of the counter.
Summary
This article described how to create a fast page hit counter that's easy to use with plain HTML. The code uses ASP.NET and the JScript .NET programming language and includes features like a logo, automatically creating new counters, and preventing users from artificially inflating the value of the counter.
Installing and Using the Sample Code
Use the following steps to install the sample:
- Download the code.
- Extract the code to a folder on your system.
- Use the IIS Admin Tool or the folder's properties to create a new virtual directory.
- Create a folder called data under the new virtual directory.
- Grant read, modify, and write permission to the IIS User account.
- Point your browser to the new virtual directory.
You should see a page that contains a short message and the counter image.