Skip to content

Instantly share code, notes, and snippets.

@debpu06
Created October 10, 2018 04:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save debpu06/09eeb2c64bb7a8a9f678bbe1cdc7399a to your computer and use it in GitHub Desktop.
Save debpu06/09eeb2c64bb7a8a9f678bbe1cdc7399a to your computer and use it in GitHub Desktop.
Our API for tracking store interactions using Episerver Insights
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
using EPiServer.Commerce.Catalog.ContentTypes;
using EPiServer.Core;
using EPiServer.Personalization;
using EPiServer.Reference.Commerce.Site.Features.Login.Services;
using EPiServer.Reference.Commerce.Site.Features.Recommendations.Constants;
using EPiServer.Reference.Commerce.Site.Features.Recommendations.Hubs;
using EPiServer.Reference.Commerce.Site.Features.Shared.Extensions;
using EPiServer.Reference.Commerce.Site.Features.Start.Pages;
using EPiServer.Tracking.Commerce.Data;
using EPiServer.Tracking.Core;
using EPiServer.Web.Routing;
using Mediachase.Commerce.Catalog;
using Mediachase.Commerce.Customers;
using Microsoft.AspNet.SignalR;
using Newtonsoft.Json;
using RestSharp;
namespace EPiServer.Reference.Commerce.Site.Features.Recommendations.Controllers
{
[RoutePrefix("api/track")]
public class TrackController : ApiController
{
private readonly HttpContextBase _httpContextBase;
private readonly UserService _userService;
private readonly IContentLoader _contentLoader;
private readonly ReferenceConverter _referenceConverter;
private readonly UrlResolver _urlResolver;
private readonly ITrackingService _trackingService;
private static readonly string apiBaseUrl = ConfigurationManager.AppSettings["episerver:profiles.ProfileStoreApiBaseUrl"];
private static readonly string subscriptionKey = ConfigurationManager.AppSettings["episerver:profiles.ProfileStoreSubscriptionKey"];
private static string _deviceId;
public TrackController(ITrackingService trackingService, HttpContextBase httpContextBase, UserService userService, IContentLoader contentLoader, ReferenceConverter referenceConverter, UrlResolver urlResolver)
{
_trackingService = trackingService;
_httpContextBase = httpContextBase;
_userService = userService;
_contentLoader = contentLoader;
_referenceConverter = referenceConverter;
_urlResolver = urlResolver;
}
/// <example>GET: api/track/product/fasdkjdf904034</example>
/// <summary>
/// Gets information about a product being interacted with
/// </summary>
/// <param name="beaconId">Id of the bluetooth beacon</param>
/// <returns>Information about a product</returns>
[HttpGet]
[Route("product/{beaconId}")]
public IHttpActionResult GetProductData(string beaconId)
{
string code = GetCode(beaconId);
if (string.IsNullOrWhiteSpace(code))
{
return NotFound();
}
object productObject = GetProductObject(code);
if (productObject == null)
{
return NotFound();
}
return Ok(productObject);
}
/// <example>POST: api/track/product/fasdkjdf904034</example>
/// <summary>
/// Tracks when a customer views a product in the store
/// </summary>
/// <param name="beaconId">Id of the bluetooth beacon</param>
/// <returns>Message that is recorded in EPiServer Insights</returns>
[HttpPost]
[Route("product/{beaconId}")]
public async Task<IHttpActionResult> TrackProductView(string beaconId)
{
var user = GetUser();
string code = GetCode(beaconId);
if (string.IsNullOrWhiteSpace(code))
{
return NotFound();
}
var contentLink = _referenceConverter.GetContentLink(code);
var productName = code;
if (!ContentReference.IsNullOrEmpty(contentLink))
{
var product = _contentLoader.Get<EntryContentBase>(contentLink);
productName = product.Name;
}
var trackingData = new TrackingData<ProductTrackingData>
{
EventType = CustomTrackingType.Store,
EventTime = DateTime.UtcNow,
User = user,
Value = $"Viewed {productName} in the Las Vegas store.",
Payload = new ProductTrackingData(code, "en", _httpContextBase, GetCommerceUser()),
PageTitle = productName,
};
await _trackingService.Track(trackingData, _httpContextBase);
return Ok(trackingData.Value);
}
/// <example>POST: api/track/product/fasdkjdf904034/purchase</example>
/// <summary>
/// Tracks when a product is purchased
/// </summary>
/// <param name="beaconId">Id of the bluetooth beacon</param>
/// <returns>Message that is recorded in EPiServer Insights</returns>
[HttpPost]
[HttpGet]
[Route("product/{beaconId}/purchase")]
public async Task<IHttpActionResult> TrackProductPurchase(string beaconId)
{
var user = GetUser();
string sku = GetCode(beaconId);
if (string.IsNullOrWhiteSpace(sku))
{
return Ok();
}
var contentLink = _referenceConverter.GetContentLink(sku);
var productName = sku;
if (!ContentReference.IsNullOrEmpty(contentLink))
{
var product = _contentLoader.Get<EntryContentBase>(contentLink);
productName = product.Name;
}
var trackingData = new TrackingData<OrderTrackingData>
{
EventType = CustomTrackingType.Store,
EventTime = DateTime.UtcNow,
User = user,
Value = $"Purchased {productName} in the Las Vegas store.",
//Hard coding this order data for demo purposes
Payload = new OrderTrackingData(new List<CartItemData>()
{
new CartItemData(sku, sku, 1, 100)
},
"usd", 100, 10, 110, "12345678", "en", _httpContextBase, GetCommerceUser()),
PageTitle = productName,
};
await _trackingService.Track(trackingData, _httpContextBase);
return Ok(trackingData.Value);
}
/// <example>POST: api/track/store/enter</example>
/// <summary>
/// Tracks when a customer enters the store
/// </summary>
/// <returns>Message that is recorded in EPiServer Insights</returns>
[HttpPost]
[Route("store/enter")]
public async Task<IHttpActionResult> TrackStoreEnter()
{
UserData user = GetUser();
var trackingData = new TrackingData<CustomTrackingType>
{
EventType = CustomTrackingType.Store,
EventTime = DateTime.UtcNow,
User = user,
Value = "Entered the Las Vegas store."
};
await _trackingService.Track(trackingData, _httpContextBase);
ChangeDisplayToLastViewedProduct();
return Ok(trackingData.Value);
}
/// <example>POST: api/track/store/exit</example>
/// <summary>
/// Tracks when a customer exits the store
/// </summary>
/// <returns>Message that is recorded in EPiServer Insights</returns>
[HttpPost]
[Route("store/exit")]
public async Task<IHttpActionResult> TrackStoreExit()
{
var user = GetUser();
var trackingData = new TrackingData<CustomTrackingType>
{
EventType = CustomTrackingType.Store,
EventTime = DateTime.UtcNow,
User = user,
Value = "Exited the Las Vegas store."
};
await _trackingService.Track(trackingData, _httpContextBase);
return Ok(trackingData.Value);
}
/// <summary>
/// Gets the current user
/// </summary>
/// <returns><see cref="UserData"/></returns>
private UserData GetUser()
{
var user = _userService.GetCustomerContact(EPiServerProfile.Current.UserName) ?? new CustomerContact();
return new UserData
{
Email = user.Email ?? EPiServerProfile.Current.Email,
Name = $"{user.FirstName} {user.LastName}"
};
}
/// <summary>
/// Gets the current commerce user
/// </summary>
/// <returns><see cref="CommerceUserData"/></returns>
private CommerceUserData GetCommerceUser()
{
var user = _userService.GetCustomerContact(EPiServerProfile.Current.UserName) ?? new CustomerContact();
return new CommerceUserData()
{
Email = user.Email ?? EPiServerProfile.Current.Email,
Name = $"{user.FirstName} {user.LastName}"
};
}
/// <summary>
/// Gets the product code based on the beacon id
/// </summary>
/// <param name="beaconId">Id of the bluetooth beacon</param>
/// <returns>Product code</returns>
private string GetCode(string beaconId)
{
var startPage = _contentLoader.Get<StartPage>(ContentReference.StartPage);
string data = startPage.BeaconJSON;
var beacon = JsonConvert.DeserializeObject<List<BeaconIdLookup>>(data).FirstOrDefault(x => x.Guid == beaconId);
return beacon?.Code;
}
/// <summary>
/// Gets information about the last product viewed
/// </summary>
/// <returns>Information about a product</returns>
private object GetLastViewedProduct()
{
// TODO: We can actually get the last viewed product from Perform's productRecentlyViewedWidget, but it only returns the
// most recent one, so it won't work for the demo.
// var trackingResult = _recommendationService.TrackProduct(HttpContext, currentProduct.Code, false);
// var lastProductViewed = trackingResult?.GetRecentlyViewedProduct(_referenceConverter);
// return lastProductViewed;
string lastEvents = GetLastEventsAsJson();
var trackedProductCodes = GetTrackingProductCodes(lastEvents);
string code = string.Empty;
foreach (var productCode in trackedProductCodes)
{
if (DemoConstants.MW_PRODUCTS.Contains(productCode))
{
code = productCode;
break;
}
}
if (string.IsNullOrWhiteSpace(code))
{
return null;
}
var productObject = GetProductObject(code);
return productObject;
}
/// <summary>
/// Gets a list of product codes that have been tracked.
/// </summary>
/// <param name="lastEvents">JSON string of tracking information</param>
/// <returns>List of product codes</returns>
private static List<string> GetTrackingProductCodes(string lastEvents)
{
var result = new List<string>();
dynamic trackingResponseObject = JsonConvert.DeserializeObject(lastEvents);
var trackingArray = trackingResponseObject?.items;
foreach (var trackingItem in trackingArray ?? Enumerable.Empty<dynamic>())
{
result.Add(trackingItem?.Payload?.product?.refCode.ToString());
}
return result;
}
/// <summary>
/// Returns a generic object describing a product
/// </summary>
/// <param name="code">Product code</param>
/// <returns>Generic product <see cref="object"/></returns>
private object GetProductObject(string code)
{
object productObject = null;
var contentLink = _referenceConverter.GetContentLink(code);
if (!ContentReference.IsNullOrEmpty(contentLink))
{
var product = _contentLoader.Get<EntryContentBase>(contentLink);
productObject = new
{
code = product.Code,
productName = product.Name,
image = Request.RequestUri.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped) + product.GetAssets<IContentImage>(_contentLoader, _urlResolver).FirstOrDefault()
};
}
return productObject;
}
/// <summary>
/// Gets a JSON string of items tracked in EPiServer Insights
/// </summary>
/// <returns>JSON string of tracking items</returns>
private static string GetLastEventsAsJson()
{
var baseUrl = "/api/v1.0/trackevents";
var client = new RestClient(apiBaseUrl);
var request = new RestRequest(baseUrl, Method.GET);
request.AddHeader("Ocp-Apim-Subscription-Key", subscriptionKey);
//var profileId = GetProfile();
request.AddParameter("$filter", $"EventType eq product and DeviceId eq {_deviceId}");
request.AddParameter("$orderBy", "EventTime DESC");
request.AddParameter("$top", "10");
// Execute the request to get the profile
var getTrackingResponse = client.Execute(request);
return getTrackingResponse?.Content;
}
/// <summary>
/// Gets a cookie's value from the request headers.
/// </summary>
/// <param name="cookieId">Name of the cookie to return</param>
/// <returns>Value of the specific cookie being requested</returns>
private string GetCookieData(string cookieId)
{
if (!Request.Headers.Contains("Cookie"))
{
return string.Empty;
}
var cookies = Request.Headers.GetValues("Cookie");
var cookieDictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var values = cookies.First().TrimEnd(';').Split(';');
foreach (var parts in values.Select(c => c.Split(new[] { '=' }, 2)))
{
var cookieName = parts[0].Trim();
string cookieValue = parts.Length == 1 ? string.Empty : parts[1];
cookieDictionary[cookieName] = cookieValue;
}
return cookieDictionary.FirstOrDefault(x => x.Key.Equals(cookieId)).Value;
}
/// <summary>
/// Sends a signal to the display, which will then change the animation on the screen.
/// </summary>
private void ChangeDisplayToLastViewedProduct()
{
_deviceId = GetCookieData("_madid");
var lastViewedProduct = GetLastViewedProduct();
string productJson = JsonConvert.SerializeObject(lastViewedProduct, Formatting.Indented);
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<DisplayHub>();
context.Clients.Group(DemoConstants.STORE_GROUP).addNewMessage(productJson);
}
}
/// <summary>
/// Class for a Beacon Id
/// </summary>
public class BeaconIdLookup
{
public string Guid { get; set; }
public string Code { get; set; }
}
}
@debpu06
Copy link
Author

debpu06 commented Oct 19, 2018

For information about how we implemented this in our project:
https://davidboland.site/blog/track-users-in-store-with-episerver-profile-store/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment