Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Google Analytics API for modern C#
C#
branch: master

README.md

Igooana

Build status

Google Analytics API for modern C#.

The goal of this library is to create a modern, fully asynchronous Google Analytics API for C# 5. All potentially long operations are async/await methods.

Installation

Use NuGet:

Install-Package Igooana 

Usage

Before you start, make sure you have your Google Analytics clientId and clientSecret credentials. If you don't, first get them from Google here: https://cloud.google.com/console#/project

Authentication

Using the api requires OAuth 2.0 authentication with web browser. If you're using Xaml (WPF or Silverlight applications), the code might look like:

<Grid>
  <WebBrowser Name="Browser" Navigating="OnNavigating" />
</Grid>

Code behind:

void OnLoaded(object sender, RoutedEventArgs e) {
  // assuming you already have clientId and clientSecret
  api = new Api(clientId, clientSecret);
  Browser.Navigate(api.AuthenticateUri);
}

private async void OnNavigating(object sender, NavigatingCancelEventArgs e) {
  ICredentials credentials = await api.Authenticate(e.Uri);
  if(credentials.Authenticated){
    // You are authenticated at this poing
  }
}

ICredentials is a simple class that contains the AccessToken and RefreshToken. AccessToken is valid only for 60 minutes, so a refresh is necessary when it expires. Igooana will refresh access tokens automatically, so you don't have to do anything.

3rd party app access confirmation screen.

During the process of authentication, google shows a special Consent Page to verify that user grants permission to this app. Igooana handles this screen transparently for you, i.e. you don't need to do anything to handle it. It's possible, however that user clicks Cancel on the Consent Page. In this case, api.Authenticate throws AccessDeniedException.

Here is how to handle it:

private async void OnNavigating(object sender, NavigatingCancelEventArgs e) {
  try{
    ICredentials credentials = await api.Authenticate(e.Uri);
    if(credentials.Authenticated){
      // You are authenticated at this poing
    }
  }
  catch(AccessDeniedException){
    MessageBox.Show("You must allow this app accessing your data.");
    // Start authentication sequence again
    Browser.Navigate(api.AuthenticateUri);
  }
}

Credentials reuse and restoring the API

When you first got the instance of ICredentials, you will want to store it in the DB in order to reuse the AccessToken/RefreshToken. Let's say you already have an instance of ICredentials and user opens your app 2nd time. You will want to restore the API from your credentials.

if(DB.CredentialsExist){
  Api.Restore(DB.Credentials);
  // Call the API methods using Api.Current as usual, no need to reauthenticate
}

The Api will store the credentials and use RefreshToken to obtain new AccessToken automatically as needed. Whenever Api refreshes the AccessToken, you will want to be notified, to update it in the DB.

if(DB.CredentialsExist){
  Api.Restore(DB.Credentials);
  Api.Current.AccessTokenRefreshed += (sender, e) => DB.UpdateAccessToken(e.AccessToken);
}

Token revocation

It's possible that user revokes access to any 3rd party app by revoking a token. When this happens, Igooana will try to refresh AccessToken and fail, because the RefreshToken will no longer be valid. In this case, Igooana will raise TokenRevokedExeption.

Example of correctly handling token revocation:

try {
  data = await Api.Current.Management.GetProfilesAsync();
}
catch (TokenRevokedException) {
  MessageBox.Show("You revoked the token and need to relogin");
  DB.ClearCredentials();//because they're no longer valid
  // start the relogin process, for example, open the web browser control and navigate to the initial URL.
}

What about 2-factor authentication?

Two factor authentication implies you have a one-time password sent to you either via text message or using a separate app for it. When you login for the first time with 2-factor authentication, you receive an SMS with a confirmation password, open it, read the code, return back to the app, insert the code in the browser and click 'Confirm'.

