Skip to main content

Architecture

Multiplatform Development for Sitecore

Passage Lights@1x.jpg

When you’re building a website, you typically don’t worry about targeting multiple Sitecore versions. You usually pick one version and develop against it at full speed.

Contributing to a library or framework is different. Targeting only one version of Sitecore will dramatically reduce the applicability of your work. Instead, you need to think about how to make your code work with multiple different versions of Sitecore–i.e., multiplatform development. And, in the interest of efficiency, you’ll want to find a way to make it smooth and easy to switch between versions.

I have been working on a project — Content Search Provider for Algolia — that aims to deliver Algolia goodness to all Sitecore versions starting from 7.2. This multiplatform development challenge proved to be complex, so I’d like to share my approach and take a look at whether there are any other workable options.

 

Targets

The first important decision to be made is how to define the supported targets, and Sitecore Search Provider presents an immediate complication: the Search Provider API has seen multiple breaking changes that require some attention.

After its introduction with Sitecore v7, the Search Provider model’s interfaces were changed twice: once in v8.0 and again in v8.1. In both versions, additional methods and dependencies were incorporated into base interfaces. This means generated binaries are not compatible across different versions. So, in my case, I had to maintain 3 different versions–one for Sitecore 7x, one for 8.0 and another for 8.1.

Other multiplatform development projects may require an even more granular approach. For example, in SCORE, we are targeting every Sitecore release to address every minor Sitecore code variation.

 

Preprocessor Directives

After targets are defined, it’s time to dive into the code. Sitecore provides a set of interfaces and abstract classes that need to be implemented to build a new provider. The problem is that the signature of these interfaces was extended in subsequent versions (after being initially defined in Sitecore 7). Such variations can be addressed with Preprocessor Directives like #if. The code looks like this:

#if (SITECORE8)
using Sitecore.ContentSearch.Sharding;
#endif

namespace Score.ContentSearch.Algolia
{
public class AlgoliaUpdateContext: IProviderUpdateContext
{
...
#if (SITECORE8)
public IEnumerable<Shard> ShardsWithPendingChanges { get; private set; }
#endif
}
}

In the case of Algolia, Sharding is seamlessly implemented by a service and no extra code is required in the provider. Nevertheless, the ShardsWithPendingChanges property needs to be added to the class because it’s part of the IProviderUpdateContext Interface and the code can’t be compiled without it.

Now, at this point a careful reader might ask: What does the SITECORE8 symbol mean here? Let’s talk about MSBuild Properties to answer this question.

 

MSBuild Project Properties

Typically symbols that are used in an #if directive are defined in *.scproj file. This is where the DEBUG symbol originates. The DEBUG symbol is set for Debug build configuration:

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DefineConstants>TRACE;DEBUG</DefineConstants>
</PropertyGroup>

To cover different Sitecore versions, additional Build Configurations like Debug_Sitecore7, Debug_Sitecore8, Release_Sitecore7 and more could be added. But this seemingly simple approach would create unnecessary complexity. Targeting a Sitecore version adds another dimension to configuration, which means the number of configurations will grow exponentially with every new Sitecore version or build profile.

A better way to handle this is by using another property called SitecoreVersion. All version-specific symbols are set up based on SitecoreVersion, similar to how it’s done for Configuration:

<PropertyGroup Condition="'$(SitecoreVersion)' != '' And '$(SitecoreVersion)' >= 8">
<DefineConstants>SITECORE8</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(SitecoreVersion)' != '' And '$(SitecoreVersion)' >= 8.1">
<DefineConstants>SITECORE8;SITECORE81</DefineConstants>
</PropertyGroup>

As soon as SitecoreVersion is defined, symbols are assigned and can be reached from the code. The Version is inclusive of the symbols of previous releases to simplify directives in the code.

SitecoreVersion property can be defined as follows:

<PropertyGroup>
<SitecoreVersion>8.1</SitecoreVersion>
</PropertyGroup>

Everything works now, but…we still have to address the issue of switching between versions. One approach is to modify the *.csproj file every time there is a need to switch, but this isn’t ideal for a couple of reasons:

  • First, there could be multiple project files and all of them should be in sync.
  • And, the *.csproj file will be committed into source control, so modifications could mess up other developers’ work.

Instead, we can move the SitecoreVersion property to a separate file. This file should be incorporated into the project using the Import element.

Import Element in MSBuild

The Import element allows us to include the content of another file in the project (and, if you’d like to learn more about Import, please check MSDN):

<Import Project="$(SolutionDir)sitecore-version.props" Condition="exists('$(SolutionDir)sitecore-version.props')" />

 

Now switching between versions is simple: You just have to copy one little file or use a script similar to this one.

So, our code is covered, but what about dependencies? Sitecore thought about us and recently released a Nuget feed that hosts DLLs for all Sitecore versions.

 

Nuget Packages

The developer’s life became both easier and more complex with the introduction of the Sitecore Nuget feed. It’s easier because there is no need to maintain Sitecore DLLs and write “copy DLLs to folder” setup instructions. The complication comes from the fact that every version is now stored in its own folder and references are now version specific and cannot be reused.

Luckily, Include files will help here. First, you need to install all required Sitecore Nuget packages into the project. After that, you should cut these references from *.csproj file and copy to a separate file. Then, just add the Import element for your file into the project:

<Import Project="$(SolutionDir)\Automation\Sitecore-Versions\$(SitecoreVersion)\references-qualified.proj" Condition="'$(SitecoreVersion)' != '' And exists('$(SolutionDir)\Automation\Sitecore-Versions\$(SitecoreVersion)\references-qualified.proj')" />

That Import element respects the SitecoreVersion we just defined. Some hints for managing references:

  1. Don’t forget about the packages.config file. It should be version specific as well.
  2. Move any third-party modules Sitecore depends on – like HtmlAgilityPack, Lucene.Net and Newtonsoft.Json. – to an external file.
  3. Add reference to sitecore.nexus if you use Sitecore.FakeDb.

 

Up Next

That’s all for now on multiplatform development. Next, I am going to review what v8.2 brings us. Stay tuned–and happy coding!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Brian Beckham

As a Sitecore MVP, Brian spends most of his time consulting and architecting software solutions for enterprise-level Sitecore projects.

More from this Author

Follow Us