Removing port 443 from urls generated by Sitecore

For as long as I’ve been working on Sitecore there has been this really annoying issue where setting the link manager to include server url and running under https will cause urls to be generated with the port number included. e.g. https://www.himynameistim.com:443/ which naturally you don’t actually want.

Aside: This issue was finally fixed in Sitecore 9.1

To overcome this there are a few methods you can take.

Method 1 – Set the Scheme and Port on you site defenition

This is possibly the smallest change you can make as it’s just 2 settings in a config file.

Setting the external port on site node to 80 (yes 80) tricks the link manager code into not appending the port number as it does it for everything other than port 80.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"&gt;
  <sitecore&gt;
    <sites xdt:Transform="Insert"&gt;
      <site name="website"&gt;
        <patch:attribute name="hostName"&gt;www.MySite.com</patch:attribute&gt;
        <patch:attribute name="rootPath"&gt;/sitecore/content/MySite</patch:attribute&gt;
        <patch:attribute name="scheme"&gt;https</patch:attribute&gt;
        <patch:attribute name="externalPort"&gt;80</patch:attribute&gt;
      </site&gt;     
    </sites&gt;
  </sitecore&gt;
</configuration&gt;

What I don’t like about this method though, is your setting something to be wrong to get something else to come out right. It’s all a bit wrong.

Method 2 – Write your own link provider

The second method which I have generally done is to write your own provider which strips the port number off the generated URL.

For this you will need:

1. A patch file to add the provider:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"&gt;
  <sitecore&gt;
    <linkManager defaultProvider="sitecore"&gt;
      <patch:attribute
          name="defaultProvider"
          value="CustomLinkProvider" /&gt;
      <providers&gt;
        <add name="CustomLinkProvider"
             type="MySite.Services.CustomLinkProvider, 
                         MySite"
                       languageEmbedding="never"
                       lowercaseUrls="true"
                       useDisplayName="true"
                       alwaysIncludeServerUrl="true"
          /&gt;
      </providers&gt;
    </linkManager&gt;
    <mediaLibrary&gt;
      <mediaProvider&gt;
        <patch:attribute name="type"&gt;
          MySite.Services.NoSslPortMediaProvider, MySite
        </patch:attribute&gt;
      </mediaProvider&gt;
    </mediaLibrary&gt;
  </sitecore&gt;
</configuration&gt;

2. A helper method that removes the ssl port

namespace MySite
{
    /// <summary&gt; 
    /// Link Helper is used to remove SSL Port 
    /// </summary&gt; 
    public static class LinkHelper
    {
        /// <summary&gt; 
        /// This method removes the 443 port number from url 
        /// </summary&gt; 
        /// <param name="url"&gt;The url string being evaluated</param&gt; 
        /// <returns&gt;An updated URL minus 443 port number</returns&gt; 
        public static string RemoveSslPort(string url)
        {
            if (string.IsNullOrWhiteSpace(url))
            {
                return url;
            }

            if (url.Contains(":443"))
            {
                url = url.Replace(":443", string.Empty);
            }

            return url;
        }
    }
}

3. The custom link provider which first gets the item url the regular way and then strips the ssl port

using Sitecore.Data.Items;
using Sitecore.Links;

namespace MySite
{
    /// <summary&gt;Provide links for resources.</summary&gt; 
    public class CustomLinkProvider : LinkProvider
    {
        public override string GetItemUrl(Item item, UrlOptions options)
        {
            // Some code which manipulates and exams the item...

            return LinkHelper.RemoveSslPort(base.GetItemUrl(item, options));
        }
    }
}

4. The same provider for media

using Sitecore.Data.Items;
using Sitecore.Resources.Media;

namespace MySite
{
    /// <summary&gt; 
    /// This method removes SSL port number from Media Item URLs 
    /// </summary&gt; 
    public class NoSslPortMediaProvider : MediaProvider
    {
        /// <summary&gt; 
        /// Overrides Url mechanism for Media Items 
        /// </summary&gt; 
        /// <param name="item"&gt;Sitecore Media Item</param&gt; 
        /// <param name="options"&gt;Sitecore Media Url Options object</param&gt; 
        /// <returns&gt;Updated Media Item URL minus 443 port</returns&gt; 

        public override string GetMediaUrl(MediaItem item, MediaUrlOptions options)
        {
            var mediaUrl = base.GetMediaUrl(item, options);
            return LinkHelper.RemoveSslPort(mediaUrl);
        }
    }
}

What I don’t like about this method is it’s messy in the opposite way. The port number is still being added, and we’re just adding code to try and fix it after.

Credit to Sabo413 for the code in this example

Method 3 – Official Sitecore Patch

Given that it’s Sitecore’s bug, it does actually make sense that they fix it. After all people are paying a license fee for support! This simplifies your solution down to 1 extra patch file and a dll. What’s better is as it’s Sitecores code they have the responsibility of fixing it, if it ever breaks something, and you have less custom code in your repo.

You can get the fix here for Sitecore version 8.1 – 9.0.

So this may leave you wondering how did Sitecore fix it? Well having a look inside the dll reveals they wen’t for method 2.

Sitecore: Returning a 404 response on the page requested rather than redirecting to a 404 page

Previously I’ve blogged about:

but while looking through the posts today, I realised I had never written about how you stop Sitecore from issuing 302 redirects to your 404 page and instead return a 404 on the URL requested with the contents of the 404 page.

While search engines will recognise a 302 response to a 404 as a 404 (in fact they’re intelligent enough to work out that a 404 page without a correct response status code is a 404) it’s considered SEO best practice for the URL to stay the same and to issue the correct status code.

Creating a NotFoundResolver class

When Sitecore processes a request it will run the httpRequestBegin pipeline, and within that pipeline is a Item Resolver processor that will attempt to find the requested item. If after this the context item is still null then the logic to redirect to the ItemNotFoundUrl will kick in. To stop this happening we can simply add our own process to the pipeline after ItemResolver and set the item.

Our class looks like this:

using Sitecore;
using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Diagnostics;
using Sitecore.Pipelines.HttpRequest;

namespace Pipelines.HttpRequest
{
    public class NotFoundResolver : HttpRequestProcessor
    {
        private static readonly string PageNotFoundID = Settings.GetSetting("PageNotFound");

        public override void Process(HttpRequestArgs args)
        {
            Assert.ArgumentNotNull(args, nameof(args));

            if ((Context.Item != null) || (Context.Database == null))
                return;

            if (args.Url.FilePath.StartsWith("/~/"))
                return;

            var notFoundPage = Context.Database.GetItem(new ID(PageNotFoundID));
            if (notFoundPage == null)
                return;

            args.ProcessorItem = notFoundPage;
            Context.Item = notFoundPage;
        }
    }
}

To add our process to the pipeline we can use a patch file like this:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"&gt;
  <sitecore&gt;
    <pipelines&gt;
      <httpRequestBegin&gt;
        <processor
          patch:after="processor[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']"
          type="LabSitecore.Core.Pipelines.NotFoundResolver, LabSitecore.Core"  /&gt;
      </httpRequestBegin&gt;
    </pipelines&gt;
    <settings&gt;
      <!-- Page Not Found Item Id --&gt;
      <setting name="PageNotFound" value="ID OF YOUR 404 PAGE" /&gt;
    </settings&gt;
  </sitecore&gt;
</configuration&gt;

Notice the setting for the ID of your 404 page to be loaded as the content.

Remember if you do do this make sure you also follow one of the methods to return a 404 status code, otherwise you will have just made every URL a valid 200 response on your site.

What I learnt at Sugcon 2019

This year Sugcon came to London which given that’s where I’m based is awesome for me. In total it was a 3 day conference starting with Sitecore Experience aimed more at marketers than developers. As a developer I only went to the 2 developer days, so for your benefit here’s a summary of everything I saw.

Day 1

Day 1 started with a keynote, sadly life got in the way and I missed the first few hours. I’m told it was good though.

After that the day was split into a mix of sessions in the big room for all and smaller break out sessions where you could pick 1 of 4 to attend.

JSS Immersion – Lessons learned and looking ahead with Anastasiya Flynn

To kick things off I went to a talk on JSS, mostly because JSS is a subject I know very little about. This was something that became even more apparent as the talk went on! At the end of it I came away with an appreciation that I need to invest some time in learning a lot more, but my other take away was a few links on things that will help me out if I ever try some React stuff.