In other words, 2-factor authentication requires you to switch to another application (if you're on a mobile) and has +1 confirmation step. The programmatic part of the authentication flow stays the same and you, as API user, don't need to do anything at all.

Management

Api class offers Management property which you can use to access Management API operations.

Profiles

  var profiles = await api.Management.GetProfilesAsync();

Reporting

Api class offers a Execute method which accepts a Query and executes it. Constructing a Query is typesafe and fun. Let's say you'd like to view a number of visits and pageviews by browser for last month.

try {
  int profileId = GetProfileId(); // this might use Api.Management to get all your profiles.
  var query = Query
    .For(profileId, DateTime.Now.AddDays(-31), DateTime.Now)
    .WithMetrics(Metric.Session.Visits + Metric.PageTracking.Pageviews)
    .WithDimensions(Dimension.PlatformOrDevice.Browser);
  Result result = await api.Execute(query);
  foreach(var row in result.Values){
    Console.WriteLine("I had {0} visits with {1} pageviews on {2} browser", 
      row.Visits, row.Pageviews, row.Browser);
  }
}
catch (ConnectionException ex) {
  MessageBox.Show(ex.DetailedMessage);
}

Take

Take limits number of returned rows. The default value is 1,000 rows.

Get only 10 rows

var query = Query
    .For(profileId, DateTime.Now.AddDays(-31), DateTime.Now)
    .WithMetrics(Metric.Session.Visits + Metric.PageTracking.Pageviews)
    .WithDimensions(Dimension.PlatformOrDevice.Browser);
    .Take(10);
  Result result = await api.Execute(query);

Skip

Skip skips the rows, it only selects the rows after certain offset. The skip value is 1 based (not 0).

You can implement a paginated values collection using a combination of Skip and Take.

This example only returns items from 11 to 20, essentially allowing you to get the second page of the values.

var query = Query
    .For(profileId, DateTime.Now.AddDays(-31), DateTime.Now)
    .WithMetrics(Metric.Session.Visits + Metric.PageTracking.Pageviews)
    .WithDimensions(Dimension.PlatformOrDevice.Browser);
    .Skip(10)
    .Take(10);
  Result result = await api.Execute(query);

Ordering

Query offers two methods for ordering:

Order, that orders rows by given Dimension or Metric in ascending direction.

OrderByDescending, that orders rows by given Dimension or Metric in descending direction.

These options allow you to efficiently select an ordered resultset from Google API and to not perform the ordering client-side.

An example of viewing top 10 countries by page views:

var query = Query
    .For(profileId, DateTime.Now.AddDays(-31), DateTime.Now)
    .WithMetrics(Metric.PageTracking.Pageviews)
    .WithDimensions(Dimension.GeoNetwork.Country)
    .Take(10)
    .OrderByDescending(Metric.PageTracking.Pageviews);
  Result result = await api.Execute(query);

Query execution result

What does Api#Execute return? It's a Igooana.Result object. This object has two useful properties:

  1. Values property - an IEnumerable<dynamic>. This collection contains actual analytics data items. Using dynamic allows you to have a collection of objects with all the properties you've queried with metrics and dimensions. Moreover, all properties have their respective CLR types, not strings Google Analytics returns.

  2. Totals property - a dynamic. This property contains the summed metrics. The names of totals directly correspond to the metrics you query. The properties have the correct CLR types, as with Values

Totals property example:

  int profileId = GetProfileId(); // this might use Api.Management to get all your profiles.
  var query = Query
    .For(profileId, DateTime.Now.AddDays(-31), DateTime.Now)
    .WithMetrics(Metric.Session.Visits + Metric.PageTracking.Pageviews)
    .WithDimensions(Dimension.PlatformOrDevice.Browser);
  Result result = await api.Execute(query);
  Console.WriteLine("Total Visits: {0}, total page views: {1}", 
    result.Totals.Visits, result.Totals.Pageviews);

Potential caveat when using silverlight/windows phone data binding to result.Values:

There currently is a bug related to it: http://connect.microsoft.com/VisualStudio/feedback/details/522119/databinding-to-dynamic-objects-is-broken

There are various workarounds listed on that issue page. I'm using dynamic to anonymous object conversion:

Result resut = await api.Execute(query);
var values = result.Values.Select(x => new {Visits = x.Visits, Pageviews = x.Pageviews});
// now values can be safely data-bound in Silverlight/Windows Phone projects

This is a work in progress, PRs are welcome :)

All code is MIT licensed

Valentin Vasilyev, 2014

Something went wrong with that request. Please try again.