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.

Advertisements

Using compile options for version compatibility

Here’s the scenario; Your building a module and it needs to be compatible with different versions of a platform. e.g. Sitecore, and everything’s great up until the day you need to call different methods in different versions of the platform. You’d rather not drop support for the old versions, and nor do you want to start maintaining two code bases. So what do you do?

C# Preprocessor Directives

Preprocessor directives provide a way to give the compiler instructions to follow while its compiling a project. By using this we can give the compiler conditions to compile different versions in different ways. Thereby allowing us to maintain one codebase, but produce compilations for different versions of the platform. e.g. One for Sitecore 8.0 and another for Sitecore 9.0.

#if, #else and #endif

When the compiler encounters an #if followed by an #endif, it will only compile the code between the two if the specified symbol had been defined.

#if DEBUG
    Console.WriteLine("Debug version");
#else
    Console.WriteLine("Non Debug version");
#endif

Defining a preprocessor symbol

For the if statement to work, your going to need to define your symbol which is being evaluate.

This can be included in code as follows

#define YOURSYMBOL

A more useful was of defining this however is to include it in your call to MSBuild (this is particularly useful when using a build server).

-define:name[;name2]

If your compiling from Visual Studio an easier solution is to set up a new build configuration with a conditional compilation symbol.

  1. Right click your solution item in Solution Explorer and select Properties
  2. Click Configuration Properties on the left and then Configuration Manager on the right
  3. In the pop up window click the Active solution configuration drop down and then click New
    BuildConfiguration
  4. Enter the name of the build config. In my example above I have SC82 for Sitecore 8.2 and SC90 for Sitecore 9.0.
  5. Click Ok and close all the windows you just opened
  6. Right click the project that your going to build and select Properties
  7. Select the Build tab
  8. Select your build configuration from the configuration at the top
  9. Enter the symbol your using for the #if directives
    Conditional Compilation Symbols

Reference different versions of an assembly

Adding conditions to our code is good, but for this to fully work we also need to reference different versions of the assemblies that are causing the issue in the first place.

There’s no way of doing this through Visual Studio but by editing the .csproj file manually we can update the hint path on a reference to include the configuration name as a variable.

    
       ..\libraries\$(Configuration)\Sitecore.Kernel.dll
      False
    

This example shows how different versions of the Sitecore Kernel can be referenced by keeping each version in a subfolder that corresponds with the build configuration name.

As well as different versions of assemblies, it may also be needed to target different versions of the .net framework. This can be done in the .csproj file by including additional proerty groups that have a condition on the configuration name.

  
    bin\SC82\
    TRACE;SC82
    true
    v4.5.2
  
  
    bin\SC90\
    TRACE;SC90
    true
    v4.6.2
  

In this example I’m targeting .net 4.5.2 for my Sitecore 8.2 configuration and 4.6.2 for my Sitecore 9 configuration.

Useful Links