https://www.styled-components.com

https://www.react-spring.io

PAAS It on: Learning’s from a year on Sitecore with Criss Titschinger

Criss works as a dev opps person and over the last year went on the journey of having a Sitecore 8.2 install upgraded to 9 using a fully cloud architecture in Azure.

Overall his experience sounded positive but he did have a few warnings from pain he experienced:

  • Beware of cold start up times with web apps. These can be a real performance hit, especially when Azure decides its going to move your web app instance
  • Web app slots share processing usage so when your warming one up, your live one is taking a hit. If you run on the edge of capacity, this will be an issue
  • Azure search is easy to install but it has a field limitation of 1000 to watch out for
  • Data migration in an upgrade takes a long time the second time. It took 9 days to migrate a years data from mongo! Only do it once.
  • Run your upgrade on clean instances and do the code in visual studio.
  • Web apps need to be on the premium service plan. The others are to weak
  • Use elastic pools for your database to save money. The microservice architecture introduces a LOT of new dbs which are going to cost money in azure resources. Most of the time they also don’t do that much so put them in a pool to share resources
  • Moving to 9 is going to increase hosting charges. Be honest with clients about it.

Day 2

On Day 2 I got to attend from the start so it was a much fuller day for me.

10x your Sitecore development with Mark Cassidy

The day started with a talk on questioning how long it should take to build a Sitecore site. It was a question that never really got answered but the main thing Mark really raised was, do we over engineer what we do and would simpler actually be enough? He went on to show a time lapse video of himself implementing a bootstrap template in Sitecore which took 15 hours.

To build this site he didn’t install any modules (no glass) and used just the standard Sitecore api. As he pointed out, it was all stuff that could be done by a dev with only the basic Sitecore training, which as there’s a short supply of devs in the world, we can potentially make better use of who does what.

Extending and implementing cloud architectures with Rob Habraken

After one talk on cloud the day before I almost gave this one a miss, but I’m glad I didn’t.

Rob gave us some of his learning’s and things to look out for. As the the previous session the theme of Sitecore 9 becoming far more complex came up and he had some interesting takes on it:

  • Use what you need, disable roles that you don’t. I see plenty of Sitecore customers not making use of all the features, and when your in a microservice architecture it does raise the question of why even have this stuff turned on. If you don’t use marketing automation then you don’t need the role running. It’s just costing money to do nothing.
  • Scale down when your not using resource. Unlike a VM web apps can not be turned off so they always cost money. You can delete and recreate, but that’s a pain. Instead set up a pipeline to scale them to the lowest resource setting when not being used.
  • He went on to discuss and show how we can use azure functions and logic apps to implement our code rather than building into the main Sitecore project. However you should be careful overdoing it as it can become complex quickly and it’s easy to end up with a massive unorganised list of individual azure functions.

Automated personalisation with Chris Nash and Niels Kuhnel

Chris and Niels pointed out the flaw in Sitecores reporting on personalised content. How do we know the rate each converts to a goal at? There’s the A/B Test report’s but that’s not quite the same thing.

They went on to show how they had started measuring the display impressions and click through on personalised content. Then linking the results collected in the reporting db up to a Power BI dashboard.

Sitecore identity: A new Sitecore authentication mechanism with Himadri Chakrabarti

Himadri gave us a look at the new Identity Server framework in Sitecore 9.1:

  • Identity server 4 framework
  • Still uses old asp net membership provider underneath
  • Can work with sub providers like Azure

Measure if you want to go faster with Jeremy Davis

Jeremy was in the situation where a site they were developing would have TV adverts during one of the most watch programs on British TV. Naturally he got scared and went looking for tools to help with performance. He told us about two of them:

  • Sitecore debug tool in experience editor showing the time it takes for components to load.
  • Using Visual Studio debugger to monitor processor usage and memory usage.

Both of these tools are very good at pointing you in the direction of smelly code and the best part is you already have them.

Unfortunately it’s the kind of demo that really doesn’t convert to text to write here.

We released JSS, you’ll never guess what happened next with Adam Weber & Kam Figy

