Blog

Part 2: Experience Profile – Multi-site, Multi-domain tracking

This blog examines a way in which you can effectively track a user across multiple sites with different domains. It demonstrates how visitor data collected across different top-level domains can be merged together. Merged visitor data provides a much clearer picture of a users movements (experience profile timeline) when a brand consists of many different websites.

You can view Part 1, Part 3 and  Part 4 via the respective links.


Note:  Tracking across sub-domains is a much easier task in Sitecore and is solved using the setting “Analytics.CookieDomain”. The problem this blog is solving is about top level domains not being able to identify and merge visits. This is due to the fact that top-level domains cannot access each others analytics cookie (for very valid security reasons). The problem is summarised well here.


Why is this necessary?

To start off with let’s define the current problem that required this solution.

Problem Definition:

Given that two websites are hosted in Sitecore under unique top level domains. If a visitor goes to http://www.abc.com.au and then visits http://www.def.com.au the same visitor is not linked and the Experience Profile shows two different visitors.

Solution:

The solution Aceik came up with for this is best depicted with a simple diagram.

GlobalCookie

(download a bigger diagram)

The different technologies at play in the diagram include:

  1. Multiple Sitecore Sites
  2. Google Analytics or GTM
  3. IFrames – running javascript, using PostMessage, with domain security measures.
  4. Cookies – Top Level Domain

What is happening in Simple Terms?

Essentially a user visits any one of your Sitecore sites and we tell XDB how it can identify that user by using the assigned Google Client ID.

To do this we use a combination of IFrame message passing between a group of trusted sites and cookies that give us the ability to go back and get the original Client ID for reuse.

By reusing the same Google Client ID on all Sitecore sites we can produce a more complete user profile. One that will contain visit data from each website. The resulting timeline of events in the Experience Profile will now display a federated view across many domains.


A More Technical Explanation

This is a more detailed Technical explanation. Let’s break it down into the sequence of steps a user could trigger and the messages being passed around.

First visit to “def.com.au”:

  1. User navigates to “def.com.au”.
  2. Google analytics initializes on page load with a random Client ID (CID) assigned.
  3. Async JavaScript is used to inject an invisible IFrame into the page. The IFrame URL is pointed at “abc.com.au/cookie”.
  4. Once the IFrame has completed loading the URL JavaScript obtains the CID from Google and uses “PostMessage” to pass it through to the “Global CID Cookie”.
  5. The “Global CID Cookie” has no prior value set so is updated with the CID passed in.
  6. The cookie page responds using “PostMessage” and sends back the same CID passed in.
  7. JavaScript on the page “def.com.au” receives the message and stores the CID received in the “Domain CID Cookie”.
  8. JavaScript on the page triggers backend code that will identify the user against the CID and merge all visits for that user.

Later visit to “hij.com.au”

  1. User navigates to “hij.com.au”.
  2. Google analytics initialisers on page load with a random Client ID (CID) assigned.
  3. Async JavaScript is used to inject an invisible IFrame into the page. The IFrame URL is pointed at “abc.com.au/cookie”.
  4. Once the IFrame has completed loading the URL JavaScript obtains the CID from Google and uses “PostMessage” to pass it through to the “Global CID Cookie”.
  5. The “Global CID Cookie” has a prior value, so the CID passed in is not used or set.
  6. The cookie page responds using “PostMessage” and sends back the prior existing CID stored from the first visit to”def.com.au”.
  7. JavaScript on the page “hij.com.au” receives the message and stores the CID received in the “Domain CID Cookie”.
  8. JavaScript on the page triggers backend code that will identify the user against the CID.  The current visit data for  “hij.com.au” is merged with the previous visit data from “def.com.au”.
  9. JavasScript on the page updated the CID stored in Google Analytics so that no other actions use the newer CID generated by “hij.com.au”.
  10. When the page is refreshed the Google Analytics initialisation code checks the “Domain CID Cookie” and passes through the existing CID to google for continued use.