C# preprocessor directives
-define (C# Compiler Options)

A first look at Sitecore SPEAK 3

SPEAK (Sitecore Process Enablement and Accelerator Kit) is the framework for constructing admin interfaces in Sitecore. It was introduced to the platform prior to Sitecore 8, but really became the way to do things after Sitecore 8’s UI refresh which introduced the start page and made accessing full page SPEAK applications logical.

Sitecore9StartScreen

SPEAK 1 and 2

The goals of SPEAK were to:

  • Provide a streamlined approach to application development.
  • Enable reuse of UI elements.
  • Enforce a consistent look and feel.

In order to achieve this SPEAK 1 and 2 provides a component library of controls that can be used to construct pages. This ensures that the UI retains a consistent look and feel, and also minimizes the amount of work on a Sitecore developer. Logic is then added to an application using JavaScript for the front end and C# for server side code.

While this all sounds great many developers find SPEAK hard to use. In order to construct a UI out of the re-usable components, Sitecore lent on it’s existing functionality to be able to construct pages out of presentation items, however there is no WYSIWYG editor and the only real way to construct the layout is through Sitecore Rocks. This in itself isn’t awful, but when combined with the fact the average Sitecore developer doesn’t need to build an admin application that often, it presents a steep learning curve using a tool they may not use to put together components they’re not familiar with.

SPEAK 3

SPEAK 3 aims to address complaints in previous versions by introducing a completely new framework based on Angular.

Since SPEAKs initial incarnation, client side application development has moved on a long way, so rather than continuing to construct their own framework, Sitecore has chosen Angular as the the platform to use going forward.

Begin Angular, SPEAK 3 applications can run independently of Sitecore, however the purpose of SPEAK 3 is still to make it simple to integrate Sitecore-branded applications into the content manager.

My First Look

Before being a Sitecore back-end developer I worked on bespoke web based applications using client side frameworks such as Knockout, so the news that Sitecore was going to adopt Angular was great. Digging into Angular again however has given me a first hand experience of how fast the JavaScript world is changing. Gone is the promotion of MVC on the client being replaced with service/controller patterns. Whereas with Knockout and AngularJS (what Angular 1.x is now known as) we could add data binding to just an aspect of a page, Angular is really for running an entire application, routing and all.

Building an SPEAK 3 application really means building an Angular application with some modules provided by Sitecore. These modules will provide integration features such as:

  • Sitecore context
  • Translations for applications
  • Translations for the SPEAK 3 component library
  • Component user access authorization
  • Preventing cross-site request forgery (Anti-CSRF)

In addition to this the SPEAK 3 components will also sort out compatibility issues such as modifying the routing so that the application no longer needs to be in the route of the site and can be in a sub-folder of sitecore.

Angular for a Sitecore dev

To start it’s good to know an outline of what developing Angular involves.

Angular 2+ is built using TypeScript. You don’t need to use TypeScript, but as most of the examples are you probably will want to too. TypeScript is a superset of of JavaScript which adds strong typing support as well as other features of ECMAScript 2015 to backport it to older versions of JavaScript.

TypeScript needs to be compiled into JavaScript before it can run in the browser.

The easiest way to get started with Angular and TypeScript is using Node.js to install tools via NPM. Node is not a requirement for Angular and you won’t need it in production, but for local dev using Node to host your application can make life a lot easier.

Angular has a CLI which makes things easy to create and run an Angular application.

Visual Studio can be used as an IDE for TypeScript and Angular, but you might find life easier using Visual Studio Code.

It’s better than it sounds 🙂

All this might sound a bit daunting to the average C# developer. Technologies like Node and NPM traditionally are more at home in the open source community.

There is however a lot to be positive about. If your the type of dev that prefers writing c# to JavaScript, then the inclusion of TypeScript is going to please you, as it brings the type checking structure and class organisation that we’re used too.

The angular cli (command line interface) is also a reason to be pleased. One large difference between the .net and open source world has been the ability to click a button and get going. Open source typically comes with the setup of many components to get a solution working. At times when you try to learn something it can feel like your spending more effort doing setup that actual dev on the platform. Angular still needs to have all these components put together, but the cli takes care of all this for you, effectively recreating a file new project experience, just through a command line.

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.

 

Top features in Sitecore 9

Sitecore 9 is out and with it comes cool new wizzy stuff. Here’s my top features from the new version of the platform.

#1 – New Forms module

Sitecore 9 Forms

I think everyone would agree that Web Forms for Marketers was starting to show it’s age and the UI was getting a bit dated compared to the rest of Sitecore.

The new Forms module, which is just called forms is completely new from the ground up. It has a new drag and drop UI with long awaited support for multiple page forms.

Like WFFM, the module is extendable through custom save actions and comes with a number of useful default ones out of the box.

There is no upgrade option for moving a WFFM form to the new module but WFFM will continue to work on Sitecore 9 and is being dropped in Sitecore 9.1.

I think more than anything the UX improvements will make a real difference for users by being much simpler to understand and will drive to much more use.

#2 – Marketing Automation

Sitecore 9 Marketing Automation

In Sitecore 9 Engagement Plans are being replaced with Marketing Automation. Like the forms module, this is completely new from the group up rather than an UI update to the existing Engagement Plans.

The new Marketing Automation module has a really easy to use drag and drop ui which is a vast improvement over the old Silverlight implementation engagement plans had. It’s also directly accessible from the dashboard rather than being hidden in the Marketing Control Center.

One of the biggest changes (aside from the UI) is there is now no need to enroll users in a plan at a specific state either by code or a wffm save action. I found this one of the most confusing aspects to end users who were expecting creating states with a trigger to automatically add people once they had triggered a goal, so this is great to see fixed.

Plans now have very clear start and end points with a number of options on the start node (goals, events), which can be combined to trigger who should be added to the plan.

Overall for the moment you try creating a plan just to see what it can do, the whole process is so much simpler that I think this will have a significant aspect on users. Engagement Plans were something that needed to be learn’t in order to get anything out of them, and wernt intuitive enough leading to frustration. With Marketing Automation I think a lot of people that were put off before will now benefit from this module.

#3 – xConnect

xConnect is a new service layer that sits between xDB and the client. That could be the CMS, a device or some other custom server side process that needs to read or write xDB data.

With xConnect as the service layer this means that no system has direct access to the collection database or search indexes. Any system wanting to access this data will go through xConnect which also helps with support for things like GDPR.

xConnect is installed separately from Sitecore itself and does not have any dependencies on the Sitecore kernel. When you install Sitecore locally you will see two IIS entries, one for Sitecore and one for xConnect. Communication with xConnect is done via a set of RESTful API’s over HTTPS, making integrating with it extremely simple to do.

What xConnect really brings to the table is the ability to scale an combine many more systems rather than just the CMS. e.g. Phone Apps.

#4 – Sitecore Installation Framework (SIF)

Installing Sitecore 9 is very different to previous versions (see my Sitecore 9 installation tips here), gone are the days of copying a web-root and restoring some db’s. The entire installation is now done with a new framework based on PowerShell scripts.

While this is going to create a pain point in the amount of time it takes to get started. It will almost certainly vastly improve DevOps tasks as it opens up numerous options to put the installation scripts in deployment pipelines.

Sitecore 9 installation tips

Sitecore 9 released this week and with it comes a whole new installation process. Gone are the days you could just download the web root and restore some dbs or just run the installation gui and enter a db connection string. Sitecore 9 has some fairly fundamental architectural changes with multiple IIS entries and and some windows services to go with it. Server roles are now also being properly configured rather than updating config files to match what it says in an excel doc.

Along with these changes, the installation process has moved to be based on powershell scripts, which on one hand has made things a bit harder, but it also brings great positives that the process can now be customized with scripts that are repeatable without the risk of mistakes.

Here’s my tips for a smooth local installation (production installs are different to a local install).

Tip #1 – Check the Prerequisites and Requirements

It sounds obvious but when presented with a new toy you want to play with it as fast as possible, and with a 49 page document the urge is there to skip to the installation and hope for the best.

Skipping however is likely to result in install failures as the installer relies on modules such as Web Deploy and the right version of SQL Server which were not needed for the version you may already have installed.

Tip #2 – Make sure you have the right versions

You may have SQL and Solr but are they the right version?

SQL Express 2016

Sitecore 9 supports SQL Server 2014 SP2 and SQL Server 2016. Now that SQL Server 2017 is out, actually finding the link for 2016 express has become a challenge, but here it is.

Download SQL Server 2016 Express

Solr 6.6.1

Sirecore 9 supports Solr version 6.6.1. I typically use Bitnami Solr as it’s a lot easier to install than doing Solr on it’s own. Like SQL though the latest version is newer than what Sitecore supports and finding the link to the older one can be a bit of a challenge.

Download Bitnami Solr 6.6.1

Tip #3 – Solr requires SSL

By default Solr does not install with SSL turned on, but without it your install will fail. More specifically it will fail trying to start an xConnect service.

Enabling SSL for Solr

To create a self-signed certificate for Solr we can use the JDK Keytool which if you’ve installed Solr you should already have installed.

Note: These instructions are based on this guide from Apache and this blog post from Jason St-Cyr.

  1. Open command prompt
  2. Change to the Solr ‘etc’ directory
    cd "{SOLR_HOME}\server\etc"
  3. Execute the keygentool command
    "{JAVA_HOME}\bin\keytool.exe" -genkeypair -alias solr-ssl -keyalg RSA -keysize 2048 -keypass secret -storepass secret -validity 9999 -keystore solr-ssl.keystore.jks -ext SAN=DNS:localhost,IP:127.0.0.1 -dname "CN=localhost, OU=Organizational Unit, O=Organization, L=Location, ST=State, C=Country"

    This will generate the keystore with a password of ‘secret’ as valid for localhost and 127.0.0.1. You can add other DNS and IPs as desired, or skip hostname verification.

  4. Convert generated JKS to PKCS12
     "{JAVA_HOME}\bin\keytool.exe" -importkeystore -srckeystore solr-ssl.keystore.jks -destkeystore solr-ssl.keystore.p12 -srcstoretype jks -deststoretype pkcs12
  5. Enter password when prompted. The password ‘secret’ was used in the previous step. Remember to use your password instead if you changed it in the keygen command parameters.
  6. Open Windows Explorer and navigate to the ‘etc’ directory (“{SOLR_HOME}\server\etc”)
  7. Double-click on the generated ‘p12’ file (solr-ssl.keystore.p12 if you used the default parameters from the previous steps)
  8. In the wizard, specify the following values (there will be some extras you can ignore):
    • Store Location: Local Machine
    • File name: Leave as provided
    • Password: secret
    • Certificate Store: Trusted Root Certification Authorities

    Remember to use your password instead if you changed it during the previous steps.

  9. Open the solr.in.cmd file for editing (e.g. {SOLR_HOME}\bin\solr.in.cmd)
  10. Un-comment the SSL settings:
    set SOLR_SSL_KEY_STORE=etc/solr-ssl.keystore.jks
    set SOLR_SSL_KEY_STORE_PASSWORD=secret
    set SOLR_SSL_TRUST_STORE=etc/solr-ssl.keystore.jks
    set SOLR_SSL_TRUST_STORE_PASSWORD=secret
    set SOLR_SSL_NEED_CLIENT_AUTH=false
    set SOLR_SSL_WANT_CLIENT_AUTH=false

    Remember to update passwords and file paths to match to the parameters you specified.

  11. Restart SOLR to pick up the changes.

Tip #4 – Close management studio

I’m not sure if this was a one off thing, but with management studio open my installation failed with a single user access issue.

Tip #5 – Check the logs

The installation script will output logs to the folder it runs in. If your installation fails it will reference a log file. To find out why the installation failed or get some more info go and check the log referenced.

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