Change Sitecore template on media upload

In one of our projects we recently had to swap the media template when a new item is uploaded so we can have extra fields against the media item.

Sean Holmesby has already posted a blog a while back on this and we used the concept as the starting point for our implementation but made it more flexible so it can be reused across different projects. Eventually I created a Sitecore module and uploaded to the market place as well as Github for everybody’s benefit and allowing to contribute.

Let’s have a look at the configuration of the module first:

  <sitecore>
    <processors>
      <uiUpload>
        <processor patch:after="*[@type='Sitecore.Pipelines.Upload.Save, Sitecore.Kernel']" mode="on" type="Swissworx.Modules.MediaTemplateSwapper.TemplateSwapProcessor, Swissworx.Modules.MediaTemplateSwapper">
          <Name>Template swapper</Name>
          <MediaRootPath>/sitecore/media library</MediaRootPath>
          <SwapConfigurations hint="list">
            <Template type="Swissworx.Modules.MediaTemplateSwapper.SwapperConfiguration, Swissworx.Modules.MediaTemplateSwapper">
              <SourceTemplateId>{AB86861A-6030-46C5-B394-E8F99E8B87DB}</SourceTemplateId>
              <TargetTemplateId></TargetTemplateId>
            </Template>
            <Template type="Swissworx.Modules.MediaTemplateSwapper.SwapperConfiguration, Swissworx.Modules.MediaTemplateSwapper">
              <SourceTemplateId>{DAF085E8-602E-43A6-8299-038FF171349F}</SourceTemplateId>
              <TargetTemplateId></TargetTemplateId>
            </Template>
          </SwapConfigurations>
        </processor>
      </uiUpload>
    </processors>
  </sitecore>

We are patching the media swapper processor after Save pipeline for Sitecore.

The module allows to configure a media root path which comes in handy when you have a multi-site setup and you only want to swap templates for some sites.

The swap configurations are a straight forward mapping definition for the source template that is being swapped with the target template.

From the implementation side of things I created a SwapperConfiguration class which allows us to configure the mapping and of course the pipeline processor which takes the UploadArgs.

And here is the processor:

public void Process(UploadArgs args)
{
    Assert.ArgumentNotNull(args, "args");
    using (new SecurityDisabler())
    {
        Item uploadedItem = this.database.GetItem(args.Folder);
        if (uploadedItem?.Paths != null)
        {
            string uploadPath = uploadedItem.Paths.ContentPath;
            if (!uploadPath.StartsWith(this.MediaRootPath, StringComparison.OrdinalIgnoreCase))
            {
                // Item has not been uploaded to the path configured in scope for this processor
                return;
            }

            foreach (Item item in args.UploadedItems)
            {
                // Check whether the processor has a swap config that matches the source template id
                SwapperConfiguration swapConfiguration = this.SwapConfigurations.FirstOrDefault(config => new ID(config.SourceTemplateId) == item.Template.ID);
                if (swapConfiguration != null)
                {
                    TemplateItem targetTemplate = this.database.Templates[swapConfiguration.TargetTemplateId];
                    try
                    {
                        item.ChangeTemplate(targetTemplate);
                    }
                    catch (Exception e)
                    {
                        Log.Error(
                            $"Failed changing source template {swapConfiguration.SourceTemplateId} to target template {swapConfiguration.TargetTemplateId} for item '{item.Name}'.",
                            e,
                            this);
                    }
                }
            }
        }
    }
}

Sitecore field level fallback not working with template inheritance

It has been known for a while that there are issues in Sitecore 8.0/8.1 with the field level fallback when using template inheritance (which everybody should use!). For the above versions Sitecore created a support hotfix with the number 105327. This blog post is outlining the issue in more details.

It turns out this issue is still present in Sitecore 8.2 but unfortunately the aforementioned hotfix no longer works for 8.2. I reached out to Sitecore support and they have created a new hotfix that works for 8.2 with the number 184934. You can download it from here if you want it without having to request it from Sitecore support.

I hope this helps for people who need the language fallback to work in more advanced setups.

Geo IP API lookup provider for Sitecore

As an alternative to the geo IP lookup provider that ships with Sitecore I have created my own provider using IP-API which is a free web service a while back and I have used it already for a couple years.

IP-API offers free lookup’s using their web services for up to 150 requests per minute. That means it will be good enough for your Sitecore setup if you have 150 or less new user sessions created per minute as there is some caching implemented that will ensure that each IP look up will only be done once. The caching is using the Sitecore built-in GeoIpCacheManager.GeoIpCache.

There is also a paid subscription for 160 Euro per year if you want unlimited amount of lookup’s which is a good value for money.

