Skip to content

Commit

Permalink
Add Support for UA's UserID (#10)
Browse files Browse the repository at this point in the history
* initial add with tests

* add tests to new factory methods

* some renaming of 'Anonymous Client Id' to 'Client Id'

* allow exception tracking via a returned object... nobody wants their app to crash because of tracking.

* implemented Version 5 GUID logic for ClientId

* added 1.4.4 and 1.4.4-rc nuget packages (they are the same code)
  • Loading branch information
cogree authored Dec 8, 2017
1 parent a8413de commit e5d70cb
Show file tree
Hide file tree
Showing 27 changed files with 954 additions and 254 deletions.
47 changes: 0 additions & 47 deletions Nuget/UniversalAnalyticsMeasurementProtocolWrapper.1.2.0.nuspec

This file was deleted.

Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2013/01/nuspec.xsd">
<metadata>
<id>UniversalAnalyticsMeasurementProtocolWrapper</id>
<version>1.4.0</version>
<version>1.4.4</version>
<title>Universal Analytics Measurement Protocol .NET Wrapper</title>
<authors>jakejgordon</authors>
<owners>jakejgordon</owners>
Expand All @@ -13,6 +13,7 @@
<description>Wrapper for pushing data to Universal Analytics properties via the Measurement Protocol (version 1) on the server-side. This version only supports pushing Event data to web properties.</description>
<summary>Wrapper for pushing data to Universal Analytics properties via the Measurement Protocol on the server-side. This version only supports pushing Event data to web properties.</summary>
<releaseNotes>
* 1.4.4 Added ability to track via userId; Compress errors into object to avoid crashes.
* 1.4.0 Added async methods and switched the back-end to use the HTTPS Google endpoint instead of the HTTP one
* 1.3.0 Major overhaul to the interfaces. Provided a factory for getting IUniversalAnalyticsEvent objects, and eliminated one of the required app settings.</releaseNotes>
<copyright>jakejgordon 2017</copyright>
Expand Down
135 changes: 106 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,125 @@
Universal-Analytics-For-DotNet
==============================

A .NET wrapper over top of Google's Universal Analytics Measurement Protocol HTTP API. For now, this wrapper allows you to push Events from server-side code. This offers the advantages of 1) not relying on client-side javascript and 2) allowing you to push more interesting events that may not be available on the client side. For example, if you have a website for collecting donations you could push an event to indicate that a donation occurred and push the "value" of that donation. See https://developers.google.com/analytics/devguides/collection/protocol/v1/devguide#event for more details.
A .NET wrapper over top of Google's Universal Analytics Measurement Protocol HTTP API. This wrapper allows you to push UA events from server-side code.

Pushing an event is as simple as:
## Event Tracking

First, add your tracking id to your App.config/Web.config:
```xml
<configuration>
<appSettings>
<add key="UniversalAnalytics.TrackingId" value="UA-XXXXXXXX-X"/>
</appSettings>
...
</configuration>
```

Then, create an event tracker and factory.
```c#
IEventTracker eventTracker = new EventTracker();
IUniversalAnalyticsEvent analyticsEvent = new UniversalAnalyticsEvent(
//Required. Anonymous client id.
//See https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#cid for details.
"developer",
//Required. The event category for the event.
// See https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#ec for details.
"test category",
//Required. The event action for the event.
//See https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#ea for details.
"test action",
//The event label for the event.
// See https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#el for details.
"test label",
//Optional. The event value for the event.
// See https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#ev for details.
"10");
eventTracker.TrackEvent(analyticsEvent);
// The factory pulls your tracking ID from the .config so you don't have to.
IUniversalAnalyticsEventFactory eventFactory = new UniversalAnalyticsEventFactory();
```

