The Task
For many portal websites, they always host one or two newsletters for their
users. Oftentimes, when they send out their newsletter, they want to be able
know how many users have opened and read the newsletter and where they are from.
The Solution
To accomplish this task, we embed an image call into the newsletter’s HTML so
that every time when the user opens the newsletter, an image call is made and
the server is notified. On the server side, we set up an HttpHandler to parse
the image call in order to collect the information from the user as well as to
end the call by generating a transparent beacon image.
Step 1. Decide What to Embed In the Image Call
In our example we will embed the NewsletterID and UserID into the image call,
and a typical beacon image call in our example will be like:
<img src=”http://the-portal-site/Newsletter/NewsletterID.UserID.open”
height=1 width=1>
So if we have NewsletterID as 234 and UserID as 123, the image tag will be:
<img src=”http://the-portal-site/Newsletter/234.123.open”
height=1 width=1>
If you notice, here we use the extension ‘.open’. This is to make ourselves
convenient when setting up server-side URL mapping (see step 5).
Step 2. Create an Http Handler by Implementing IHttpHandler Interface
The IHttpHandler interface defines the contract that ASP.NET implements to
synchronously process HTTP Web requests using custom HTTP handlers. By
implementing the IHTTPHandler interface, our custom handler will be able to
process the incoming request just like ISAPI extension filters but with a
simpler programming model.
using System;
using System.Web;
namespace EmailTracking
{
/// <summary>
/// Summary description for OpenHandler.
/// </summary>
public class OpenHandler : IHttpHandler
{
// Specify the location of the beacon image
private string pixelFile = "~/Images/pixel.gif";
// Constructor
public OpenHandler()
{}
// Process the request
public void ProcessRequest(HttpContext context)
{
OpenURIParser.Process(
context.Request.Path,
context.Request["REMOTE_ADDR"]);
// Output image
context.Response.ContentType = "image/gif";
context.Response.WriteFile(
context.Request.MapPath( pixelFile ) );
context.Response.End();
}
// This property is used to determine whether an instance
// of the handler can be reused across multiple requests.
public bool IsReusable
{
get { return true; }
}
}
}
Step 3. Create a URL Parser Class
This is just a simple parsing class to get the NewsletterID, UserID and IP
information from the request. We can then pass the information along to the next
layer (database or file processing to record the information).
using System;
using System.IO;
namespace EmailTracking
{
/// <summary>
/// Summary description for OpenURIParser.
/// </summary>
public class OpenURIParser
{
// Constructor
public OpenURIParser()
{}
// Do the parsing
public static void Process( string pUri, string pIP)
{
string[] arrIDs;
string country;
// Create a CountryLookUp instance by specifying
// the location of the GeoIP database file
CountryLookUp countryLookUp =
new CountryLookUp(@"C:\winnt\system32\GeoIP.dat");
try
{
// a valid call will be like:
// "/NewsletterID.UserID.open"
arrIDs = GetReqeustedFileName(pUri).Split('.');
if (arrIDs.Length == 2)
{
// Get the NewsletterID
int newsletterID = Convert.ToInt32(arrIDs[0]);
// Get the UserID
int userID = Convert.ToInt32(arrIDs[1]);
// Get the user’s origin by his IP
country = CountryLookUp. lookupCountryCode(pIP);
// Write open stats either to database or file
// You need to write your own Stats class
Stats.UpdateOpenStats(
newsletterID,
userID,
countryByIP);
}
}
catch (Exception)
{
// Failure in parsing request
// You can write your error handling here
}
}
// Get the file name from the request url
private static string GetReqeustedFileName(string pUri)
{
return Path.GetFileNameWithoutExtension(pUri);
}
}
}
Step 4. Create a CountryLookUp Class
By utilizing MaxMind’s GeoIP database, we can obtain the Country (as mentioned
on their site, they can obtain Region, City, Latitude, and Longitude as well) of
any IP address. This will be very helpful if we want to geo-target newsletters.
The C# API which I have ported can be downloaded at
http://www.maxmind.com/app/csharp.
using System;
using System.IO;
using System.Net;
namespace EmailTracking
{
/// <summary>
/// Summary description for CountryLookup.
/// </summary>
public class CountryLookup
{
private FileStream fileInput;
private static long COUNTRY_BEGIN = 16776960;
....
public CountryLookup(string fileName)
{
fileInput = new FileStream(
fileName, FileMode.Open, FileAccess.Read);
}
public string lookupCountryCode(string str)
{
IPAddress addr;
try
{
addr = IPAddress.Parse(str);
}
catch (FormatException e)
{
return "--";
}
return lookupCountryCode(addr);
}
....
public string lookupCountryCode(IPAddress addr)
{
....
}
....
}
}
Step 5. Register Your Custom Http Handler
Once you have your custom handler application compiled, all you are left to do
is to register your handler with IIS and ASP.NET runtime.
First, add the following entries to your appplication’s web.config file:
<system.web>
<httpHandlers>
<add verb="*" path="*.open"
type="EmailTracking. OpenHandler, EmailTracking" />
</httpHandlers>
</system.web>
This configuration tells the ASP.NET to handle requests with extension .open
using our custom http handler.
The next step is to map the .open extension on IIS. Because IIS is the first
place the user’s request will go, we need to config IIS to pass the request with
.open extension to ASP.NET so then the request can reach our http handler.
Open the IIS snap-in and go to the application where you set up to host the
tracking system and open the properties of this application. On the Directory
tab, click the Configuration.