Created
October 10, 2018 04:23
-
-
Save debpu06/09eeb2c64bb7a8a9f678bbe1cdc7399a to your computer and use it in GitHub Desktop.
Our API for tracking store interactions using Episerver Insights
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
For information about how we implemented this in our project:
https://davidboland.site/blog/track-users-in-store-with-episerver-profile-store/