Adam and Kam showed us JSS working with SXA and Sitecore Forms. As mentioned before I don’t know much about JSS but after this talk I’m convinced I definitely need to.

Right now it doesn’t sound like I would make a site using it, but it could definitely be the future of how we build sites.

The stand out thing is being able to keep your Sitecore install unmodified which would essentially lead us to a real SAAS solution where a Sitecore instance could be spun up from the marketplace and then all other functionality added through server-less functions and a headless front end.

Sitecore SXA example site

Recently I’ve been looking into building sites using Sitecore Experience Accelerator (SXA). If you haven’t heard of it, in short SXA cut’s the amount of dev effort by building sites through pre-built re-usable components and then adding some styling. For a brochure type site this can (in some cases) remove virtually all the back end dev. You can read more about it here https://www.sitecore.com/en-gb/products/sitecore-experience-platform/wcm/experience-accelerators

Getting your head around SXA however can be a slight challenge. There is a getting started guide from Sitecore, which covers grid layouts, choosing features etc, but being able to understand how a site should actually be constructed and how the editor will use it can become confusing.

What would really help which Sitecore don’t provide is an example site. However Cognifide have and you can download it from Github here: https://github.com/Cognifide/Sitecore.XA.Showcase

To get started with it:

  1. Install a clean copy of Sitecore with SXA
  2. Download the code from github
  3. Restore the NuGet packages
  4. In the App_Config you will see a config file named Sitecore.XA.Project.Showcase.User.config. This includes one setting that needs updating to point to the folder you downloaded the solution to. This is then going to be used by unicorn to synchronize the items.
  5. Publish the solution into your Sitecore install
  6. Login as an admin and go to http://<yourinstancename>/unicorn.aspx
  7. Click the sync button, to synchronize the items
  8. Publish the site

You will now be able to see the showcase site in the admin and have a click through it on the published version.

SXA Showcase

What’s really great about the SXA showcase site is it’s a site all about SXA with loads of useful information on how you build an SXA site as well as actually be being built in SXA.

Sitecore Alias as Redirect

One feature of Sitecore that I have always disliked is Alias’s. On each page of a site, content editors have the ability to click an alias button on the presentation tab and add alternative urls for the page.

Alias Toolbar

Once added these will appear in the Aliases folder under system.

Alias

However all this accomplishes is multiple URLs existing for one page which is a big SEO no no.

Content editors like to do this in order to create simple URLs for things like landing pages. e.g. himynameistim.com/Sitecore but search engines hate it as they see multiple pages with the exact same content. As a result the value of each page gets lowered and appears lower in search engine results. What Content editors really want is to set up a 301 redirect so that they can have the simple URL but redirect users to the actual page on the site.

Aliases as Redirects

One solution is to updated the aliases functionality to cause a redirect to it’s linked item rather than resolve the page.

To do this we need to create a pipeline processor that inherits from AliasResolver.

using Sitecore;
using Sitecore.Configuration;
using Sitecore.Diagnostics;
using Sitecore.Pipelines.HttpRequest;
using System.Net;
using System.Web;
using AliasResolver = Sitecore.Pipelines.HttpRequest.AliasResolver;

namespace HiMyNameIsTim.Pipelines
{
    public class AliasAsRedirectResolver : AliasResolver
    {
		public override void Process(HttpRequestArgs args)
		{
			if (!Settings.AliasesActive)
			{
				return; // if aliases aren't active, we really shouldn't confuse whoever turned them off
			}

			var database = Context.Database;

			if (database == null)
			{
				return; // similarly, if we don't have a database, we probably shouldn't try to do anything
			}

			if (!Context.Database.Aliases.Exists(args.LocalPath))
			{
				return; // alias doesn't exist
			}

			var targetID = Context.Database.Aliases.GetTargetID(args.LocalPath);

			// sanity checks for the item
			if (targetID.IsNull)
			{
				Tracer.Error("An alias for \"" + args.LocalPath + "\" exists, but points to a non-existing item.");
				return;
			}
			var item = args.GetItem(targetID);

			if (database.Aliases.Exists(args.LocalPath) && item != null)
			{
				if (Context.Item == null)
				{
					Context.Item = item;
					Tracer.Info(string.Concat("Using alias for \"", args.LocalPath, "\" which points to \"", item.ID, "\""));
				}

				HttpContext.Current.Response.RedirectLocation = item.Paths.FullPath.ToLower()
					.Replace(Context.Site.StartPath.ToLower(), string.Empty);
				HttpContext.Current.Response.StatusCode = (int)HttpStatusCode.MovedPermanently;
				HttpContext.Current.Response.StatusDescription = "301 Moved Permanently";
				HttpContext.Current.Response.End();
			}
		}
    }
}

