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);
                    }
                }
            }
        }
    }
}

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>