Google Analytics .NET Integration
I have put together a page to display my Google Analytics information for my ASP.NET sites using some other help around the net and a little know-how.
I started by building this class, which I call GoogleAnalytics.cs:
using System;
using System.Collections.Generic;
using System.Web;
using System.Net;
using System.IO;
using System.Text;
using System.Xml;
using System.Collections.Specialized;
using System.Diagnostics;
/// <summary>
/// Summary description for GoogleAnalytics
/// </summary>
public class GoogleAnalytics
{
public enum mode { ClientLogin, AuthSub }
//used when you want to manage email/pass from within your app
public static string getSessionTokenClientLogin(string email, string password)
{
//Google analytics requires certain variables to be POSTed
string postData = "Email=" + email + "&Passwd=" + password;
//defined - should not channge much
postData = postData + "&accountType=HOSTED_OR_GOOGLE" + "&service=analytics" + "&source=ACNSSite";
ASCIIEncoding encoding = new ASCIIEncoding();
byte[] data = encoding.GetBytes(postData);
HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create("https://www.google.com/accounts/ClientLogin");
myRequest.Method = "POST";
myRequest.ContentType = "application/x-www-form-urlencoded";
myRequest.ContentLength = data.Length;
Stream newStream = myRequest.GetRequestStream();
// Send the data.
newStream.Write(data, 0, data.Length);
newStream.Close();
HttpWebResponse myResponse = (HttpWebResponse)myRequest.GetResponse();
Stream responseBody = myResponse.GetResponseStream();
Encoding encode = System.Text.Encoding.GetEncoding("utf-8");
StreamReader readStream = new StreamReader(responseBody, encode);
//returned from Google Analytics API
string response = readStream.ReadToEnd();
//get the data we need
string[] auth = response.Split(new string[] { "Auth=" }, StringSplitOptions.None);
//return it (the authorization token)
return auth[1];
}
//used when you have authenticated on Google (via AuthSub & query params) & have a temp token
public static string getSessionTokenAuthSub(string tempToken)
{
string response = GArequestResponseHelper("https://www.google.com/accounts/AuthSubSessionToken", tempToken, mode.AuthSub);
Debug.WriteLine("getting this far");
Debug.WriteLine("perm token=" + response.Split('=')[1]);
//temp (once off) token will have been exchanged for session token, return it
return response.Split('=')[1];
}
public static NameValueCollection getAccountInfo(string sessionToken, mode mode)
{
string response = GArequestResponseHelper("https://www.google.com/analytics/feeds/accounts/default", sessionToken, mode);
//response will contain an XML formatted string similar to
//http://code.google.com/p/ga-api-http-samples/source/browse/trunk/src/v1/accountFeedResponse.xml
//we need to convert it to proper XML for parsing
XmlDocument accountinfoXML = new XmlDocument(); accountinfoXML.LoadXml(response);
//each account/profile combo the current user is authorized for will an 'entry' element
XmlNodeList entries = accountinfoXML.GetElementsByTagName("entry");
NameValueCollection profiles = new NameValueCollection();
for (int i = 0; i < entries.Count; i++)
{
//profile name, profile ID - profile ID is needed for ID what data you want from the API
profiles.Add(entries.Item(i).ChildNodes[2].InnerText,entries.Item(i).ChildNodes[6].Attributes["value"].Value);
}
return profiles;
}
public static string getContentOverviewData(string sessionToken, string profileID, mode mode)
{
//we'll also want to pass in non hardcoded dates and report specifiers
//month in format yyyy-mm-dd etc.
//At the moment we just getting pageview metric for our sites URLs
//for the purposes of demostration
string response = GArequestResponseHelper("https://www.google.com/analytics/feeds/data?ids=ga:" + profileID + "&dimensions=ga:pageTitle&metrics=ga:pageviews&sort=-ga:pageviews&start-date=" + DateTime.Now.AddDays(-30).ToString("yyyy-MM-dd") + "&end-date=" + DateTime.Now.ToString("yyyy-MM-dd") + "&prettyprint=true&max-results=20", sessionToken, mode);
//response will contain an XML formatted string similar to
//http://code.google.com/p/ga-api-http-samples/source/browse/trunk/src/v1/dataFeedResponse.xml
return response;
}
public static string getContentOverViewDataByTime(string sessionToken, string profileID, mode mode)
{
return "";
}
public static string getTrafficSourcesOverviewData(string sessionToken, string profileID, mode mode)
{
string response = GArequestResponseHelper("https://www.google.com/analytics/feeds/data?ids=ga:" + profileID + "&dimensions=ga:source&metrics=ga:pageviews&sort=-ga:pageviews&start-date=" + DateTime.Now.AddDays(-30).ToString("yyyy-MM-dd") + "&end-date=" + DateTime.Now.ToString("yyyy-MM-dd") + "&prettyprint=true&max-results=10", sessionToken, mode);
return response;
}
public static string getMapOverlayData(string sessionToken, string profileID, mode mode)
{
string response = GArequestResponseHelper("https://www.google.com/analytics/feeds/data?ids=ga:" + profileID + "&dimensions=ga:country&metrics=ga:pageviews&sort=-ga:pageviews&start-date=" + DateTime.Now.AddDays(-30).ToString("yyyy-MM-dd") + "&end-date=" + DateTime.Now.ToString("yyyy-MM-dd") + "&prettyprint=true&max-results=100", sessionToken, mode);
return response;
}
public static string getVisitsData(string sessionToken, string profileID, mode mode)
{
string response = GArequestResponseHelper("https://www.google.com/analytics/feeds/data?ids=ga:" + profileID + "&metrics=ga:visitors&start-date=" + DateTime.Now.AddDays(-30).ToString("yyyy-MM-dd") + "&end-date=" + DateTime.Now.ToString("yyyy-MM-dd") + "&prettyprint=true&max-results=100", sessionToken, mode);
return response;
}
public static string getPageViewsData(string sessionToken, string profileID, mode mode)
{
string response = GArequestResponseHelper("https://www.google.com/analytics/feeds/data?ids=ga:" + profileID + "&metrics=ga:pageviews&start-date=" + DateTime.Now.AddDays(-30).ToString("yyyy-MM-dd") + "&end-date=" + DateTime.Now.ToString("yyyy-MM-dd") + "&prettyprint=true&max-results=100", sessionToken, mode);
return response;
}
public static string getPagesPerVisitData(string sessionToken, string profileID, mode mode)
{
string response = GArequestResponseHelper("https://www.google.com/analytics/feeds/data?ids=ga:" + profileID + "&metrics=ga:uniquePageviews,ga:visits&start-date=" + DateTime.Now.AddDays(-30).ToString("yyyy-MM-dd") + "&end-date=" + DateTime.Now.ToString("yyyy-MM-dd") + "&prettyprint=true&max-results=100", sessionToken, mode);
return response;
}
public static string getBounceRateData(string sessionToken, string profileID, mode mode)
{
string response = GArequestResponseHelper("https://www.google.com/analytics/feeds/data?ids=ga:" + profileID + "&metrics=ga:bounces,ga:entrances&start-date=" + DateTime.Now.AddDays(-30).ToString("yyyy-MM-dd") + "&end-date=" + DateTime.Now.ToString("yyyy-MM-dd") + "&prettyprint=true&max-results=100", sessionToken, mode);
return response;
}
public static string getAvgTimeOnSiteData(string sessionToken, string profileID, mode mode)
{
string response = GArequestResponseHelper("https://www.google.com/analytics/feeds/data?ids=ga:" + profileID + "&metrics=ga:timeOnSite,ga:visits&start-date=" + DateTime.Now.AddDays(-30).ToString("yyyy-MM-dd") + "&end-date=" + DateTime.Now.ToString("yyyy-MM-dd") + "&prettyprint=true&max-results=100", sessionToken, mode);
return response;
}
public static string getNewVisitsData(string sessionToken, string profileID, mode mode)
{
string response = GArequestResponseHelper("https://www.google.com/analytics/feeds/data?ids=ga:" + profileID + "&metrics=ga:newVisits,ga:visits&start-date=" + DateTime.Now.AddDays(-30).ToString("yyyy-MM-dd") + "&end-date=" + DateTime.Now.ToString("yyyy-MM-dd") + "&prettyprint=true&max-results=100", sessionToken, mode);
return response;
}
public static string getVisitsPerDayData(string sessionToken, string profileID, mode mode)
{
string response = GArequestResponseHelper("https://www.google.com/analytics/feeds/data?ids=ga:" + profileID + "&dimensions=ga:day&metrics=ga:visits&start-date=" + DateTime.Now.AddDays(-30).ToString("yyyy-MM-dd") + "&end-date=" + DateTime.Now.ToString("yyyy-MM-dd") + "&prettyprint=true&max-results=100", sessionToken, mode);
return response;
}
public static string GArequestResponseHelper(string url, string token, mode mode)
{
HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(url);
//will always be a token of some sort required in the header but the format
//it is passed in will depend on what type of authorization is being used
if (mode == mode.ClientLogin)
{
myRequest.Headers.Add("Authorization: GoogleLogin auth=" + token);
}
else if (mode == mode.AuthSub)
{
myRequest.Headers.Add("Authorization: AuthSub token=" + token);
}
//obviously you need some kind of try/catch here
//but OK to bubble auth/connection failures up for demo
HttpWebResponse myResponse = (HttpWebResponse)myRequest.GetResponse();
Stream responseBody = myResponse.GetResponseStream();
Encoding encode = System.Text.Encoding.GetEncoding("utf-8");
StreamReader readStream = new StreamReader(responseBody, encode);
//return string itself (easier to work with)
return readStream.ReadToEnd();
}
}
I then created a page to display the data. The markup is as follows:
<asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server">
<link href="<%=ResolveClientUrl("~/_support/css/table.css") %>" rel="stylesheet" type="text/css" />
<script src="http://www.google.com/jsapi" type="text/javascript"></script>
<script type="text/javascript">
google.load("visualization", "1", { packages: ["corechart"] });
google.setOnLoadCallback(drawChart);
function drawChart() {
var data = new google.visualization.DataTable();
data.addColumn('string', 'Source');
data.addColumn('number', 'Visits');
<%=ChartData %>
var chart = new google.visualization.PieChart(document.getElementById('traffic_chart_div'));
chart.draw(data, { width: 550, height: 400, title: 'Traffic Sources' });
}
</script>
<script type='text/javascript'>
google.load('visualization', '1', { 'packages': ['geomap'] });
google.setOnLoadCallback(drawMap);
function drawMap() {
var data = new google.visualization.DataTable();
data.addRows(<%=MapCount %>);
data.addColumn('string', 'Country');
data.addColumn('number', 'Visits');
<%=MapOverlay %>
var options = {};
options['dataMode'] = 'regions';
var container = document.getElementById('map_canvas');
var geomap = new google.visualization.GeoMap(container);
geomap.draw(data, options);
};
</script>
<script type="text/javascript">
google.load("visualization", "1", { packages: ["corechart"] });
google.setOnLoadCallback(drawChart);
function drawChart() {
var data = new google.visualization.DataTable();
data.addColumn('string', 'Day');
data.addColumn('number', 'Visits');
<%=VisitsData %>
var chart = new google.visualization.LineChart(document.getElementById('chart_div'));
chart.draw(data, { width: 600, height: 350, title: 'Visits per Day' });
}
</script>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<h2 id="page-heading">Google Analytics</h2>
<div class="grid_16">
<p><a href="/Admin">Back to Admin</a></p>
<p>This is an overview of analytic data from Google Analytics from the past 30 days. If you would like to view the full reports visit <a href="http://google.com/analytics" rel="external">Google Analytics</a>.</p>
<div class="box">
<h2>Visits</h2>
<div class="block">
<div id="chart_div" style="margin-left:150px"></div>
</div>
</div>
<div class="box">
<h2>Site Usage</h2>
<table>
<thead>
<tr>
<th scope="col">Metric</th>
<th scope="col">Value</th>
</tr>
</thead>
<tbody>
<tr><td>Visits</td><td><asp:Literal ID="Literal1" runat="server"></asp:Literal></td></tr>
<tr><td>Pageviews</td><td><asp:Literal ID="Literal2" runat="server"></asp:Literal></td></tr>
<tr><td>Pages/Visit</td><td><asp:Literal ID="Literal3" runat="server"></asp:Literal></td></tr>
<tr><td>Bounce Rate</td><td><asp:Literal ID="Literal4" runat="server"></asp:Literal></td></tr>
<tr><td>Average Time on Site</td><td><asp:Literal ID="Literal5" runat="server"></asp:Literal></td></tr>
<tr><td>New Visits</td><td><asp:Literal ID="Literal6" runat="server"></asp:Literal></td></tr>
</tbody>
</table>
</div>
<div class="box">
<h2>Map Overlay</h2>
<div class="block">
<div id='map_canvas' style="margin-left:150px;"></div>
</div>
</div>
<div class="box">
<h2>Traffic Sources Overview</h2>
<div class="block">
<div id="traffic_chart_div" style="margin-left:150px;"></div>
</div>
</div>
<div class="box">
<h2>Content Overview</h2>
<asp:GridView ID="GridView1" runat="server" DataKeyNames="id" AutoGenerateColumns="false">
<Columns>
<asp:BoundField HeaderText="Page Title" DataField="pageTitle" />
<asp:BoundField HeaderText="Page Views" DataField="pageViews" />
</Columns>
</asp:GridView>
</div>
</div>
</asp:Content>
I use a very hack-ish method of injecting data into my javascript functions, there is no doubt a better way of doing this (AJAX), but I wrote this a good amount of time ago before I knew better.
Now for the meat and bones that really make all of this work together. Here is the code behind:
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Configuration;
using System.Xml;
using System.Data;
using System.Globalization;
public partial class admin_Analytics : System.Web.UI.Page
{
public string ChartData;
public string MapOverlay;
public int MapCount;
public string VisitsData;
protected void Page_Load(object sender, EventArgs e)
{
//resultsXML.Save(Server.MapPath("../") + "\\files\\test.xml");
string token = GoogleAnalytics.getSessionTokenClientLogin(ConfigurationManager.AppSettings["AnalyticsLogin"], ConfigurationManager.AppSettings["AnalyticsPassword"]);
Session["token"] = token;
LoadVisits();
LoadSiteUsage();
LoadMapOverlay();
LoadTrafficSourcesOverview();
LoadContentOverview();
}
protected void LoadVisits()
{
string results = GoogleAnalytics.getVisitsPerDayData(Session["token"].ToString(), ConfigurationManager.AppSettings["AnalyticsProfileID"], GoogleAnalytics.mode.ClientLogin);
//we need to convert it to proper XML for parsing
XmlDocument resultsXML = new XmlDocument(); resultsXML.LoadXml(results);
//each line/record return from the API will be contained in an 'entry' element
XmlNodeList entries = resultsXML.GetElementsByTagName("entry");
VisitsData += "data.addRows(" + entries.Count + ");";
int i = 0;
foreach (XmlNode node in entries)
{
DateTime time = DateTime.Now.AddDays(-31 + Convert.ToInt32(node.ChildNodes[4].Attributes["value"].Value));
VisitsData += "data.setValue(" + i + ", 0, '" + time.ToShortDateString() + "');";
VisitsData += "data.setValue(" + i + ", 1, " + node.ChildNodes[5].Attributes["value"].Value + ");";
i++;
}
}
protected void LoadSiteUsage()
{
string results = GoogleAnalytics.getVisitsData(Session["token"].ToString(), ConfigurationManager.AppSettings["AnalyticsProfileID"], GoogleAnalytics.mode.ClientLogin);
//we need to convert it to proper XML for parsing
XmlDocument resultsXML = new XmlDocument(); resultsXML.LoadXml(results);
//each line/record return from the API will be contained in an 'entry' element
XmlNodeList entries = resultsXML.GetElementsByTagName("entry");
Literal1.Text = entries[0].ChildNodes[4].Attributes["value"].Value;
results = GoogleAnalytics.getPageViewsData(Session["token"].ToString(), ConfigurationManager.AppSettings["AnalyticsProfileID"], GoogleAnalytics.mode.ClientLogin);
resultsXML = new XmlDocument(); resultsXML.LoadXml(results);
entries = resultsXML.GetElementsByTagName("entry");
Literal2.Text = entries[0].ChildNodes[4].Attributes["value"].Value;
results = GoogleAnalytics.getPagesPerVisitData(Session["token"].ToString(), ConfigurationManager.AppSettings["AnalyticsProfileID"], GoogleAnalytics.mode.ClientLogin);
resultsXML = new XmlDocument(); resultsXML.LoadXml(results);
entries = resultsXML.GetElementsByTagName("entry");
Literal3.Text = (Convert.ToDouble(entries[0].ChildNodes[4].Attributes["value"].Value) / Convert.ToDouble(entries[0].ChildNodes[5].Attributes["value"].Value)).ToString();
results = GoogleAnalytics.getBounceRateData(Session["token"].ToString(), ConfigurationManager.AppSettings["AnalyticsProfileID"], GoogleAnalytics.mode.ClientLogin);
resultsXML = new XmlDocument(); resultsXML.LoadXml(results);
entries = resultsXML.GetElementsByTagName("entry");
Literal4.Text = (Convert.ToDouble(entries[0].ChildNodes[4].Attributes["value"].Value) / Convert.ToDouble(entries[0].ChildNodes[5].Attributes["value"].Value)).ToString("0.##%", CultureInfo.CurrentCulture);
results = GoogleAnalytics.getAvgTimeOnSiteData(Session["token"].ToString(), ConfigurationManager.AppSettings["AnalyticsProfileID"], GoogleAnalytics.mode.ClientLogin);
resultsXML = new XmlDocument(); resultsXML.LoadXml(results);
entries = resultsXML.GetElementsByTagName("entry");
TimeSpan t = TimeSpan.FromSeconds(Convert.ToDouble(entries[0].ChildNodes[4].Attributes["value"].Value) / Convert.ToDouble(entries[0].ChildNodes[5].Attributes["value"].Value));
Literal5.Text = string.Format("{0:D2}:{1:D2}:{2:D2}", t.Hours, t.Minutes, t.Seconds);
results = GoogleAnalytics.getNewVisitsData(Session["token"].ToString(), ConfigurationManager.AppSettings["AnalyticsProfileID"], GoogleAnalytics.mode.ClientLogin);
resultsXML = new XmlDocument(); resultsXML.LoadXml(results);
entries = resultsXML.GetElementsByTagName("entry");
Literal6.Text = (Convert.ToDouble(entries[0].ChildNodes[4].Attributes["value"].Value) / Convert.ToDouble(entries[0].ChildNodes[5].Attributes["value"].Value)).ToString("0.##%", CultureInfo.CurrentCulture);
}
protected void LoadMapOverlay()
{
string results = GoogleAnalytics.getMapOverlayData(Session["token"].ToString(), ConfigurationManager.AppSettings["AnalyticsProfileID"], GoogleAnalytics.mode.ClientLogin);
//we need to convert it to proper XML for parsing
XmlDocument resultsXML = new XmlDocument(); resultsXML.LoadXml(results);
//each line/record return from the API will be contained in an 'entry' element
XmlNodeList entries = resultsXML.GetElementsByTagName("entry");
MapCount = entries.Count;
int i = 0;
foreach (XmlNode node in entries)
{
MapOverlay += "data.setValue(" + i + ", 0, '" + node.ChildNodes[4].Attributes["value"].Value + "');";
MapOverlay += "data.setValue(" + i + ", 1, " + node.ChildNodes[5].Attributes["value"].Value + ");";
i++;
}
}
protected void LoadTrafficSourcesOverview()
{
string results = GoogleAnalytics.getTrafficSourcesOverviewData(Session["token"].ToString(), ConfigurationManager.AppSettings["AnalyticsProfileID"], GoogleAnalytics.mode.ClientLogin);
//we need to convert it to proper XML for parsing
XmlDocument resultsXML = new XmlDocument(); resultsXML.LoadXml(results);
//each line/record return from the API will be contained in an 'entry' element
XmlNodeList entries = resultsXML.GetElementsByTagName("entry");
ChartData += "data.addRows(" + entries.Count + ");";
int i = 0;
foreach (XmlNode node in entries)
{
ChartData += "data.setValue(" + i + ", 0, '" + node.ChildNodes[4].Attributes["value"].Value + "');";
ChartData += "data.setValue(" + i + ", 1, " + node.ChildNodes[5].Attributes["value"].Value + ");";
i++;
}
}
protected void LoadContentOverview()
{
string results = GoogleAnalytics.getContentOverviewData(Session["token"].ToString(), ConfigurationManager.AppSettings["AnalyticsProfileID"], GoogleAnalytics.mode.ClientLogin);
//we need to convert it to proper XML for parsing
XmlDocument resultsXML = new XmlDocument(); resultsXML.LoadXml(results);
//each line/record return from the API will be contained in an 'entry' element
XmlNodeList entries = resultsXML.GetElementsByTagName("entry");
DataTable table;
table = MakeContentOverviewTable();
foreach (XmlNode node in entries)
{
DataRow row = table.NewRow();
row["pageTitle"] = node.ChildNodes[4].Attributes["value"].Value;
row["pageViews"] = node.ChildNodes[5].Attributes["value"].Value;
table.Rows.Add(row);
}
GridView1.DataSource = table;
GridView1.DataBind();
}
private DataTable MakeContentOverviewTable()
{
DataTable ContentOverview = new DataTable("ContentOverview");
DataColumn idColumn = new DataColumn();
idColumn.DataType = System.Type.GetType("System.Int32");
idColumn.ColumnName = "id";
idColumn.AutoIncrement = true;
ContentOverview.Columns.Add(idColumn);
DataColumn pageTitle = new DataColumn();
pageTitle.DataType = System.Type.GetType("System.String");
pageTitle.ColumnName = "pageTitle";
ContentOverview.Columns.Add(pageTitle);
DataColumn pageViews = new DataColumn();
pageViews.DataType = System.Type.GetType("System.Int32");
pageViews.ColumnName = "pageViews";
ContentOverview.Columns.Add(pageViews);
return ContentOverview;
}
}
All you need to do at this point is make sure that you have the following lines in the Web.Config:
<appSettings>
<add key="AnalyticsLogin" value="login@goeshere.com"/>
<add key="AnalyticsPassword" value="1337P4ssw0rdH3r3"/>
<add key="AnalyticsProfileID" value="123456789"/>
</appSettings>