Last visit to “abc.com.au”

  1. Google analytics initialisers on page load, checks for the existence of the “Global CID Cookie” and passes through the existing CID to google for continued use.
  2. Javascript on the page notices that “Global CID Cookie” is set but the “Domain CID Cookie” is not.
  3. JavaScript on the page triggers backend code that will identify the visit against the CID.  The current visit data for  “abc.com.au” is merged with the previous visit data from “def.com.au” and “hij.com.au”.

Second-time visits are an easy win

Once each domain has completed a first pass of checking for a Global CID the code is smart enough that it doesn’t need to repeat all these steps again.

The first pass sets a local domain cookie and this is used for quick access on each subsequent page load. The first pass is also coded in an async way so that it will not have an impact on page load times.

We can also set it up so that the code initialising the google tracker is instantly provided with the multi-domain Client ID straight away.

https://www.useloom.com/share/0b8c9f0a2fbc41c0baa1ae4db5e9ae6b

This loom video shows exactly how we set up the global page view event with two variables. The final variable runs custom javascript that will read our cookie to grab the Client ID.

The javascript to paste into the GTM variable is:

function() {
var getGAPrior = function(name) { var value = "; " + document.cookie; var parts = value.split("; " + name + "="); if (parts.length == 2) return parts.pop().split(";").shift(); }
var localCookie = getGAPrior("LocalCidCookie"); if (typeof localCookie !== "undefined") { return localCookie; }
var globalCookie = getGAPrior("GlobalCidCookie"); if (typeof globalCookie !== "undefined") { return globalCookie; }
return null;
}

What about FXM ?

You could potentially solve this same problem using a customisation around FXM.  It may be possible for FXM to replace the need for IFrame communication.

In order to achieve this, you would need to write a customisation to the Beacon service that allowed a Google Client ID to be sent and received.

However, the main sticking point remains around the need to maintain a global storage area (Global Cookie) that is owned by the user.  Due to the browser limitations (noted by Sitecore) of passing cookies, I’m not entirely sure an FXM replacement will work.

Compare that with browser support for IFrame “PostMessage” and you can see why we travelled down one rabbit hole compared to another.

Reference: https://community.sitecore.net/developers/f/10/t/380


Conclusion

Every website visitor gets assigned a Google Analytics Client ID.  You can use this ID to identify a user in XDB very early on. For multi-site tracking, the Client ID supplied by Google comes in very handy.  By using some clever communication tactics between your sites you can merge all the users visit data into a single profile view. The resulting timeline of events in the Experience Profile should prove very useful to your marketing teams.

To continue reading, jump over to Part 3 where we cover off External Tracking via FXM and Google Client ID.

Top 5 Ways to Extend Sitecore HTML Cache

In 2017 I wrote a reasonably long post on all the different considerations a Sitecore Caching Strategy might cover.

Following on from that post its time to share some custom HTML cache extensions that we at Aceik may incorporate into our projects. This count down of custom settings has been collected from across the Sitecore community.

5) Vary By Personalisation

This one (in my opinion) is a must-have if your incorporating personalisation in your homepage.

I admit it will only be effective up to a certain number of content variations and even then needs to be used with caution. Still, if you can save your server and databases from getting hit and help keep Time to First Byte (TTFB) low, its always worth it.

Please note that if you’re displaying a customised message with data that is only relevant to that user, the number of variations may not make it worthwhile.  On the other hand, if your showing variations based on a handful of profile card match rules, we found it to be fairly effective.

Code Sample: ApplyVaryByPeronalizedDatasource.cs

Credits: Ahmed Okour

4) Vary By Resolution

Its a fairly common scenario that we want to display a different image based on the users screen size. So it stands to reason that we would need a way to differentiate this when it comes to caching our Renderings.

The particular implementation was used in combination with Scott Mulligan’s Sitecore Adaptive Image Library.

The Adaptive Image library stores the users screen resolution via a cookie in the front end razor/javavscript:

document.cookie = '@Sitecore.Configuration.Settings.GetSetting("resolutionCookieName") =' + Math.max(screen.width, screen.height) + '; path=/';
  • The first time around if no cookie is set it uses the default largest image size as the cache key.
  • If the cookie is set the cache incorporates the screen resolution.
args.CacheKey += "_#resolution:" + AdaptiveMediaProvider.GetScreenResolution();

Code 1:  ApplyVaryByResolution.cs

Code 2: AdaptiveMediaProvider.cs 

Credit:  Dadi Zhao

3) Vary By Timeout

This one’s a little different, it requires not only a new checkbox but also a new “Single-Line text” field that allows you to enter a timeout value.  The idea, as you might have guessed, is for the rendering cache to expire after a certain amount of time.

Code 1: ApplyVaryByTimeout.cs

Credit: Dylan Young

2) Vary By Url

An oldy but a goody. I’m a little surprised this one just hasn’t made it into the out of the box product. On the other hand, I can see how it could be overused if you don’t understand the context it applies to. Essentially you can take either the Context Item ID or the raw URL and make your rendering cache vary based on that key.

A good use case for this setting could be for navigation that requires the current page to always be highlighted.

Code 1: ApplyVaryByURLCaching.cs  (Context Item ID formula)

Code 2: ApplyVaryByRawUrlCaching.cs (Raw URL formula)

Credit:  The 10 other people that have blogged about this over the years.

1) Vary By Website

Given Sitecore is an Enterprise content management system we often see multi-site implementations launched on the platform. It makes sense then that you have an option to cache renderings that don’t change all that much on one site but have different content on another.

Example Usage: A global navigation used across all sites that requires some content for the context site to show differently.

Code: ApplyVaryByWebsite.cs

Credit: Younes van Ruth

That rounds out the count down of some of the top ways to extend Sitecore’s out of the box rendering cache. Your renderings will likely use a combination of these settings in order to achieve adequate caching coverage.

For a better idea on how you might add the top 5 above into Sitecore. Please see the technical footnote below. 



 

Technical Footnote:

All these extensions will add an extra checkbox in the Rendering cache tab within Sitecore.

cachesettings

In order for this check box to show up you need to add your custom checkbox fields to the template:

/sitecore/templates/System/Layout/Sections/Caching

You can achieve this in several ways. and there are a lot of other blogs “on the line” that describe how to add in these custom checkboxes so I won’t go into a deep dive here.

With regard to the Helix architecture lets outline one way you could set this up. Aceik has a module in the foundation layer that has all the custom cache checkboxes added to a single template (serialized in Unicorn within that module) . The system template above is then made to inherit from your custom template in order to inherit the custom caching fields.

custom

Aceik – Two MVPs in 2019

This year team Aceik have set ourselves new goals, new challenges and new benchmarks in the Sitecore space. Just one month into 2019 and we’re absolutely thrilled to find these goals already being achieved.

Our New Year’s resolutions are off to a great start with our team already setting the benchmark in global Sitecore excellence. Two of our incredible staff members – Jason Horne and Thomas Tyack, have been awarded with the prestigious Sitecore Most Valuable Professional (MVP) awards for 2019.

MVPs are recognized by Sitecore as being global leaders in Sitecore implementation and for proactively utilising their knowledge within the wider community. Both Jason Horne and Thomas Tyack go above and beyond in their spare time to share their knowledge with their wider networks and to strengthen their own expertise.

So, what makes Thomas and Jason among the global best? Jason Horne is a fantastic leader, 4 times MVP, founder and CEO of Aceik. He also gives back to the Sitecore community by running the only Sitecore meet up group in Melbourne. Thomas Tyack also wears many hats at Aceik. He is the Queensland Technical Lead, Architect and Senior Developer for our team and carries this workload with expert professionalism and advanced skill.

Our passion as a team is reflected in the work we do and we ensure every client is fully across every project, start to finish. Transparency is key in our business and we work with our clients to ensure every project goes above and beyond the benchmark. Over the past five years, we’ve ensured a 100% success rate in project delivery to the highest standard. Our efficient yet diligent work is what makes us stand out from the rest.