And patch in in place of the regular Alias Resolver.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <httpRequestBegin>
        <processor type="HiMyNameIsTim.Core.Pipelines.AliasAsRedirectResolver, LabSitecore.Core" 
                   patch:instead="*[@type='Sitecore.Pipelines.HttpRequest.AliasResolver, Sitecore.Kernel']"/>
      </httpRequestBegin>
    </pipelines>
  </sitecore>
</configuration>

The above code is adapted from a solution given by Jordan Robinson but with a bug fixed to stop every valid URL without an alias writing an error to the log file.

Sitecore Logging

One of the advantages of using a platform like Sitecore over completely bespoke development, is the number of features that are built-in that day to day you completely take for granted. An important one of those is logging.

If you’re building a bespoke application, adding some sort of support for generating log files can be a bit of a pain. Granted there are solutions that can be added to your project that do most of the lifting for you, but you still need to think about it, decide which to use and understand how to use it. With Sitecore the ability to write to a log file has been built in along with the logic that’s going to delete old log files and stop your servers hard disk filling up. Under the hood Sitecore is using Log4Net to generate log files, a side effect of this is that config changes can not be made using patch files.

Logs are written to the logs folder within your data folder. If your on Sitecore 8.2 or below this will be adjacent to your website folder. If your on Sitecore 9 or using Sitecore PaaS this will be in the App_Data folder within your sites folder.

The different log files

Sitecore generates 6 different log files while it’s running, these are:

Log – A general log file which you can write to
Crawling – Logs from the Sitecore Search Providers for crawling
Search – Logs from the Sitecore search providers for searches
Publishing – Logs generated during Sitecore publishes
FXM – Logs from federated experience manager
WebDAV – A log for WebDAV activity

Customizing the amount of detail

At different times you will likely want to see a different amount of detail in your log files. For instance on a production server you will want to keep logs to a minimum to maximise performance. However in a different environment where you are trying to debug an issue you would want all the logs you can get.

For this reason when writing a log a priority level is assigned and each log file can be configured to only write logs at a certain level or below to disk.

Priority levels are:

  1. DEBUG
  2. INFO
  3. WARN
  4. ERROR
  5. FATAL

To configure what level of logging should be output, configure the priority value in the log4net section of the web.config file.

<log4net>
  <appender name="LogFileAppender" 
            type="log4net.Appender.SitecoreLogFileAppender, Sitecore.Logging">
    <file value="$(dataFolder)/logs/log.{date}.txt" />
    <appendToFile value="true" />
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%4t %d{ABSOLUTE} %-5p %m%n" />
    </layout>
  </appender>
  <root>
    <priority value="INFO" />
    <appender-ref ref="LogFileAppender" />
  </root>
</log4net>

 

Writing to the log file

Writing to the log file is super easy to do from within your Sitecore application. The Sitecore.Diagnostics.Log class contains static functions to write to the general log file at different priority levels.

// Writes a log at the error priority level
Sitecore.Diagnostics.Log.Error("That wasn't meant to happen", this);

Custom Experience Buttons vs Edit Frames in Sitecore

So your going that extra mile and fully supporting the experience editor in your Sitecore solution, but how do you support a WYSIWYG editor when a field isn’t actually visible, or you need to provide the ability to edit a complex field type such as a drop-down or a multi-list?

The solution is to use either Edit Frames or Custom Experience Buttons, both of which will display a dialog containing the fields to edit. The difference between them comes down to where the toolbar containing a button will appear.

Edit Frames require extra code to be added to your view and are designed to surround a section(s) of your view. Custom Experience buttons however appear on the existing tool bar that’s shown when you select a component in the experience editor.

Edit Frame

