When building the ASP.NET web site for CALM [did you notice the permalink?], I wanted an easy way of knowing which referring links were driving traffic to the website and which ones were not. I also wanted a simple way to redirect incoming traffic based on the context of origin, and to track different marketing campaigns (from articles, emails, blogs, Google AdWords, etc.). Permalinks solve all three problems.
Contents
The formal notion of permalinks has been around since roughly 2000 (see Wikipedia). However, anyone who has ever reorganized a website, renamed pages, changed the landing page, or clicked on a link that was gone has probably imagined such a thing.
"Permalink" is short for "permanent link". The term originated as a way of referencing a web page or blog post that had been archived or moved. Permalinks are often combined with URL-rewriting, so that the browser presents human-readable (and search-engine-friendly) URLs while the back-end still tracks links using a database.
The simplest implementation of permalinks is simply an ID number passed in the URL’s query string. The main page uses the ID to decide which internal page to display.
Permalinks allow you to change the landing page without changing the website, simply by changing the destination entry in the database for the link ID. This allows you to reorganize your website as needed without the risk of killing external links.
Permalinks allow you to track which referring links are getting to your "goal pages", whether for demo downloads or orders, or even Easter Eggs. For example, the main page can remember the incoming link ID (in the session, for example), and the goal page can retrieve it and update a counter to track the hits.
The demo application includes an SQL script to create and populate a small database, and an ASP.NET website to demonstrate the use and tracking of permalinks. The SQL script creates the Campaign and CampaignLink tables, and populates them with some sample data. The website has a few content pages and "goal" pages in it, plus an "external referral" page to simulate external links and demonstrate how permalinks can be used to track referrals very specifically. A statistics page is also included to show the hit and goal statistics for each Campaign and CampaignLink.
To run the demo, open Microsoft SQL Server Management Studio using a login that has rights to create databases, tables, and Stored Procedures. Then, open the script create-permalinks-database.sql and execute it. Refresh the Databases list, and you should see the PermaLinks database.
Then, open the PermaLinkDemo.sln file in Visual Studio (VS2005, one master page), change the connect-string in the web.config file, if necessary, and build and run the website. The default page is external.aspx, which simulates external links using the CampaignLinks table in the database. Click on an external link to see which page it takes you to. Click on the "goal" buttons to increase the counters. Click the Back link to go back to the external-links page. Repeat for a while to drive the counts up. Then, click on the Show PermaLink Stats link to see the results.
The techniques used in the website and Stored Procedures are straightforward, but a few may be of interest to some:
- The external links page (external.aspx) is generated using a
SqlDataReader
and a StringBuilder
to populate a Literal
control with a table of links.
protected void Page_Load(object sender, EventArgs e)
{
string baseUrl = this.Request.Url.AbsoluteUri;
baseUrl = baseUrl.Replace("/external.aspx",
"/?linkid=");
SqlConnection conn = null;
SqlCommand cmd = null;
SqlDataReader rdr = null;
StringBuilder sb = new StringBuilder();
try
{
conn = new SqlConnection(
ConfigurationManager.ConnectionStrings["db"].ConnectionString);
cmd = new SqlCommand("select CampaignLinkId,
CampaignLinkDescription from dbo.CampaignLink", conn);
conn.Open();
rdr = cmd.ExecuteReader(CommandBehavior.Default);
sb.Append("<table border='0' align='center' width='90%'>");
while (rdr.Read())
{
sb.Append("<tr><td>>a href='");
sb.Append(baseUrl);
sb.Append(rdr.GetInt32(0));
sb.Append("'>");
sb.Append(rdr.GetString(1));
sb.Append("</a></td></tr>");
}
sb.Append("</table>");
this.Literal1.Text = sb.ToString();
}
catch(Exception ex)
{
this.Literal1.Text = "ERROR: " + ex.Message;
}
finally
{
if (rdr != null && !rdr.IsClosed)
{
rdr.Close();
rdr.Dispose();
rdr = null;
}
if (conn != null && conn.State != ConnectionState.Closed)
{
conn.Close();
conn.Dispose();
conn = null;
}
}
}
The Default.aspx page captures the link ID, stores it in the Session, and uses a Stored Procedure to retrieve the target URL for redirection.
protected void Page_Load(object sender, EventArgs e)
{
int linkId = 0;
if (!int.TryParse(this.Request.Params["linkid"], out linkId))
{
linkId = 1;
}
string url = getUrl(linkId);
this.Session["linkid"] = linkId;
this.Response.Redirect(url);
}
protected string getUrl(int id)
{
SqlConnection conn;
SqlCommand cmd;
string url = "hireme.aspx";
try
{
conn = new SqlConnection(
ConfigurationManager.ConnectionStrings["db"].ConnectionString);
cmd = new SqlCommand("dbo.uspGetLinkUrl "
+ id.ToString(), conn);
conn.Open();
object result = cmd.ExecuteScalar();
if (result != null && result != DBNull.Value)
{
url = Convert.ToString(result);
}
}
catch
{
}
return url;
}
The incoming link ID is retrieved from the Session by the goal-button handlers to update the counters using Stored Procedures.
protected void Button1_Click(object sender, EventArgs e)
{
updateBuyCounter();
}
private void updateBuyCounter()
{
SqlConnection conn = null;
SqlCommand cmd = null;
string linkid = Convert.ToString(this.Session["linkid"]);
if (linkid == null || linkid.Length <= 0) { return; }
try
{
conn = new SqlConnection(
ConfigurationManager.ConnectionStrings["db"].ConnectionString);
cmd = new SqlCommand("exec dbo.uspUpdateBuyCount "
+ linkid, conn);
conn.Open();
cmd.ExecuteNonQuery();
}
catch
{
}
finally
{
if (conn != null && conn.State != ConnectionState.Closed)
{
conn.Close();
conn.Dispose();
conn = null;
}
}
}
The statistics page is generated using three DataGridView
controls, a SqlDataReader
, and a Stored Procedure that returns three result sets.
protected void Page_Load(object sender, EventArgs e)
{
SqlConnection conn = null;
SqlCommand cmd = null;
SqlDataReader rdr = null;
try
{
conn = new SqlConnection(
ConfigurationManager.ConnectionStrings["db"].ConnectionString);
cmd = new SqlCommand("dbo.uspCampaignStats", conn);
cmd.CommandType = CommandType.StoredProcedure;
conn.Open();
rdr = cmd.ExecuteReader(CommandBehavior.Default);
GridViewCampaign.DataSource = rdr;
GridViewCampaign.DataBind();
rdr.NextResult();
GridViewCampaignLink.DataSource = rdr;
GridViewCampaignLink.DataBind();
rdr.NextResult();
GridViewTotals.DataSource = rdr;
GridViewTotals.DataBind();
}
catch
{
}
finally
{
if (rdr != null && !rdr.IsClosed)
{
rdr.Close();
rdr.Dispose();
rdr = null;
}
if (conn != null && conn.State != ConnectionState.Closed)
{
conn.Close();
conn.Dispose();
conn = null;
}
}
}
Permalinks are an easy way to prevent "link rot", track referrals, and monitor traffic. Much more detailed information is available in a website’s logs, and there are several products that mine these logs and produce detailed reports complete with graphs, charts, paths, etc. Such tools are not always available or convenient; however, a simple statistics page can be formatted to be readable on a SmartPhone – and thus accessible anytime, anywhere.
The basic permalinks implementation could be expanded to track visitor paths, using the session ID. Permalinks can also be implemented using an HttpModule, but the basic functionality remains the same. Permalinks can also be combined with URL rewriting to create more user-friendly links, effectively hiding the link IDs from the browser or search engine. Finally, a full-blown permalink solution would obviously include administration pages to maintain Campaigns and CampaignLinks.
- 2008-09-04
- Initial article published.