We are absolutely thrilled to have two globally-recognised Sitecore MVPs as part of our team, a true accolade to the work that goes on behind the scenes. A huge congratulations to other 2019 MVPs around the world, we can’t wait to see what this year brings!

MVP    jaaaaaaason    Thomas

Part 1 – Experience Profile – Identify Users Early

This is the first part of a four-part blog series where I will introduce some XDB customisation that could be of use on your next Sitecore project. All these customisations relate back to the Experience Profile and identifying the user.

Part 1:  We introduce the concept of early profile identification, there is no such thing as the anonymous user.

Part 2: We dive into the world of multi-site, multi-domain tracking. How to implement a global-common cookie for all your brand’s sites.

Part 3: External Tracking via FXM and Google Client ID – How to continue tracking a user on another website (not hosted in Sitecore).  (Release TBC)

Part 4: How to achieve Instant personalisation on the very first page load. We can make use of the fact that inbound links from a social stream can already identify a users demographic.


 

Part 1:  Identify Users Early

Some of this blog has not been updated for Sitecore 9 yet, other parts have.

In part one I’m going to talk about identifying your visitors as early on in the visit.

By Identifying users I am referring to allowing users to show up in the Experience Profile.

ExperienceProfileButton

By default, Sitecore will not track every single anonymous user that reaches your site.  In order to get them to show up in the Experience Profile, a few quick changes are necessary.

One of the simplest ways to do this is using either WFFM or the new Forms components in Sitecore 9. In fact, the setup hasn’t changed all that much between the version for this particular use case.

Sitecore 9:  Setup forms save actions

Sitecore 8:  Setup save action in WFFM

You can use these save action with any forms on your website that collect personal details. The identification of a user should be happening when a user submits a form that contains personal details. This is a well documented OOTB forms feature that you can set up without any developer intervention.

Another way to identify a user is to do so programmatically by updating the contacts facet details and then calling the identify method on the tracker.

Sitecore 9:  (reference)

Sitecore.Analytics.Tracker.Current.Session.IdentifyAs("sitecoreextranet", "identifier");

Sitecore 8:

Sitecore.Analytics.Tracker.Current.Session.Identify("identifier")
A side by side code example is available here. 

Calling the above line of code with a string identifier associates the visit data with that identifier. When the user visits again on a different device or tracked website if you are able to call the same line of code with the exact same identifier the visitor’s data will be merged into a single Experience Profile record.

Taking the above concept a little bit further we can also track users across non-Sitecore based websites. By using some google smarts and injecting the FXM beacon onto a third party website we can continue to track the user including, page visits, goals, and outcomes.  (this is covered off more in Part 3)


 

No User is Anonymous

Given that we can choose when a user should be identified and displayed in the Experience Profile. Its time to introduce a concept that no user is anonymous. In fact, this is true for the majority of websites in existence, if they use Google Analytics.

Google assigns an identifier called the Client ID to each visitor that comes along to your website.  The Client ID is stored in the GA cookie and has an expiration date of 2 years after creation.

Note: Google also has a concept of User ID that is used to track sessions across devices. The difference is that each website must send this value to Google in order for it to be used. In reality, this is going to most relevant if you only want to identify users in Sitecore if they have performed Authentication. 

We can use Google’s Client ID to allow the user to show up in the Experience Profile as early as is necessary.

To do this setup the following:

  1. Read the Client ID via JavaScript
    • if (typeof ga !== "undefined"){
          cid = ga.getAll()[0].get("clientId");
      }
  2. Send the Client ID to XDB / XConnect via async javascript.  (Github Reference)
    • if (typeof cid !== "undefined") {	
      	var setEventPath = '/api/xdb/Analytics/TriggerEvent/Event/?eventName=updateGoogleCid&data=' + cid;
              $.ajax({
      		type: 'POST',
      		url: setEventPath,
      		dataType: 'json',
      		success: function (json) {
      		      setCookie(cookieName, cid, 1);
      		},
      		error: function () {
      		      console.warn("An error occurred triggering the event");
      		}
      	});
      }
    • The above code assumes a custom Controller was set up to trigger Goals via Ajax/Javascript.  (Github Reference)
  3. Identify the user (See code examples mentioned earlier or look at our example controller)

