I’ve been thinking about how to solve a very simple problem on a website: visitor behaviour tracking. In a sense it is what Google Analytics does, but there are problems with conventional JavaScript based trackers.

They are good at tracking page views, but very bad at tracking actions or behaviour around a website. Some products like Headlight are actually pretty good at tracking actions such as button clicks etc, but at the level I’m interested in tracking, I would have to add JavaScript all over my page. I don’t want to do that.

Also, there are some very good server-side logging products like log4net out there. The problem is that they are not really meant for tracking website behaviour with URLs, user agents and other important metadata.

What I want is a combination of the traditional JavaScript- and server-side methods. So, I’ve played around with a custom HttpModule that logs all page views and custom actions. You add the custom actions yourself by calling VistorLog.AddAction("message", "type"). That way you have page views and actions in a chronologically correct order.

A neat thing is that all page views and actions are kept in session and only when the session expires does it write to the database. That way it can do a batch insert which is much faster than hitting the database constantly. Another neat thing is that the HttpModule is only 100 lines of code.

The code

Basically, three things are going on. The session starts and we add a Visit object to it. 

void session_Start(object sender, EventArgs e)

{

  HttpContext context = HttpContext.Current;

  Visit visit = new Visit();

  visit.UserAgent = context.Request.UserAgent;

  visit.IpAddress = context.Request.UserHostAddress;

  context.Session.Add("visit", visit);

}

Then every page view is registered after an .aspx page is served.

void context_PostRequestHandlerExecute(object sender, EventArgs e)

{

  HttpContext context = ((HttpApplication)sender).Context;

 

  if (context.CurrentHandler is Page)

  {

    Visit visit = context.Session["visit"] as Visit;

    if (visit != null)

    {

      Action action = new Action();

      action.Url = context.Request.Url;

      action.Type = "pageview";

      visit.Action.Add(action);

    }

  }

}

Then the session ends and we need to store the visitor log.

void session_End(object sender, EventArgs e)

{

  HttpContext context = HttpContext.Current;

  Visit visit = context.Session["visit"] as Visit;

  if (visit != null)

  {

    // Log the Visit object to a database

  }

}

Implementation

When you have registered the HttpModule in the web.config, then it starts collection page views in the session. To store them in a database you must add your own code to the session_End method of the module. Now you are also able to store actions just by calling a static method on the VisitorLog module:

VisitorLog.AddAction("Profile picture deleted", "deletion");

Keep in mind that this code is just me playing around in my sandbox. It has never been in a production environment.

Download

VisitorLog.zip (1,11 kb)

At work I’ve come to realize that I don’t always produce code that just work. I make mistakes and often forget to test properly before I sign off a task. So I talked to our test lead and asked him to compile a list of the most common issues he finds when testing my work. I then printed out the list and hung it on the wall next to my desk as a checklist to go through whenever I think a task is done. 

He could actually only produce 7 common issues, but I put in three more that relates to code quality. Since I’m an ASP.NET front-end developer, the list focuses on UI issues seen through the lens of a tester. The list is not prioritized.

I hereby declare that all features, tasks and tweaks…

Works correctly in IE6, IE7 and Firefox

The test lead found that cross-browser issues surfaced from time to time. Most often it was really minor issues like positioning of elements or general IE6 quirks. The reason why this rule isn’t called “Works correctly across browsers” but instead focuses on IE and Firefox is simple. If you can get your page to work in IE6, IE7 and Firefox, then it almost always works in Safari and Opera as well. Second, those browsers cover about 98% of our visitors. Third, if it was called “Works correctly across browsers” it might be easier to ignore when going through the list.

Is XSS secure

Make sure that all input fields are handled correctly so no HTML or JavaScript entered by a user can mess up the page.

Long text doesn't break design

My limited brain tells me that a name is never longer than approx. 50 characters. I still believe it, but what happens if you put in a 300 character name? In many cases this would break the UI. Either limit the length of text a user can submit or tweak the design to take long text into account.

Localize everything

When I start working on a new feature for the website, I hard code all text because it might change during development. When the feature is approved by the product team then I localize the text. Since this is on the list, I sometimes forget to localize ALL the text. I will still do hard coding of text, but this checklist will help me remember to localize before signing off.

The enter-key works as expected

When using ASP.NET webforms, a form element is probably located at the master page so all pages resides inside that form. That often leads to strange behaviours of the enter-key. Remember to set default buttons either from code-behind or on the Panel webcontrol.

The code is reusable when possible

This is a general coding law and in ASP.NET the same rules apply. Separate elements in user- and server controls and make them small and specialized so they can be used elsewhere. Read more about reusable ASP.NET.

Unit tests written when possible

If you’re not using the ASP.NET MVC framework, unit testing your website is not easy. However, if you pull out as much of the code-behind logic into components that you can put in a library, then you can unit test. Instead of using .ashx files for HttpHandlers, put them in a separate library. Use server controls instead of user controls when you can.

The code is well commented

This goes without saying. Comment and document your code so other developers can easily pick up where you left.

Is peer verified before going into testing

Before a feature is signed off and sent to test, we do peer verification. Peer verification is when one of your colleagues performs sanity checking on your feature or just tries to break it. This let you find issues early on and make the life easier for the testers. When we are really busy, I often forget to ask for peer verification and that shows.

Is signed off by product owner

At work we have a product team that always have the ownership of a feature. We developers also have ownership of features, but that is from an implementation level. During a busy day when I do several different tasks, I sometimes forget to get the product owner to sign off my work. If it isn’t signed off, then you’re simply not done yet and I sometimes have to finish a task several days after I was “done”. This is annoying and makes it harder to meet deadlines. I must say that this is probably the single most important issue, but it is also the one that I almost never forget for that very reason.

Time will tell if this list will raise the quality of my work. I’m sure that as long as I don’t ignore the list, it will. Are any issues missing?