Make sure you define your UA tracking ID in your project's App.config/Web.config!
Next, create an event to push to Google Analytics. Note that Google has defined that an event must have either a `ClientId (cid)`, [see here](https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#cid), or `UserId (uid)`, [see here](https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#uid). The factory has overloads for each of these cases.

To create an event with a `ClientId`:

```c#
// Create a clientId with a random Guid...
ClientId clientId = new ClientId();
// OR from a supplied Guid...
ClientId clientId = new ClientId(new Guid("..."));
// OR from a supplied string (uses a hash of the string).
ClientId clientId = new ClientId("...");

var analyticsEvent = eventFactory.MakeUniversalAnalyticsEvent(
// Required. The client id associated with this event.
// See https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#cid for details.
clientId,
// Required. The event category for the event.
// See https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#ec for details.
"test category",
// Required. The event action for the event.
//See https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#ea for details.
"test action",
// Optional. The event label for the event.
// See https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#el for details.
"test label",
// Optional. The event value for the event.
// See https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#ev for details.
"10");
```
<configuration>
<appSettings>
<add key="UniversalAnalytics.TrackingId" value="UA-XXXXXXXX-X"/>
</appSettings>
...
</configuration>

To create an event with a `UserId`:

```c#
// Create a user id from a string
UserId userId = new UserId("user-id");

// Create an event with a user id:
var analyticsEvent = eventFactory.MakeUniversalAnalyticsEvent(
// Required. The user id associated with this event.
// See https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#uid for details.
userId,
// Required. The event category for the event.
// See https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#ec for details.
"test category",
// Required. The event action for the event.
//See https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#ea for details.
"test action",
// Optional. The event label for the event.
// See https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#el for details.
"test label",
// Optional. The event value for the event.
// See https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#ev for details.
"10");
```

To create an event without using `ClientId` or `UserId` objects:

```c#
var analyticsEvent = eventFactory.MakeUniversalAnalyticsEvent(
// Required (if not using userId). The client id for this event.
// See https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#cid for details.
"35009a79-1a05-49d7-b876-2b884d0f825b",
// Required. The event category for the event.
// See https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#ec for details.
"test category",
// Required. The event action for the event.
//See https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#ea for details.
"test action",
// Optional. The event label for the event.
// See https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#el for details.
"test label",
// Optional. The event value for the event.
// See https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#ev for details.
"10",
// Required (if not using clientId). The user id associated with this event.
// See https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#uid for details.
"user-id");
```

Finally, push the event to Google Analytics using the EventTracker:

```c#
var trackingResult = eventTracker.TrackEvent(analyticsEvent);

// Note that exceptions are contained in the result object and not thrown for stability.
if (trackingResult.Failed)
{
// Log to the appropriate error handler.
Console.Error.WriteLine(trackingResult.Exception);
}
```

## Notes
The code is almost entirely unit/integration tested so it should be stable and easily updatable. I'm using it on my own site right now so you can find more specific examples at: https://github.com/jakejgordon/NemeStats

For your own application you will probably want to create an additional wrapper over top of this so you can confine the EventCategory and EventAction values to something that makes sense for your own app (without having to hard-code magic strings for the parameters). My website has examples of this as well.

![Alt text](https://raw.githubusercontent.com/jakejgordon/Universal-Analytics-For-DotNet/master/universal_analytics_realtime_events_screenshot.jpg?raw=true "Screenshot of Real-Time Events After Pushing Data")

# NuGet
## NuGet
[Check out the NuGet page for this package](https://www.nuget.org/packages/UniversalAnalyticsMeasurementProtocolWrapper/)


57 changes: 40 additions & 17 deletions Source/UniversalAnalyticsHttpWrapper/EventTracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ namespace UniversalAnalyticsHttpWrapper
/// </summary>
public class EventTracker : IEventTracker
{
private readonly IPostDataBuilder postDataBuilder;
private readonly IGoogleDataSender googleDataSender;
private readonly IPostDataBuilder _postDataBuilder;
private readonly IGoogleDataSender _googleDataSender;

/// <summary>
/// This is the current Google collection URI for version 1 of the measurement protocol
/// </summary>
Expand All @@ -24,8 +25,8 @@ public class EventTracker : IEventTracker
/// </summary>
public EventTracker()
{
postDataBuilder = new PostDataBuilder();
googleDataSender = new GoogleDataSender();
this._postDataBuilder = new PostDataBuilder();
this._googleDataSender = new GoogleDataSender();
}

/// <summary>
Expand All @@ -35,30 +36,52 @@ public EventTracker()
/// <param name="googleDataSender"></param>
internal EventTracker(IPostDataBuilder postDataBuilder, IGoogleDataSender googleDataSender)
{
this.postDataBuilder = postDataBuilder;
this.googleDataSender = googleDataSender;
this._postDataBuilder = postDataBuilder;
this._googleDataSender = googleDataSender;
}

/// <summary>
/// Pushes an event up to the Universal Analytics web property specified in the .config file.
/// Tracks the event and puts the result in a container object.
/// </summary>
/// <param name="analyticsEvent">The event to be logged.</param>
public void TrackEvent(IUniversalAnalyticsEvent analyticsEvent)
{
string postData = postDataBuilder.BuildPostDataString(MEASUREMENT_PROTOCOL_VERSION, analyticsEvent);
/// <param name="analyticsEvent"></param>
/// <returns>The result of the tracking operation</returns>
public TrackingResult TrackEvent(IUniversalAnalyticsEvent analyticsEvent)
{
var result = new TrackingResult();

try
{
string postData = this._postDataBuilder.BuildPostDataString(MEASUREMENT_PROTOCOL_VERSION, analyticsEvent);
this._googleDataSender.SendData(GOOGLE_COLLECTION_URI, postData);
}
catch (Exception e)
{
result.Exception = e;
}

googleDataSender.SendData(GOOGLE_COLLECTION_URI, postData);
return result;
}

/// <summary>
/// Pushes an event up to the Universal Analytics web property specified in the .config file.
/// Tracks the event and puts the result in a container object.
/// </summary>
/// <param name="analyticsEvent">The event to be logged.</param>
public async Task TrackEventAsync(IUniversalAnalyticsEvent analyticsEvent)
/// <param name="analyticsEvent"></param>
/// <returns>The result of the tracking operation</returns>
public async Task<TrackingResult> TrackEventAsync(IUniversalAnalyticsEvent analyticsEvent)
{
var postData = postDataBuilder.BuildPostDataCollection(MEASUREMENT_PROTOCOL_VERSION, analyticsEvent);
var result = new TrackingResult();

try
{
var postData = this._postDataBuilder.BuildPostDataCollection(MEASUREMENT_PROTOCOL_VERSION, analyticsEvent);
await this._googleDataSender.SendDataAsync(GOOGLE_COLLECTION_URI, postData);
}
catch (Exception e)
{
result.Exception = e;
}

await googleDataSender.SendDataAsync(GOOGLE_COLLECTION_URI, postData);
return result;
}
}
}
7 changes: 4 additions & 3 deletions Source/UniversalAnalyticsHttpWrapper/GoogleDataSender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ public GoogleDataSender()

public void SendData(Uri googleCollectionUri, string postData)
{
if (String.IsNullOrEmpty(postData))
if (string.IsNullOrEmpty(postData))
throw new ArgumentNullException("postData", "Request body cannot be empty.");

HttpWebRequest httpRequest = WebRequest.CreateHttp(googleCollectionUri);
var httpRequest = WebRequest.CreateHttp(googleCollectionUri);
httpRequest.ContentLength = Encoding.UTF8.GetByteCount(postData);
httpRequest.Method = "POST";
using(Stream requestStream = httpRequest.GetRequestStream())

using(var requestStream = httpRequest.GetRequestStream())
{
using (var writer = new StreamWriter(requestStream))
{
Expand Down
16 changes: 8 additions & 8 deletions Source/UniversalAnalyticsHttpWrapper/IEventTracker.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq;
using System.Threading.Tasks;

namespace UniversalAnalyticsHttpWrapper
Expand All @@ -12,14 +9,17 @@ namespace UniversalAnalyticsHttpWrapper
public interface IEventTracker
{
/// <summary>
///
/// Tracks the event and puts the result in a container object.
/// </summary>
/// <param name="analyticsEvent"></param>
void TrackEvent(IUniversalAnalyticsEvent analyticsEvent);
/// <returns>The result of the tracking operation</returns>
TrackingResult TrackEvent(IUniversalAnalyticsEvent analyticsEvent);

/// <summary>
///
/// Tracks the event and puts the result in a container object.
/// </summary>
/// <param name="analyticsEvent"></param>
Task TrackEventAsync(IUniversalAnalyticsEvent analyticsEvent);
/// <returns>The result of the tracking operation</returns>
Task<TrackingResult> TrackEventAsync(IUniversalAnalyticsEvent analyticsEvent);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,9 @@ public interface IUniversalAnalyticsEvent
/// Gets the event value for this event.
/// </summary>
string EventValue { get; }
/// <summary>
/// Gets the optional user Id for this event.
/// </summary>
string UserId { get; }
}
}
Loading

0 comments on commit e5d70cb

Please sign in to comment.