UPDATE 1: Apparently
HttpContext.Current.Request.AnonymousID already uses the cookie internally, so there might be something that makes it stop working. I’ll update when I found out.
Today we received a support ticket as customers seeing corrupted carts data being lost – line items with invalid data, duplicated line items etc. “Corrupted data” is one of the alarming words that we take very seriously, so I decided to jump on it right away.
The setup is a load balancing environment, and the problem only happens with anonymous users. However, it can be “fixed” by turning on the sticky sessions mode. So basically, instead of having sessions on the memory of a server (so sessions on server A can’t be seen by server B, and vice versa), they need a mechanism (can be a database) to share sessions between servers.
When looking at their code, I found out what was wrong:
var orderFactory = ServiceLocator.Current.GetInstance<IOrderFactory>();
var contactId = PrincipalInfo.CurrentPrincipal.GetContactId();
var cart = orderFactory.LoadOrCreateCart(contactId, "Default");
This code is actually from Episerver official documentation. Keep in mind that most of sample code from Episerver is not tested in a load balancing configuration – they are supposed to be examples of using the APIs, so if you are running on load balancer, just don’t take sample code as-is, without testing. Load balancing is hard!
PrincipalInfo.CurrentPrincipal.GetContactId() will return the contact id of the current user, if he or she is registered. Otherwise it’ll create a new
Guid value, based on
That explains why the problem only happens with anonymous users, and why sticky session fixes it. When an anonymous user’s request is sent to another server,
GetContactId sees that as a different user and returns a new
Guid value. That ends up
IOrderFactory.LoadOrCreateCart to create a new cart instead of loading the existing one. (if you don’t already know, then a cart is supposed to be unique with a combination of name, customer id, and the market it’s in, so you can’t have two carts with same name of “default”, for same customer, and in same market, but you can have two carts for same customer with same name, if they are in “US” and “SWE” markets). Sticky session fixes that by ensuring that every server sees a same
AnonymousID , therefore same cart is loaded correctly, and the problem disappears.
So can we just stick with sticky sessions, and be done with it?
Well, it depends. Sticky session is not without overhead, and it limits the ability for you to scale up. If you add more servers to your load balancer, your session server can easily be the single point of failure. In most of the cases, you won’t want that. Also, enabling sticky session to fix this specific issue is a bit overkill.
So is there an effective, nice way to fix the issue? Well, in this case, I think cookie is a very good choice – it’s lightweight, it’s easy to work with, and it solve the load balancing problem nicely.
public static class PersistentCustomerId
const string CookieKey = "AnonymousId";
public static Guid GetCustomerId()
var cookie = HttpContext.Current.Request.Cookies[CookieKey];
if (cookie == null)
var anonynousId = HttpContext.Current.Request.AnonymousID;
cookie = new HttpCookie(CookieKey, anonynousId);
cookie.Expires = DateTime.UtcNow.AddDays(30);
return new Guid(anonynousId);
return new Guid(cookie.Value);
I’m using a static class here, but you can easily change it to a normal class, or an extension of
IPrincipal , to replace the
GetContactId (you can name it
GetPersistentContactId, for instance). The idea is simple: if the current user is authenticated (i.e. logged in) just return the normal contact id. Otherwise check if the current request has a cookie with specific name. If there is not (first request), we create one and add back to the response. Otherwise, just take that cookie value and return.
Will Episerver include this in the framework? I doubt it. Using cookie is an implementation and it does not work in every case (customers might choose to reject cookies, rendering this broken). For the time being, make sure to take the sessions into consideration if you are running on load balancing configuration.
Shameless plug: this will be converted to a recipe, in my new book. If this blog post helps you, consider support my book. Thank you!