The IP-API based lookup provider is now available on Github as well as a Sitecore module on the market place.

If you want to create your own lookup provider you can start by creating a new class that derives from Sitecore.Analytics.Lookups.LookupProviderBase. Don’t forget that you also need to add a project reference to the Sitecore.Analytics library. For that I am using the Sitecore nuget server which is pretty cool.

Then you can use your own logic to lookup geo location information based on IP address by overriding the GetInformationByIp(string ip) from the base class. Don’t forget to use the caching provided by Sitecore so you will not lookup the same IP address multiple times:

    GeoIpCache geoIpCache = GeoIpCacheManager.GeoIpCache;
    WhoIsInformation information = geoIpCache.Get(ip);
    if (information != null)
    {
        return information;
    }

Also, make sure you handle errors gracefully in case your logic fails:

    try
    {
       // Your logic to lookup
    }
    catch (Exception ex)
    {
       HandleLookupErrorArgs handleLookupErrorArgs = new HandleLookupErrorArgs(ex);
       CorePipeline.Run("ces.geoIp.handleLookupError", handleLookupErrorArgs);
       if (handleLookupErrorArgs.Fallback == null)
       {
           throw;
       }

       if (handleLookupErrorArgs.CacheFallback)
       {
           geoIpCache.Add(ip, handleLookupErrorArgs.Fallback);
       }

       return handleLookupErrorArgs.Fallback;
   }

Last but not least you will also need to create a Sitecore config patch to replace the built-in Sitecore lookup provider which can be done like this:

  <sitecore>
    <lookupManager defaultProvider="default">
      <providers>
        <add>
          <patch:attribute name="type">Swissworx.Modules.Analytics.Lookups.IpApi.LookupProvider, Swissworx.Modules.Analytics.Lookups.IpApi</patch:attribute>
        </add>
      </providers>
    </lookupManager>
  </sitecore>

Sitecore 8.1 SolrConnectionException – the operation timed out

Lately I have noticed a lot of the following exceptions in our client’s Sitecore log:

Exception: SolrNet.Exceptions.SolrConnectionException
 Message: The operation has timed out

This started to happen as our dedicated processing server is going through a few months of data as it was not running properly for a while.

I have spent some time trying to tweak the Solr server’s parameters such as the request time out without seeing any improvement in regards to the number of logged time out exceptions.

Eventually I approached Sitecore support and they told me that this is a known bug in Sitecore 8.1 which has been fixed for 8.2.

If you are running 8.1 same as us and you experience this problems, I recommend you reach out to Sitecore support as they have a patch for each of their support DI containers which will allow you to configure the Solr time out in the settings.

I hope this helps others to save the time investigating this issue.

Sitecore 8 MongoCommandException using Mongo Db as a service (mLab)

In essence Andy Cohen has already described the solution to overcome the MongoCommandException in one of his blog posts.

However, when I connected to my Mongo database hosted with mLab and attempted to run the db.grantRolesToUser command I got an exception that my user was not authorized on my database to execute the command.

If you experience the same issue the following steps might help you to overcome it:

  1. On your admin database add a new admin user
  2. Use command prompt to login to admin database using the newly created admin user
  3. Switch to your analytics database
  4. Run db.grantRolesToUser command

Monitoring Sitecore performance counters in Application Insights (Azure)

Most Sitecore administrators and developers are probably very well aware of the power of performance counters when it comes to trouble shooting issues. But it is not well known that performance counters can actually be visualized in your Azure dashboard using Application Insights.

For this blog post I assume that you are familiar with the process to install performance counters on a server. Also, I would like to point out that it is not recommended for performance reasons to have the counters enabled all the time. They can be a great tool in resolving issues and that’s when it might help to have some graphs in the Azure portal.

In order to enable the counters for AI you can simply add them in the ApplicationInsights.config file under the Counters node:

It is worthwhile mentioning though that if you update the Application Insights version using the Nuget package manager, these changes will be overridden. As an alternative approach the performance counters can be registered with Application Insights via code which is my preferred approach:

Once the change is deployed onto the server(s) and the application is up and running the new performance counters should come through on the portal under the Application Insights Metrics section:

The data captured in Application Insights can then be further queried and shaped using Analytics that comes as part of AI or it can be exported into Power BI or other third party tools.

Here is a list of available counters that Sitecore 8 ships out of the box:

Sitecore 8.0 Performance Counters

What’s next?

There is more! The Sitecore performance counters are just the beginning. Using the standard .NET libraries it is very easy to create custom performance counters and hook them up the same way giving DevOps some very useful insights into the application during runtime.

Retrieve the SiteInfo for an item in multi-site setup