Note 1:  The above code only needs to be triggered once per visitor. To save this running multiple times you can assign a cookie to the user. By checking if the cookie has been set you can prevent the above process from running more times then necessary. 

Note 2:  In a single site environment you may choose to leave this identification until a certain amount of visit data has been collected.  For example, writing some logic to check that the user has achieved a certain amount of goals.  This will prevent users with little or no data showing the Experience Profile. 

Note 3: In a multi-site environment the opposite to note 2 becomes necessary. With visitors hitting multiple sites you need to identify them as early as possible. The main reason being that you want any visit data collected from the current website merged with visit data from any other site visits. The resulting merged data provides a great overview of a users movements. This will be discussed more in part 2 when we will look into multi-site XDB visitor identification using a Global common cookie.


Experience Profile – First Name, Last Name

This next step is optional. Given that you have identified the user it will now show in the Experience Profile.  At this point, you may not have a first and last name for that visitor. As an alternative, you could split the Client ID into two numbers and use them as the initial values for the first and last name. If the user completes a newsletter signup, logs in or makes an inquiry at a later time, that would be a good opportunity to update these to the correct values.

firstlast.png

(Github Reference)


 

Part 1: Conclusion

We have demonstrated above how you can identify a user so that they show up in the Experience Profile. As part of this, we have looked at how you could use the Client ID from Google to identify the user as early on as you like, potentially on the very first-page load.  In part 2 of this series on Experience Profile customisations we take a look at how to track users across multiple top level domains.

SXA Installation Pitfalls

When installing SXA on a fresh Sitecore instance, things will generally go pretty smoothly, however, when installing SXA on an existing Sitecore instance with customisations, it’s not necessarily quite so simple.  Here’s a summary of some of the issues that were encountered when recently installing SXA 1.4 onto a Sitecore 8.2.4 instance.  Hopefully this post will help if you encounter similar issues.

Dependency Injection

In this instance, there was code that was registering dependencies using wildcards.  Specifically, it was adding (among others) assemblies matching the pattern “*.Feature.*”.  This was wrongly picking up SXA assemblies and was giving errors in the Experience Editor like:

Error Rendering Controller: BrowserTitle. Action: Index: Could not create controller: 'BrowserTitle'.

To fix this, the Dependency Injection code was altered to exclude all assemblies matching the pattern “*.XA.*”.

Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll

The Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll file packaged in SXA 1.4 is an older version than what is being used throughout the solution we’re updating, so when trying to install the package, the installation failed halfway through. To fix this, the SXA install package was altered using 7-zip to replace the included DLL with the newer version.

Missing Rendering IDs (no null check)

This issue may be specific to this instance, and might be due to invalid data, but it’s worth mentioning, since SXA is not doing a null check and this problem may crop up for you, too.

When viewing a particular page, the following exception was thrown:

Exception: System.NullReferenceException
Message: Object reference not set to an instance of an object.
Source: Sitecore.XA.Feature.Composites
at Sitecore.XA.Feature.Composites.Pipelines.GetXmlBasedLayoutDefinition.InjectCompositeComponents.GetCompositeComponents(XElement layoutXml)
at Sitecore.XA.Feature.Composites.Pipelines.GetXmlBasedLayoutDefinition.InjectCompositeComponents.Process(GetXmlBasedLayoutDefinitionArgs args)

This turned out to be some renderings that had no ID, and SXA wasn’t doing a null check. The specific xmlLayout is shown below:


Editing the raw values and removing the renderings with a null ID fixes this problem.

To check if there were any other pages with this same problem, a small powershell script was written:

