Sitecore xDB: Associate Media Download Event to a Contact


In one of my recent articles , I have explained how to create a custom contact facet. This article outlines how we make use of this custom facet to associate media download events to a contact.

PDF_Downloads

Sitecore uses Sitecore.Resources.Media.MediaRequestHandler configured under section to process requests to media content and generate a response for the request resource. This would be an ideal place to track media download event.


	...
	
		...
		
		...
	
	...

The following list goes over the details about my implementation:

  1. Create a copy of MediaRequestHandler using decomplier and name it as XCMediaRequestHandler.cs
  2. Navigate to DoProcessRequest method which is responsible for processing the request
            /// 
            /// Performs the actual request processing.
            /// 
            /// 
            /// The context.
            ///             
            /// 
            /// The do process request.
            /// 
            /// 
            protected virtual bool DoProcessRequest(HttpContext context)
            {
                ...
            }
    
  3. We will add our code snippet here by using the available information about the media item
  4.  Since we need to track media (*.pdf) downloads only for the extranet site, I added a condition to ignore requests coming from Sitecore client
    string hostName = context.Request.UrlReferrer != null
                                    ? context.Request.UrlReferrer.Host
                                    : context.Request.Url.Host;
    
                var siteName = Context.Site != null ? Context.Site.Name.ToLower() : string.Empty;
    
                if (string.IsNullOrEmpty(url))
                {
                    //Add Media Download event to xDB only if the media type is PDF and
                    // do not track pdf documents requested from Sitecore Client
                    if (media1.MimeType.Equals("application/pdf")
                            && Context.Site != null
                            && !siteName.Equals("shell"))
                    {
                        RegisterMediaDownloadEvent(url, hostName, siteName, media1.MediaData.MediaItem.ID.Guid);
                    }
    
                    return DoProcessRequest(context, mediaRequest, media1);
                }
    
  5.  In this helper method, I am doing a check to see if the user is authenticated or anonymous.
    • Sitecore.Context.User.IsAuthenticated is true: Pass the identifier, I was using username (unique)
    • Sitecore.Context.User.IsAuthenticated is false: Pass the Sitecore analytics global cookie from which we can get the contactId
            protected void RegisterMediaDownloadEvent(string mediaUrl, string referrerSite, string siteName, Guid mediaId)
            {
                //if Sitecore.Context.User is authenticated
                if (Context.User.IsAuthenticated)
                {
                    string userName = Context.GetUserName();
                    MediaDownloadsXdbUtil.AddMediaDownloadDetails(mediaUrl, userName, referrerSite, siteName, mediaId, true);
                }
                else
                {
                    //if Sitecore.Context.User is authenticated
                    string[] delimiter = { "|" };
                    string analyticsCookie = string.Empty;
                    string analyticsGlobalCookie = WebUtil.GetCookieValue(SC_ANALYTICS_GLOBAL_COOKIE);
    
                    if (!string.IsNullOrEmpty(analyticsGlobalCookie))
                    {
                        analyticsCookie = analyticsGlobalCookie.Split(delimiter, StringSplitOptions.RemoveEmptyEntries)[0];
                    }
    
                    if (!string.IsNullOrEmpty(analyticsCookie))
                    {
                        MediaDownloadsXdbUtil.AddMediaDownloadDetails(mediaUrl, analyticsCookie, referrerSite, siteName, mediaId, false);
                    }
                }
            }
    
  6.  I have created a separate class to how to pass this information to shared session and then to the collection database (xDB)
  7. We make use of ContactManager and ContactRepository to make this feature work
    • ContactManager: We use this if we want to update analytics database without ending user's session. All our updates will be stored in shared session and will get added to xDB at the end of the session
    • ContactRepository: We use this we want to interact with xDB directly
            private static ContactManager contactManager = Factory.CreateObject("tracking/contactManager", true) as ContactManager;
            private static ContactRepository contactRepository = Factory.CreateObject("tracking/contactRepository", true) as ContactRepository;
    
  8. We need to obtain a lock on the contact before we add our custom facet information to contact
            
                var contactId = contact.ContactId;
                lockResult = contactManager.TryLoadContact(contactId);
    
    • This will lock the contact in xDB and loads it into shared session. It is again locked in shared session so that our current thread can work with the contact
    • We can except any of the following responses from this method
      1. LockAttemptStatus.Success: Contact has been locked successfully and we can work on updates
      2. LockAttemptStatus.NotFound: Contact cannot be found
      3. LockAttemptStatus.AlreadyLocked: It is already locked by another process and we cannot update
      4. LockAttemptStatus.DatabaseUnavailable: Database is down
  9. Once we obtain a successful lock, use the available information for the requested media item and add it to the facet
                var currentDownload = mediaDownloads.Downloads.Create();
                currentDownload.MediaId = mediaId.Equals(Guid.Empty) ? Guid.NewGuid() : mediaId;
                currentDownload.MediaName = mediaName;
                currentDownload.MediaURL = urlToTrack;
                currentDownload.Time = DateTime.Now.ToShortTimeString();
                currentDownload.DateTime = DateTime.Now.ToLocalTime();
                currentDownload.Referrer = urlReferrer;
    
  10. Now that we have the facet information updated, we need to release the contact. For this, we use ContactManager
                try
                {
                    //save and release contact
                    contactManager.SaveAndReleaseContact(contact);
                    Log.Debug(string.Format("MediaDownloadsXdbUtil: Media Download event successfully added to xDB. URL:{0} and userName: {1}", urlToTrack, contact.Identifiers.Identifier), new object());
                }
                catch (Exception e)
                {
                    Log.Error(string.Format("MediaDownloadsXdbUtil: There was an exception while adding the current Media Download event to xDB. Sitecore.Context.Site:{0}, UrlToTrack: {1}, ContactId:{2}",
                                                Sitecore.Context.Site != null
                                                    ? Sitecore.Context.Site.Name
                                                    : string.Empty,
                                                urlToTrack,
                                                contact.ContactId),
                                            e,
                                            new object());
                }
    
    

Once the session expires (default session timeout value 20 minutes), you will be able to see this facet information on the contacts collection.

MediaDownloadsfacetinXDB

This has been implemented and tested on Sitecore version 8.2.3. For more detailed information, I have attached the classes below.


Files:


Important Notes:

  • Handle errors gracefully
  • Update web.config to use XCMediaRequestHandler for handling media requests
  • When the user is anonymous, make sure their contact record already exists in xDB, else create a new contact record
            private static Contact GetContactInformationUsingCookie(string contactIdCookie)
            {
                Guid contactId;
                Contact contact = null;
    
                if (Guid.TryParse(contactIdCookie, out contactId))
                {
                    //Check if a user with that contactId already exists in xDB
                    contact = contactRepository.LoadContactReadOnly(contactId);
                }
    
                //else create a new contact and flush it to xDB
                if (contact == null && contactId != null)
                {
                    try
                    {
                        contact = contactRepository.CreateContact(contactId);
                        contact.ContactSaveMode = ContactSaveMode.AlwaysSave;
    
                        //flush contact to xDB
                        contactManager.FlushContactToXdb(contact);
                    }
                    catch (Exception e)
                    {
                        Log.Error(string.Format("MediaDownloadsXdbUtil: There was an exception creating a new contact using analytics cookie: {0}", contactIdCookie), e, new object());
                    }
                }
    
                return contact;
            }
    

References:

Categories: Web Development, Sitecore

SEARCH ARTICLES