31Oct, 2024
Lessons Learned From Troubleshooting GeoIp Results in Sitecore
We are closing in on the final phases of a Sitecore upgrade, which leverages GeoIp for CTAs and branding throughout the site including the global navigation. Invariably, we get reports from the testing team of inaccurate results. Today I will walk through the validation and tricks I've used when I need to support someone without being at their same location.
How Do We Use GeoIp?
In this post I'm going to use one of MNP's website, MNPDebt.ca. I work at MNP Digital where we develop and maintain most major WCMS solutions, and I am proud that we use Sitecore for our own websites.
You can see on the MNP Debt site that the nearest office appears to the right of the header's logo (along with its phone number). This is handy for all you Canadian Users out there, and we only have accurate results for domestic visitors.
The lookup operation leverages GeoIp and then retrieves the nearest office, which is stored as a datasource. All offices are pushed to Sitecore through an integration as they're maintained in another system, but that's another story.
Each office has their latitude and longitude, so the following snippet does all the work:
var result = officeItems .Select(item => new { Item = item, Distance = Distance(origin, GetLocation(item)) }) .Where(item => item?.Distance.HasValue ?? false) .OrderBy(item => item.Distance) .FirstOrDefault()?.Item; public double? Distance(ILocation A, ILocation B) { if (A.Longitude.HasValue && A.Latitude.HasValue && B.Longitude.HasValue && B.Latitude.HasValue) return Math.Sqrt(Math.Pow(B.Longitude.Value - A.Longitude.Value, 2) + Math.Pow(B.Latitude.Value - A.Latitude.Value, 2)); else return null; }
Managing Efficient GeoIp Calls
The above works well but the call to GeoIp can be a little slow, and on first page load there is a chance a location isn't rendered. To cut down on calls and reduce the need for a lookup on each page load, the office object is stored in a session.
We have use cases where an office may not service all languages, so a language-specific session object is created:
var sessionName = $"mnpdebt-nearestoffice-{Context.Language?.ToString()}"; var nearestOfficeFromSession = GetNearestOfficeFromSession(sessionName); if (nearestOfficeFromSession != null) ...
How to Know What's Going on When a Problem Is Reported
As with any large org our devices will be routed through a few different ways before the User is exposed to the WAN. VPN is one obvious measure, but there can be other security appliances which I won't divulge here. Use your imagination.
We have Users who are testing the new version of the site without realizing that such things as VPN can affect their determined location. The above session, for instance, would keep someone in Toronto if they are truly in Ottawa even after disconnecting from VPN.
To help with testing I set the foundation layer to check for a query string (something like debug-geo=true, but I'm keeping that private). When enabled, the site skips the session object and also looks for a passed IP by query string in case we want to simulate a different location (which is optional).
When an Interaction Gets in the Way of Testing
This case hadn't come up yet, but we had a User not use these parameters, and were still seeing the wrong location in their header. I asked for their IP checked in logs that in fact the right IP was being used servicing their pages. I validated the latitude and longitude using MaxMind which lined up with other tests but still wrong results. What's going on?
One thing that comes to mind is that this is all dependent on accurate GeoIp data, and in our solution, there is a check and use of the UpdateGeoIpData method if there is nothing found:
if (!interaction.HasGeoIpData) interaction.UpdateGeoIpData();
This line of code isn't properly updating their location, but I thought I was on the right track. To get some more answers I decompiled Sitecore.Analytics.dll, and checked the UpdateGeoIpData method. The beginning of the method gave me something to pursue:
public override bool UpdateGeoIpData(TimeSpan timeout) { VisitData visitData = this._visitData; if (visitData.WhoIsInformation != null) return true;
Clearly, if the interaction's WhoIsInformation is already set, it's not going to refresh. What's going on is my User's location changed during testing due to routing, but this is cached. The following change was made when debugging (by query string) is enabled, which provided the right location and an immediate sigh of relief:
if (testGeoIp) { var whoIsInformation = LookupManager.GetWhoIsInformationByIp(HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"]); interaction.SetWhoIsInformation(whoIsInformation); interaction.UpdateGeoIpData(); }