There are various blog posts out there that offer different solutions to retrieve the site information for a Sitecore item but the ones that I came across lacked support for multi-site setup where multiple site configurations have the same root path and start item. This is common when your Sitecore solution supports multi-lingual content.

Here is my extension method that caters for multi-lingual, multi-site setups:

public static SiteInfo GetSite(this Item item)
{
   List<SiteInfo> siteInfoList = Factory.GetSiteInfoList();
   SiteInfo currentSiteinfo = null;
   int matchLength = 0;
   foreach (SiteInfo siteInfo in siteInfoList)
   {
      string startPath = string.Concat(siteInfo.RootPath, siteInfo.StartItem);
      if (item.Paths.FullPath.StartsWith(startPath, StringComparison.OrdinalIgnoreCase)
         && siteInfo.Language.Equals(item.Language.Name, StringComparison.OrdinalIgnoreCase) && startPath.Length > matchLength)
      {
         matchLength = startPath.Length;
         currentSiteinfo = siteInfo;
      }
   }

   return currentSiteinfo;
}

Happy coding!

Defensive programming to prevent System.ArgumentNullException: Value cannot be null

This post is about how to prevent the System.ArgumentNullException: ‘Value cannot be null’ that usually originates from LINQ expressions where the IEnumerable source is null. I have come across this exception so many times in production environments so I decided to write this short post which hopefully raises awareness how this can be prevented by applying a defensive programming practise.

We are using the good old customer/order entities for this example:

And an example where we would see the infamous exception:

If we want to prevent this exception we can create the following simple but very effective generic extension method:

    public static class IEnumerableExtensions
    {
        public static IEnumerable<T> DefaultIfNull<T>(this IEnumerable<T> source)
        {
            return source ?? Enumerable.Empty<T>();
        }
    }

Now we change the above code to use the new extension method and the code will run without any exception:

In a lot of use cases we have IEnumerables that are only occasionally populated and they can cause a lot of headache in production environments. But the extension method described above will allow you to ensure your code is not failing under those circumstances.

Queryable data source in Sitecore MVC

Unfortunately, Sitecore does not support queries in the Data Source field for renderings out-of-the-box but I have found a blog that describes how a pipeline processor can be created to enable queries. However, the blog post is limited to WebForms implementations so I spent a bit of time making this feature available for all MVC friends out there.

Processor code

All we need is a RenderRenderingProcessor:

public class QueryableDatasourceProcessor : RenderRenderingProcessor
    {
        public override void Process(RenderRenderingArgs args)
        {
            string dataSource = args.Rendering.DataSource;
            if (dataSource.StartsWith("query:"))
            {
                string query = dataSource.Substring("query:".Length);
                Item queryItem = args.PageContext.Item.Axes.SelectSingleItem(query);
                if (queryItem != null)
                {
                    args.Rendering.DataSource = queryItem.Paths.FullPath;
                }
            }
        }
    }

Configuration

Following best practises we create a patch file for the configuration change:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <mvc.renderRendering>
        <processor patch:before="*[@type='Sitecore.Mvc.Pipelines.Response.RenderRendering.ExecuteRenderer, Sitecore.Mvc']"
            type="Swissworx.Web.Sitecore.Pipelines.QueryableDatasourceProcessor, Swissworx.Web"/>
      </mvc.renderRendering>
    </pipelines>
  </sitecore>
</configuration>

Content Editor

Now we can use Sitecore queries in the Data Source field:

DatasourceQuery

Sitecore Azure deployment using Visual Studio Cloud Service project – Part 2

In the second part of this series of blog posts I will describe what is required to deploy a Sitecore solution into the content editing environment using Visual Studio and how it can be setup.

Prerequisites

Deployment Process

The following steps will be executed when the Publish option from the Visual Studio Azure Cloud Service project is invoked:

  • Compile source code using the ContentEditing build configuration
  • Transform configuration files
    e.g. web.config, connectionStrings.config…
  • Extract archive of Sitecore 7.2 site root’s Website folder into web application temporary publishing folder
  • Publish web application to temporary publishing folder
  • Create Azure deployment package
  • Upload Azure deployment package
  • Deploy package

Since the Sitecore folder in the Website folder only contains Sitecore content editing related files that should not be changed I recommend to exclude this folder from being packaged and uploaded with each and every deployment package. The Sitecore folder alone is over 130 MB zipped and slows down the deployment process considerably. Instead the deployment setup below suggests to upload a zipped archive to the blob storage and extract that archive on role startup into the application folder on each web role.

Continue reading “Sitecore Azure deployment using Visual Studio Cloud Service project – Part 2”