$items = Get-ChildItem -Path "master:/sitecore/content/Consumer/Home" -Recurse
foreach($item in $items) {
$renderings = Get-Rendering -Item $item
foreach($rendering in $renderings) {
if($rendering.ItemID -eq $null) {
Write-Host Item: $item.DisplayName $item.ID
}
}
}

Media Library/Project Folder

The SXA installation wants to create its own “Media Library/Project” folder, using a specific ID. If this item with the specific ID doesn’t exist, SXA will fail when creating a Tenant or Site.   Your instance may already have a folder by this name, in which case it will need to be renamed before the SXA installation, then it’s contents moved into the folder created by SXA.

Duplicate Navigation Controllers

For this instance, when adding the SXA navigation rendering to a page, the following exception occurs:

Multiple types were found that match the controller named 'Navigation'. This can happen if the route that services this request ('{*pathInfo}') does not specify namespaces to search for a controller that matches the request.

There may be other similar controllers in your instance where you get the same problem.

Geocoding Australian postcodes

While working on some code that allowed a user to perform a search using only a postcode, I discovered some strange behaviour with the Google Maps API.

According to the Google Maps API documentation (https://developers.google.com/maps/documentation/geocoding/intro#ComponentFiltering), component filtering allows you to filter by postal_code and country, which would suit this need perfectly. I gave this a try, and upon initial testing, it seemed that this was the solution, however, after further testing, it was found that for some postcodes (specifically, some in NSW and ACT), the geocoding query would return ZERO_RESULTS. Maybe this is because there is an overlap in the 2000 series postcodes ¯\_(ツ)_/¯.

An example of the URL I was using for this is shown below (note that postcode 2022 and country AU will return ZER0_RESULTS):

http://maps.googleapis.com/maps/api/geocode/json?components=country%3aAU%7Cpostal_code:2022&sensor=false&client=your_client&channel=web&language=en&signature=your_signature

There are many examples on Stack Overflow of people using this format to search by postcode and claim this to be the solution, but most of them are either from other countries, where this probably isn’t an issue, or they mustn’t have discovered this issue.

According to the Google Maps API documentation, you can use administrative_area (among other fields) to “influence” results, so I tried adding the state to this field, and I found that this made everything work properly. That means that the following URL will geocode the postcode 2022:

http://maps.googleapis.com/maps/api/geocode/json?&components=country%3aAU%7Cpostal_code%3a2022%7Cadministrative_area%3aNSW&sensor=false&client=your_client&channel=web&language=en&signature=your_signature

The issue I had then was that if the user is searching only using the postcode, I had to find a way to provide the state for that postcode to Google so it could geocode the postcode properly. To do this, I created a function that gives me the state based on a postcode as shown below (although this is not a perfect solution, because new postcodes are added from time to time. Potentially a call to an Australia Post API or similar may work better going forward):


public static string PostcodeToState(int postcode)
{
var postcodes = new List();
postcodes.AddRange(Enumerable.Range(1000,1000).Select(x => new KeyValuePair("NSW",x)));
postcodes.AddRange(Enumerable.Range(2000, 600).Select(x => new KeyValuePair("NSW", x)));
postcodes.AddRange(Enumerable.Range(2619, 280).Select(x => new KeyValuePair("NSW", x)));
postcodes.AddRange(Enumerable.Range(2921, 79).Select(x => new KeyValuePair("NSW", x)));

postcodes.AddRange(Enumerable.Range(200, 100).Select(x => new KeyValuePair("ACT", x)));
postcodes.AddRange(Enumerable.Range(2600, 19).Select(x => new KeyValuePair("ACT", x)));
postcodes.AddRange(Enumerable.Range(2900, 21).Select(x => new KeyValuePair("ACT", x)));

postcodes.AddRange(Enumerable.Range(3000, 1000).Select(x => new KeyValuePair("VIC", x)));
postcodes.AddRange(Enumerable.Range(8000, 1000).Select(x => new KeyValuePair("VIC", x)));

postcodes.AddRange(Enumerable.Range(4000, 1000).Select(x => new KeyValuePair("QLD", x)));
postcodes.AddRange(Enumerable.Range(9000, 1000).Select(x => new KeyValuePair("QLD", x)));

postcodes.AddRange(Enumerable.Range(5000, 1000).Select(x => new KeyValuePair("SA", x)));

postcodes.AddRange(Enumerable.Range(6000, 798).Select(x => new KeyValuePair("WA", x)));
postcodes.AddRange(Enumerable.Range(6800, 200).Select(x => new KeyValuePair("WA", x)));

postcodes.AddRange(Enumerable.Range(7000, 1000).Select(x => new KeyValuePair("TAS", x)));

postcodes.AddRange(Enumerable.Range(800, 200).Select(x => new KeyValuePair("NT", x)));

postcodes.Add(new KeyValuePair("ACT", 2620));
postcodes.Add(new KeyValuePair("NSW", 3644));
postcodes.Add(new KeyValuePair("NSW", 3707));

return postcodes.Where(x => x.Value == postcode).Select(x => x.Key).FirstOrDefault();
}

 

Responsive Images and Sitecore

responsive

By February 2018 Australians already spent more than double the amount of time on smartphones than on their desktop1. With the greater variety of devices consumers use to access sites, it’s important to serve images which appropriately cater to those devices.

“Responsive images” describes a technique where an image is served to the browser depending (usually) on the width of the browser window. Desktop browsers would generally receive a larger version of the image with a greater download size, and mobile devices a smaller version better suiting the smaller display size of the device and being quicker to download.

One of our clients has a Sitecore 7.2 site which uses both MVC and legacy code in ASP.NET WebForms. The legacy code had multiple ways of handling image resizing, mostly inline HTML hard coding of dimensions. My aim was to provide a single C# library function that could work both with legacy code and new development and allow consistent results.

imagetag

In the newer part of the website, the designer had used a JavaScript based image processor which would allow the browser to determine the optimal resolution of the image required and only request an appropriately sized image from the server.

As you can see from the above HTML sample each image tag has multiple URLs in the data-srcset attribute. From this list the JavaScript library determines the optimal size for the image, based on device and browser size, and only requests that version of the image from the server.

Sitecore provides a tool to provide the resized image files based on the request URL and also caches these resized images. We created a helper function that could take a Sitecore image as input and return all of the URLs representing valid image sizes as a delimited string. The valid sizes are determined by a setting in a config file. This return value is used to populate the data-srcset attribute of our img.

ImageCodeSample

Sitecore 7.5 and beyond require a hash code to be added to the URL2 to prevent Denial of Service attacks by making numerous requests for images of various sizes. Because our new helper function now provides a centralised and configurable way to deliver the required URL string, this will be quite easy to change after the upgrade.

References

  1. http://www.nielsen.com/au/en/press-room/2018/february-2018-digital-ratings.html
  2. http://www.seanholmesby.com/images-not-resizing-in-sitecore-7-5-sitecore-8-0/

Monitoring and Debugging Interaction Processing in Sitecore 9 on Azure PaaS

When configuring a new instance of Sitecore XP or maintaining an existing one, you may encounter a situation where your interactions report shows far fewer interactions than expected.

low-interactions
Where are my interactions?

One possible cause is interaction processing which hasn’t kept up with the interactions being logged on your website. In some cases this can be so slow that it appears collection, processing, and reporting aren’t working at all. Here are a few things you can look at to help you diagnose your issue.

 

Are interactions being recorded?

SELECT TOP 10 * FROM xdb_collection.Interactions ORDER BY StartDateTime ASC
Run this command in each of your shard databases to see the recent interactions which have been recorded. Compare the interactions being logged with the expected number and frequency of interactions in the environment you’re looking at.

 

How many interactions are waiting to be processed?

SELECT COUNT(*) FROM xdb_processing_pools.InteractionLiveProcessingPool
This command will indicate the number of interactions waiting to be processed. Monitoring the number of records in this table can give you an indication of the number of new records being created and the number of new interactions which are being queued for processing.
If the number of records is steadily building up, either processing isn’t working or it’s working too slowly to handle the workload.
If you’re collecting interactions but not seeing the size of the live interaction processing pool change at all, there might be an issue with aggregation.

If Analytics reports don’t look quite right, there are some things you can try:

Disable device detection

We encountered an issue with slow processing on a recent project. After logging an issue with Sitecore support, they advised:
Device detection has been known to cause the slowness in rebuilding reporting DB.
Try disabling device detection to determine if this has been impacting the speed of processing.

 

Check the CPU usage on your processing role

If you’re consistently seeing a high level of activity, you may need to scale your processing instances up or out.

high-average-cpu
Time for more instances…

Check connection strings

Use the Server Role Configuration Reference to ensure you have the correct settings on each of your servers

Check Application Insights errors

Check in Application Insights for any repeated error messages that might indicate misconfiguration.

 

millions-of-interactions
That’s more like it!

Helpful links

Changing renderings from rendering parameters to datasource

Recently, for one of our projects, we found that a large number of the client’s renderings were using rendering parameters unnecessarily. In order to allow for personalisation and a better Experience Editor experience, we recommended to the client that they change these renderings to use datasources instead.

The process (which may vary slightly for your instance) went something like this:
For each rendering to be converted:-

  1. Move the related rendering parameters template from its current folder (which in this case was a folder dedicated to rendering parameters) to a more appropriate folder for data item templates.
  2. Make sure that all sub-items are serialised when the template has been moved – I encountered what seems to be a bug with Unicorn, where it doesn’t serialise them. If you get this, you might need to reserialise, or save each sub-item manually.
  3. If the template currently has a name specific to rendering parameters, rename it to something more appropriate.
  4. If required, make sure that the _Standard Values are set.
  5. Where the rest of your site data lives, create a new folder which will be used to store items of the template in question. Set the insert options to add this template.
  6. Change the rendering’s datasource location to be the newly created folder.
  7. Change the rendering to have no rendering parameters template, and instead use a datasource template of the recently moved template.
  8. Remove the “Standard Rendering Parameters” template from the base templates of the new datasource template.
  9. Add a new item to Core DB with the path something like: /content/Applications/WebEdit/Edit Frame Buttons/Signup Form Button/Signup Form
  10. Set Fields for this new item to be a pipe-delimited list of fields that the content editors will need to edit (generally all of them).
  11. In the Visual Studio solution, move the rendering parameters model into the folder where the data items models live, and rename the file and class name if required.
  12. Change the namespace to match the new location.
  13. Inside the controller method (assuming the rendering is a Controller rendering), remove all code that uses the rendering parameters, and instead, set the datasource to be the RenderingContext.Current.Rendering.DataSource.
  14. Change the view using this rendering:
  15. Add a check for if (Model != null), and display a message if in experience editor mode.
  16. Add “using (BeginEditFrame…” and use the path of the folder for the webedit button you added earlier.
  17. Added a check for Sitecore.Foundation.SitecoreExtensions.Extensions.ItemExtensions.IsDerived using a configuration item for the template ID. Display a message if wrong template and skip the rest of the view.
  18. Changed all references to items in the Model be use @Editable(x => x.)

 

Congratulations! You’re Sitecore Certified

Our developers are Sitecore 9 certified

We’d like to congratulate all our developers who’ve achieved Sitecore® 9.0 Certified Platform Associate Developer certification so far.

Sitecore’s certification exams validate the skills and knowledge of developers, marketers and business users. Test takers who pass the certification exams earn the distinction of being Sitecore Certified Professionals.

At Aceik we work exclusively in Sitecore and have formed a strategic relationship with Sitecore. All our developers are Sitecore certified and work towards certification in the latest Sitecore, are highly qualified and totally dedicated to their projects.

Please contact us for more details or if you want to discuss a project.  All enquiries should be made to info@aceik.com.au or +61 (0)4 2697 1867