Web Referral Tracking User Control

In my efforts to continue building my personal web sites, I wanted to see where my visitors are coming from and how they are getting to my web site.  Unfortunately, my ISP doesn't provide me with any kind of stats or access to the logs, so I'm kind of screwed there.  So, I decided to take things into my own hands, and wrote a user control that has relatively little UI (and even then it's easy to disable). 

My solution uses an XML file in the app_data directory.  I chose this as it was pretty easy to test and write, but I found out when I went to deploy it, my ISP won't let me write to files in my app_data directory either.  The solution I ended up using was a SQL one, but for now, I'm going to post the XML file version.  There are some issues with this, it does lock the file while the data is updated, so you run the risk of slowing your website down.  I took this as an acceptable side effect on my website as I don't get a lot of hits, and if it takes an extra second or two to load, people can deal.

<%@ Control Language="C#" ClassName="ReferralTracker" %>
<%@ Import Namespace="System.Data" %>

<script runat="server">
public bool ShowAtRuntime = false;

protected void Page_Load(object sender, EventArgs e)
//The page that was requested
string PageID = Request.RawUrl;
//Where the user came from
string Referrer = "";

//If there is no referring page, Request.UrlReferrer is null
if (Request.UrlReferrer != null)
Referrer = Request.UrlReferrer.ToString();
lblReferral.Text = "Referred By: " + Referrer;
if (ShowAtRuntime)
lblReferral.Visible = true;
string XsdFile = Server.MapPath("~") + "\\App_Data\\referral.xsd";
string XmlFile = Server.MapPath("~") + "\\App_Data\\referrals.xml";

object fileLock = new object();

System.Diagnostics.Debug.WriteLine("Referral: " + Referrer);

//Lock the code so we don't have to worry about data loss
lock (fileLock)
DataSet dsData = new DataSet();
catch (System.IO.FileNotFoundException ex)
System.Diagnostics.Debug.WriteLine("Creating file");

DataTable dt = dsData.Tables[0];
string[] keys = new string[2];
if (Referrer != null)
keys[0] = Referrer.ToLower();
keys[0] = "";
keys[1] = PageID;
//See if this request has ever been hit before
DataRow dr = dt.Rows.Find(keys);
if (dr != null)
//It's been hit before, so lets increment the counter
long iHits = (Int64)dr["Hits"];
dr["Hits"] = ++iHits;
dr["LastDateHit"] = DateTime.Now.ToString();
//This is a new hit, lets add the record
dr = dt.NewRow();
if (Referrer != null)
dr["Referrer"] = Referrer.ToLower();
dr["Referrer"] = "";
dr["Hits"] = 1;
dr["Page"] = PageID;
dr["FirstDateHit"] = DateTime.Now.ToString();
dr["LastDateHit"] = DateTime.Now.ToString();

<asp:Label ID="lblReferral" runat="server" Visible="false" />

I've put in a few comments so you can understand what I did.  It does depend on an XSD file to set things up, which is below:

<?xml version="1.0" standalone="yes"?>
<xs:schema id="NewDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xs:element name="NewDataSet" msdata:IsDataSet="true">
<xs:choice maxOccurs="unbounded">
<xs:element name="Referrals" msdata:CaseSensitive="False">
<xs:element name="Referrer" type="xs:string" />
<xs:element name="ID" type="xs:string" />
<xs:element name="Hits" type="xs:string" minOccurs="0" />
<xs:element name="FirstDateHit" type="xs:string" minOccurs="0" />
<xs:element name="LastDateHit" type="xs:string" minOccurs="0" />
<xs:unique name="Constraint1" msdata:PrimaryKey="true">
<xs:selector xpath=".//Referrals" />
<xs:field xpath="Referrer" />
<xs:field xpath="ID" />

The solution I ended up using gets the data from an SQL database instead of an XML file, but it still does the same thing where it reads from the table to find out if the hit has happened before, so it can then increase the count, or if it's a fresh hit, it'll just add a new entry to the table.

Comments (2)

  1. Oleg says:

    Your lock doesn’t lock anything. lock(this) instead or better lock the file operations.

  2. Jeff Parker says:

    Hey Pete, good post.

    If you are ever worried about performance on this though and speed an option would be to put all this in an httpHandler that has another thread running in the background for all this. I got the idea from this msdn article tip number 6 http://msdn.microsoft.com/msdnmag/issues/05/01/ASPNETPerformance/

    But basically you run all the logging emailing and stuff in another thread running in the background on the asp.net site.

Skip to main content