With an edit frame you can surround a section of html within your view with a clickable target, that will display a toolbar with a button to launch the dialogue.

To set up an edit frame:

  1. In the core database navigate to /sitecore/content/Applications/WebEdit/Edit Frame Buttons and create a new item based on Edit Frame Button Folder. This folder will be referenced in your view for the collection of buttons to be displayed.
  2. Under the new folder create a new item based on Field Editor Button and give it the name of your button.
  3. On your button item make sure you set an icon and the list of fields the button should allow the content editor to edit. These should be pipe separated.

    Edit Frame Button Config

  4. In Visual Studio open the view for your rendering
  5. Add a reference to Sitecore.Mvc.Extensions
  6. Surround the section to show the button with a using block as follows:
    @using (Html.BeginEditFrame(RenderingContext.Current.ContextItem.Paths.FullPath, "Button Folder Name", "Toolbar Title"))
    {
          // HTML here
    }
    
  7. You will now see a toolbar appear in the experience editorEdit Frame Toolbar
  8. Clicking the icon will load a dialogue to edit the listed fields

Custom Experience Buttons

Custom experience buttons differ to edit frames in that you do not need to add any code to your views. Rather than having 1 or more clickable areas in your view the button will appear on the toolbar for the entire component. This makes them beneficial when the field doesn’t directly relate to a section in the view. e.g. for editing a background video that may span the entire components background.

To set up a custom experience button:

  1. In the core database navigate to /sitecore/content/Applications/WebEdit/Custom Experience Buttons and create a new item based on Field Editor Button.
  2. On your button item make sure you set an icon and the list of fields the button should allow the content editor to edit. These should be pipe separated.
  3. Switch to the master DB and navigate to the rendering item for your component
  4. In the field for Experience Editor Buttons select the new button

    Experience Editor Buttons

  5. Selection the component will now show the additional button on the toolbar
    Experience Editor Button
  6. Clicking the icon will load a dialogue to edit the listed fields
    Edit Frame Dialog

Invert list selection with Sitecore Powershell

I recently needed to run a script on a block of Sitecore content in invert the selection of a checklist and multilist. As I couldn’t find any example of how to do this at the time, I thought I’d share what I wrote.

#script to update tier
Get-ChildItem -r -Path "master:\content\Home" -Language * | ForEach-Object {
    if ($_.PSobject.Properties.name -match "Tier") {
        [String[]]$tiers = $_.Tier -split "\|"

        $_.Editing.BeginEdit()
        $newtiers = Get-ChildItem 'master:\content\Lookups\Tiers\' | Where-Object { $tiers -notcontains $_.Id }    
        $_.Tier = $newtiers.Id -join "|"
        $_.Editing.EndEdit()
    }
}

Get-ChildItem -r -Path “master:\content\Home” -Language * | ForEach-Object {

This line is getting the child items from the home node in the master db. The -r specified that it should be recursive and the -Language * specifies to include all languages. The results are then piped to a for each loop.

if ($_.PSobject.Properties.name -match “Tier”) {

The field I needed to update was called Tier, as this was included in multiple templates I checked that the object included a field called Tier, rather than checking the template types.

[String[]]$tiers = $_.Tier -split “\|”

List fields in Sitecore get stored as a pipe separated list of the item Id’s for the selected items. In order to do a comparison in Powershell I needed to turn the string back into an array using the split command. Notice the backslash that is needed before the pipe.

$_.Editing.BeginEdit()

To edit an item you need to begin an edit

$newtiers = Get-ChildItem ‘master:\content\Components\Lookups\Tiers\’ | Where-Object { $tiers -notcontains $_.Id }

This is where we get the new list of tiers to set as the selected ones for the item. The Get-ChildItem command is retrieving the original options that could be selected and the where-object statement is then excluding the ones that are in the $tiers array we just made.

$_.Tier = $newtiers.Id -join “|”

To save the new list we need to convert the results of the query into a pipe separated list using a join.

$_.Editing.EndEdit() } }

End the editing to save and close the the if and loop statements.

 

Tracking downloads in Sitecore Experience Analytics

This blog is generally aimed at developers whereas the contents of this post could be categorized as a topic for marketers, but I’ve decided to include it as its likely something a Sitecore dev could get asked about and its also quite useful to know about.

