- Published on
Fault-resistant Sitecore xConnect client operations with just a few lines of code
- Authors

- Name
- Mark Gibbons
- @markgibbons25
Adding retry logic to your Sitecore xConnect client operations
Have you ever had an issue where an xConnect operation such as updating a contact facet has failed? Why not add some fault tolerance? I’ll show you how!
Example without retry logic
Let’s look at some code of a simple contact save that we might be doing without retry logic:
using Newtonsoft.Json;
using Sitecore.Analytics;
using Sitecore.Analytics.Model;
using Sitecore.Analytics.Tracking;
using Sitecore.Diagnostics;
using Sitecore.XConnect;
using Sitecore.XConnect.Client;
using Sitecore.XConnect.Client.Configuration;
using Sitecore.XConnect.Collection.Model;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Yours
{
public class ContactRepository : IContactRepository
{
private readonly ContactManager _contactmanager;
public ContactRepository()
{
_contactmanager = Sitecore.Configuration.Factory.CreateObject("tracking/contactManager", true) as ContactManager;
}
public bool SaveContactData(ContactModel contactModel)
{
try
{
var contactReference = GetIdentifiedContactReference();
using (var client = SitecoreXConnectClientConfiguration.GetClient())
{
var contact = client.Get(contactReference, new ContactExpandOptions(PersonalInformation.DefaultFacetKey));
// Update your contact facets
client.Submit();
ReloadContact();
}
return true;
}
catch (Exception ex)
{
Log.Error("Error saving data to profile", ex, this);
return false;
}
}
private IdentifiedContactReference GetIdentifiedContactReference()
{
if (Tracker.Enabled && !Tracker.Current.IsActive)
{
Tracker.StartTracking();
}
if (Tracker.Current?.Contact == null)
{
Log.Warn("Tracker.Current?.Contact == null", this);
return null;
}
if (Tracker.Current.Contact.IsNew)
{
Tracker.Current.Contact.ContactSaveMode = ContactSaveMode.AlwaysSave;
_contactmanager.SaveContactToCollectionDb(Tracker.Current.Contact);
}
var id = Tracker.Current.Contact.Identifiers.FirstOrDefault();
return Tracker.Current.Contact.IsNew || Tracker.Current.Contact.Identifiers.Count == 0
? new IdentifiedContactReference(Sitecore.Analytics.XConnect.DataAccess.Constants.IdentifierSource, Tracker.Current.Contact.ContactId.ToString("N"))
: new IdentifiedContactReference(id.Source, id.Identifier);
}
private void ReloadContact()
{
if (Tracker.Current?.Session == null)
{
Log.Warn("Tracker.Current?.Contact == null", this);
return;
}
_contactmanager.RemoveFromSession(Tracker.Current.Contact.ContactId);
Tracker.Current.Session.Contact = _contactmanager.LoadContact(Tracker.Current.Contact.ContactId);
}
}
}
Source: gist
Add the XdbRequestPerformer
First we’ll add some magic. It provides some exception handling and retry logic which will retry the operation up to 5 times before giving up.
using System;
using System.Linq;
using System.Runtime.CompilerServices;
using Sitecore.XConnect;
using Sitecore.XConnect.Client.Configuration;
using Sitecore.XConnect.Operations;
namespace Yours
{
public interface IXdbRequestPerformer
{
void RequestWithRetry(Action<IXdbContext> action);
void RequestWithRetry(Action<IXdbContext> action, string actionMessage);
}
public class XdbRequestPerformer : IXdbRequestPerformer
{
private const int XdbRequestRetriesCount = 5;
public void RequestWithRetry(Action<IXdbContext> action)
{
RequestWithRetry(action, string.Empty);
}
public void RequestWithRetry(Action<IXdbContext> action, string actionMessage)
{
using (var xconnectClient = SitecoreXConnectClientConfiguration.GetClient())
{
for (int index = 0; index < XdbRequestRetriesCount + 1; ++index)
{
try
{
action(xconnectClient);
break;
}
catch (Exception ex)
{
var xdbOperations = xconnectClient?.LastBatch?.Where(x => x.Status == XdbOperationStatus.Failed);
var str = string.IsNullOrEmpty(actionMessage) ? string.Empty : "of '" + actionMessage + "'";
if (ex is XdbUnavailableException || xdbOperations != null && xdbOperations.Any())
{
if (index == XdbRequestRetriesCount)
{
Sitecore.Diagnostics.Log.Error(FormattableString.Invariant(FormattableStringFactory.Create("Request {0} is failed during xDB operation. (Message: {1})", str, ex.Message)), ex, typeof(XdbRequestPerformer));
throw;
}
else
Sitecore.Diagnostics.Log.Warn(FormattableString.Invariant(FormattableStringFactory.Create("Request {0} is failed during xDB operation. Trying to retry {1} time... (Message: {2})", str, (index + 1), ex.Message)), ex, typeof(XdbRequestPerformer));
}
else
{
Sitecore.Diagnostics.Log.Error(FormattableString.Invariant(FormattableStringFactory.Create("Request {0} is failed. Retry will not be performed because the issue is probably caused not during xDB operation... (Message: {1})", str, ex.Message)), ex, typeof(XdbRequestPerformer));
throw;
}
}
}
}
}
}
}
Source: gist
Updating the example to use the retry code
With just a few lines of code you can change it to use the XdbRequestPerformer:
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Sitecore.Analytics;
using Sitecore.Analytics.Model;
using Sitecore.Analytics.Tracking;
using Sitecore.Diagnostics;
using Sitecore.XConnect;
using Sitecore.XConnect.Client;
using Sitecore.XConnect.Collection.Model;
namespace Yours
{
public class ContactRepository : IContactRepository
{
private readonly ContactManager _contactmanager;
private readonly XdbRequestPerformer _xdbRequestPerformer;
public ContactRepository(IXdbRequestPerformer xdbRequestPerformer)
{
_contactmanager = Sitecore.Configuration.Factory.CreateObject("tracking/contactManager", true) as ContactManager;
_xdbRequestPerformer = xdbRequestPerformer;
}
public bool SaveContactData(ContactModel contactModel)
{
try
{
var contactReference = GetIdentifiedContactReference();
_xdbRequestPerformer.RequestWithRetry(client =>
{
var contact = client.Get(contactReference, new ContactExpandOptions(PersonalInformation.DefaultFacetKey));
// Update your contact facets
client.Submit();
ReloadContact();
}, "SaveContactData");
return true;
}
catch (Exception ex)
{
Log.Error("Error saving data to profile", ex, this);
return false;
}
}
private IdentifiedContactReference GetIdentifiedContactReference()
{
if (Tracker.Enabled && !Tracker.Current.IsActive)
{
Tracker.StartTracking();
}
if (Tracker.Current?.Contact == null)
{
Log.Warn("Tracker.Current?.Contact == null", this);
return null;
}
if (Tracker.Current.Contact.IsNew)
{
Tracker.Current.Contact.ContactSaveMode = ContactSaveMode.AlwaysSave;
_contactmanager.SaveContactToCollectionDb(Tracker.Current.Contact);
}
var id = Tracker.Current.Contact.Identifiers.FirstOrDefault();
return Tracker.Current.Contact.IsNew || Tracker.Current.Contact.Identifiers.Count == 0
? new IdentifiedContactReference(Sitecore.Analytics.XConnect.DataAccess.Constants.IdentifierSource, Tracker.Current.Contact.ContactId.ToString("N"))
: new IdentifiedContactReference(id.Source, id.Identifier);
}
private void ReloadContact()
{
if (Tracker.Current?.Session == null)
{
Log.Warn("Tracker.Current?.Contact == null", this);
return;
}
_contactmanager.RemoveFromSession(Tracker.Current.Contact.ContactId);
Tracker.Current.Session.Contact = _contactmanager.LoadContact(Tracker.Current.Contact.ContactId);
}
}
}
Source: gist
And that’s it!
Another alternative
You could also use Polly to give much the same, with also only a few lines of code. However I think in this particular example it’s going to be easier to use the XdbRequestPerformer which has some additional smarts around batch operations where some operations succeeded and some failed.