Using Google Analytics with rich (managed) web applications in Silverlight
October 1, 2007
If you’d like to see how many people click “Skip Intro” on your rich Silverlight video intro, or perhaps click the “Details/More” button on your rich advertisement, Google Analytics can provide that data. Analytics events fired from within your rich client application show up in site traffic reports just as if they were page views within your web site, so you can organize the event names however you like—they’re just relative URLs that you create (but do not actually exist).
Although there is a web services API for all of this, the simple tracker functions exposed by Google’s JavaScript is the quickest way to get off the ground using the DOM bridge.
Here's a working v1.1 CTP application that you can execute. If you're using a tool like Fiddler to examine HTTP calls, you'll see the events heading back over the wire when you click the buttons [http://www.jeff.wilcox.name/blog/sampleApps/analytics/].
And a quick look at a sample Analytics page that shows events that were fired through managed code Button.Click events:
Simple call
If you’re already including Google’s urchin.js script on your site’s pages, then you only need to evaluate the urchinTracker function with the relative URL you’d like recorded in your reports:
string eventUrl = "/testing/mySilverlightApp1/skipIntro/";
string jsCode = "urchinTracker('" + eventUrl + "')";
HtmlElement js = HtmlPage.Document.CreateElement("script");
js.SetAttribute("type", "text/javascript");
js.SetProperty("text", jsCode);
HtmlPage.Document.GetElementsByTagName("body")[0].AppendChild(js);
This same code if you’re also compiling in my HtmlExtensions class is a little shorter:
string eventUrl = "/testing/mySilverlightApp1/skipIntro/";
string jsCode = "urchinTracker('" + eventUrl + "')";
HtmlExtensions.Eval(jsCode);
Managed analytics interface
Building a static managed class allows quick insertion into your Silverlight project’s code pretty easily. You could then insert any events you’d like to track into button events, timers, or at the start of a MediaElement in your managed content player.
Here's the one-liner that will send the "page view":
AnalyticsManager.SendEvent("/testing/mySilverlightApp1/skipIntro/");
In this managed miniature library I also added code to include Google's analytics script, so you don’t have to worry about including your analytics code anywhere in your HTML pages. Simply call the static InitializeAnalytics method with your Analytics Account ID (string) within your canvas OnLoaded method.
The size of the retail build of this static class is 5KB + the 5KB HtmlExtensions bits means that this functionality costs about the same as a small image on your web site.
AnalyticsManager.cs is also available for download here.
// NOTE: Uses HtmlExtensions class as found at
// http://blogs.msdn.com/jeffwilcox/archive/2007/09/30/useful-managed-html-dom-methods-for-microsoft-silverlight.aspx
/// <summary>
/// Static class which is able to fire events to Google Analytics. It
/// can also initialize the Urchin script when it has not been
/// included in the web page.
/// </summary>
public static class AnalyticsManager
{
#region Constants
/// <summary>Variable Analytics expects set</summary>
private const string UrchinAccountVariable = "_uacct";
/// <summary>Urchin.js location</summary>
private const string RemoteAnalyticsScript = "http://www.google-analytics.com/urchin.js";
/// <summary>Method that fires Analytics events</summary>
private const string RemoteAnalyticsFunction = "urchinTracker";
#endregion
/// <summary>
/// Send an event to Google Analytics remotely using the
/// Google Analytics (Urchin) JavaScript library.
///
/// The Silverlight HTML DOM bridge is used for this call
/// into JavaScript instead of creating an HTTP web
/// request or anything purely in managed code.
///
/// See also: Google reference at http://www.google.com/support/analytics/bin/answer.py?answer=27243&hl=en
/// </summary>
/// <example>
/// AnalyticsManager.SendEvent("/myBigAdCampaign/detailsButton/");
/// </example>
/// <param name="relativeLink">String Google Analytics would
/// recognize as an interesting event</param>
public static void SendEvent(string relativeLink)
{
if (false == IsAnalyticsScriptInstalled()) {
throw new InvalidOperationException("Analytics script has not been initialized yet.");
}
if (String.IsNullOrEmpty(relativeLink)) {
throw new ArgumentNullException("relativeLink");
}
if (false == relativeLink.StartsWith("/")) {
throw new ArgumentException("relativeLink must begin with a forward slash", "relativeLink");
}
// Silverlight v1.1 CTP hack to permit evaluation of JavaScript code
HtmlExtensions.Eval(RemoteAnalyticsFunction
+ "(\"" + relativeLink + "\");");
}
/// <summary>
/// Check whether analytics has been initialized by simply looking
/// for the Urchin account ID. A better and more concrete
/// implementation would look for a known global function or other
/// variable exposed by Google's urchin.js script.
/// </summary>
private static bool IsAnalyticsScriptInstalled()
{
return (HtmlPage.Window.GetProperty<string>(UrchinAccountVariable)
!= null );
}
#region Optional Initialization Code
/// <summary>
/// Equivalent to the tracking JavaScript code that must be inserted
/// into all pages that you want to track using Google Analytics.
///
/// This method could be called during the OnLoaded canvas event of
/// your application to ensure that Urchin methods are available
/// when you need to track events.
///
/// It is still easiest of course to simply include the traditional
/// Google Analytics code on your pages, but I wanted to present an
/// alternative using the HTML DOM and the creation of new
/// HtmlElement objects here.
/// </summary>
public static void InitializeAnalytics(string analyticsAccountId)
{
// If already included on the page
if (IsAnalyticsScriptInstalled()) {
return;
}
HtmlElement jsInclude = HtmlExtensions.CreateJavaScriptInclude(
RemoteAnalyticsScript);
HtmlElement jsScript = HtmlExtensions.CreateJavaScriptElement(
UrchinAccountVariable + " = \"" + analyticsAccountId
+ "\";" + RemoteAnalyticsFunction + "();");
HtmlExtensions.HeadElement.AppendChildren(jsInclude, jsScript);
// NOTE: Upon return, the browser will need to parse the updated
// page DOM, load the Urchin script, and then finally be available
// for our use.
}
#endregion
}