Out the box Sitecore’s Experience Analytics comes with a set of pre-configured reports to give insights into the sites visitors. Previously I blogged about Populating the internal search report in Sitecore which unless done will probably lead to someone asking why it’s blank. Another report which initially won’t show any data is downloads.

AnalyticsBehaviourTab

Located under behavior there is actually now two reports relating to downloads. The Assets report and the Downloads report.

Assets, in Sitecores words – “Describes your marketing assets or content used to attract contacts to your website and increase their engagement with your organization.”

Downloads – “Describes your specific assets, their download activity, and their value.”

These reports are populated by assigning a download event to a piece of media and organizing it as a marketing asset.

Adding a download event to an item

Sitecore doesn’t know which items you think are important to track as downloads so content editors need to mark them manually.

  1. Go to the Media Library

    Media Library

  2. Select the item you want to track as a download

    Select Item

  3. From the ribbon select the Analyze tab and click Attributes

    Click Attributes

  4. Select download form the list of events

    Select Download Attribute

  5. Remember to publish your changes.

Categorizing an Asset

By assigning a marketing asset type to your items they can be grouped for analysis. Such as grouping downloads into categories like white paper and product brochure.

  1. Before you assign a marketing asset to an item you will first need to create your assets. On the Sitecore Launchpad, open the Marketing Control Panel.

    Marketing Control Panel

  2. Go to Assets which is located under Taxonomies.

    Asset

  3. Create your set of Asset Groups and Assets within each. In this example I’ve created a group called Content and Assets called Instruction Manual and Product Brochure.

    Assets

  4. Navigate to the item you want to assign the asset to and select the relevant asset in the marketing asset field.

    Marketing Asset on Item

  5. Publish all your changes.

The result

Now you’ve set a download event and a marketing asset, the assets and downloads reports will start populating.

Going Further

The concept of having to tag every download as a download may seem a little tedious and also prone to being missed in the future. If all your downloads are PDF’s and you want to track all PDF’s as a download, one way to make life easier is to update the standard values on the PDF template item (/sitecore/templates/System/Media/Unversioned/Pdf) so that it always has the attribute of download. The content authors will still need to do the marketing asset categorization, but at least this gives them 1 less thing to do.

Populating the internal search report in Sitecore

Out the box Sitecore ships with a number of reports pre-configured. Some of these will show data without you doing anything. e.g. The pages report will automatically start showing the top entry and exit pages as a page view is something Sitecore can track.

Other’s like the internal search report will just show a message of no data to display, which can be confusing/frustrating for your users. Particularly when they’ve just spent money on a license fee to get great analytics data only to see a blank report.

Internal Search Report

The reason it doesn’t show any information is relatively straight forward. Sitecore doesn’t know how your site search is going to work and therefore it can’t do the data capture part of the process. That part of the process however is actually quite simple to do.

Sitecore has a set of page events that can be registered in the analytics tracker. Some of these like Page Visited will be handled by Sitecore. In this instance the one we are interested in is Search and will we have to register it manually.

Search Event

To register the search event use some code like this (note, there is a constant that references the item id of the search event). The query parameter should be populated with the search term the user entered.

using Sitecore.Analytics;
using Sitecore.Analytics.Data;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using SitecoreItemIds;

namespace SitecoreServices
{
    public class SiteSearch
    {
        public static void TrackSiteSearch(Item pageEventItem, string query)
        {
            Assert.ArgumentNotNull(pageEventItem, nameof(pageEventItem));
            Assert.IsNotNull(pageEventItem, $"Cannot find page event: {pageEventItem}");

            if (Tracker.IsActive)
            {
                var pageEventData = new PageEventData("Search", ContentItemIds.Search)
                {
                    ItemId = pageEventItem.ID.ToGuid(),
                    Data = query,
                    DataKey = query,
                    Text = query
                };
                var interaction = Tracker.Current.Session.Interaction;
                if (interaction != null)
                {
                    interaction.CurrentPage.Register(pageEventData);
                }
            }
        }
    }
}

Now after triggering the code to be called a few times, your internal search report should start to be populated like this.

Internal Search Report Populated