How to setup Unicorn for Helix based Sitecore project?

The idea behind using Unicorn is to serialize only items that you need, by adding the Sitecore item paths to Unicorn configuration files. When the item is controlled by Unicorn, all the child items can be serialized automatically unless excluded, which is very convenient because the developer does not have to remember to serialize items manually after every change they make in Sitecore Content Editor. The newly created items should be reserialized in order to be stored in the file system correctly – it is called initial serialization and Unicorn will complain during the next sync if it is missed.

If you are following the helix architecture, then you need to add a project for Serialization under Foundation layer. You can add a new empty MVC project.

Eg. [Customer Name|Brand Name|Sitecore].Foundation.Serialization.csproj

Sitecore.Foundation.Serialization.csproj

  • Right click on the newly added project and select “Manage NuGet Package”.
  • In the NuGet window, select the “Browse” tab if it isn’t selected already and search for “unicorn”.
  • Select the latest version available, install the package.

Once the Unicorn is installed, you can find the “Unicorn” folder created in “App_Config\Include” folder which contains Unicorn related config files.

There is one more config file “Rainbow.config” can be found outside Unicorn folder which also contains few settings referred in unicorn serialization.

Rename the file Unicorn.UI.IdentityServer.config.disabled to Unicorn.UI.IdentityServer.config if you are using Sitecore 9.1 or higher.

Build and confirm the project gets compiled successfully.

Configuration of Unicorn

After installing, it’s time to configure the Unicorn and manage your Sitecore items in serialized files.

Create a folder inside “Include” folder of “App_Config” & name it as “zzz.Foundation”

Create a “SourceFolder.config” file under zzz.Foundation folder and copy paste below configuration – update the source folder path as per your requirement. (can be root of your solution i.e “src” folder path). Unicorn will serialize Sitecore items as .yml files in this folder path.

<?xml version="1.0"?>

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/">
  <sitecore>
    <sc.variable name="sourceFolder" value="D:\Logistico Project\CodeBase\src" />
    <sites role:require="Standalone">
      <site name="website">
        <patch:attribute name="database">master</patch:attribute>
      </site>
    </sites>

    <!--Enable Unicorn login -->
    <pipelines>
      <owin.cookieAuthentication.validateIdentity>
        <processor type="Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.ValidateIdentity.ValidateSiteNeutralPaths, Sitecore.Owin.Authentication">
          <siteNeutralPaths hint="list">
            <path hint="unicorn">/unicorn.aspx</path>
          </siteNeutralPaths>
        </processor>
      </owin.cookieAuthentication.validateIdentity>
    </pipelines>
    
  </sitecore>
</configuration>

Create a new config file with name as Foundation.Serialization.config, which will contain the information/configuration for Sitecore items like settings, facets, placeholder settings etc. Comment/Uncomment the settings as per your needs.

Sample code for Foundation.Serialization.config –

<!--
	See Unicorn.config for commentary on how configurations operate, or https://github.com/kamsar/Unicorn/blob/master/README.md
-->
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/">
  <sitecore role:require="Standalone or ContentManagement">
    <unicorn>
      <configurations>
        <configuration name="Foundation.Serialization" description="Sitecore.Solution.Framework Root items" extends="Helix.Base">
          <targetDataStore physicalRootPath="$(sourceFolder)\$(layer)\$(module)\serialization" useDataCache="false" singleInstance="true"/>
          <predicate>

            <!--
				Each include can also exclude specific subitems by path:
				<exclude path="/sitecore/content"/>

				Excludes may also exclude all children at once by adding a trailing slash, e.g. this would include the root /sitecore item but no children
				<include path="/sitecore">
					<exclude path="/sitecore/" />
				</include>

				NOTE: after changing what is included or excluded, you should reserialize all items, or at least the added items
				NOTE: the "name" attribute controls the folder name the items will go into (for SFS). If unspecified, the last path segment is used. Names must be unique across the configuration.
			-->
            <!-- Settings 
            <include name="Settings.Feature" database="master" path="/sitecore/system/Settings/Feature">
              <exclude children="true" />
            </include>
            <include name="Settings.Foundation" database="master" path="/sitecore/system/Settings/Foundation">
              <exclude children="true" />
            </include>
            <include name="Settings.Project" database="master" path="/sitecore/system/Settings/Project">
              <exclude children="true" />
            </include>-->

            <!-- Facets 
            <include name="Facets.Feature" database="master" path="/sitecore/system/Settings/Buckets/Facets/Feature">
              <exclude children="true" />
            </include>
            <include name="Facets.Foundation" database="master" path="/sitecore/system/Settings/Buckets/Facets/Foundation">
              <exclude children="true" />
            </include>
            <include name="Facets.Project" database="master" path="/sitecore/system/Settings/Buckets/Facets/Project">
              <exclude children="true" />
            </include>-->

            <!-- Templates -->
            <include name="Templates.Feature" database="master" path="/sitecore/templates/Feature">
              <exclude children="true" />
            </include>
            <include name="Templates.Foundation" database="master" path="/sitecore/templates/Foundation">
              <exclude children="true" />
            </include>
            <include name="Templates.Project" database="master" path="/sitecore/templates/Project">
              <exclude children="true" />
            </include>

            <!-- Branches -->
            <include name="Branches.Foundation" database="master" path="/sitecore/templates/branches/Foundation">
              <exclude children="true" />
            </include>
            <include name="Branches.Feature" database="master" path="/sitecore/templates/branches/Feature">
              <exclude children="true" />
            </include>
            <include name="Branches.Project" database="master" path="/sitecore/templates/branches/Project">
              <exclude children="true" />
            </include>

            <!-- Renderings -->
            <include name="Renderings.Feature" database="master" path="/sitecore/layout/renderings/Feature">
              <exclude children="true" />
            </include>
            <include name="Renderings.Foundation" database="master" path="/sitecore/layout/renderings/Foundation">
              <exclude children="true" />
            </include>
            <include name="Renderings.Project" database="master" path="/sitecore/layout/renderings/Project">
              <exclude children="true" />
            </include>

            <!-- Layouts -->
            <include name="Layouts.Feature" database="master" path="/sitecore/layout/layouts/Feature">
              <exclude children="true" />
            </include>
            <include name="Layouts.Foundation" database="master" path="/sitecore/layout/layouts/Foundation">
              <exclude children="true" />
            </include>
            <include name="Layouts.Project" database="master" path="/sitecore/layout/layouts/Project">
              <exclude children="true" />
            </include>

            <!-- Placeholder Settings -->
            <include name="PlaceholderSettings.Feature" database="master" path="/sitecore/layout/placeholder settings/Feature">
              <exclude children="true" />
            </include>
            <include name="PlaceholderSettings.Foundation" database="master" path="/sitecore/layout/placeholder settings/Foundation">
              <exclude children="true" />
            </include>
            <include name="PlaceholderSettings.Project" database="master" path="/sitecore/layout/placeholder settings/Project">
              <exclude children="true" />
            </include>

            <!-- Models -->
            <include name="Models.Feature" database="master" path="/sitecore/layout/models/Feature">
              <exclude children="true" />
            </include>
            <include name="Models.Foundation" database="master" path="/sitecore/layout/models/Foundation">
              <exclude children="true" />
            </include>
            <include name="Models.Project" database="master" path="/sitecore/layout/models/Project">
              <exclude children="true" />
            </include>

            <!-- Media -->
            <include name="Media.Website" database="master" path="/sitecore/media library/Logistico">
              <exclude children="true" />
            </include>
            <!--<include name="Media.Project" database="master" path="/sitecore/media library/Project">
              <exclude children="true" />
            </include>-->

            <!-- Core templates 
            <include name="Core.Templates.Feature" database="core" path="/sitecore/templates/Feature">
              <exclude children="true" />
            </include>
            <include name="Core.Templates.Foundation" database="core" path="/sitecore/templates/Foundation">
              <exclude children="true" />
            </include>
            <include name="Core.Templates.Project" database="core" path="/sitecore/templates/Project">
              <exclude children="true" />
            </include>-->
          </predicate>
        </configuration>
      </configurations>
    </unicorn>
  </sitecore>
</configuration>

You can then override the Rainbow configuration with new configuration file (e.g. Foundation.Serialization.Settings.config) as below-

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/">
  <sitecore>
    <settings>
      <!--  Rainbow - SERIALIZATION FOLDER PATH MAX LENGTH
		      In Windows, there is 248 characters limit on the lenght of file system paths. To avoid exceeding the maximum path length, Rainbow will loop
				      long paths back to the root. This setting specifies the maximum length of the path to the serialization root path,
				      which determines how long item paths can be before they are looped.
		      Important: The value of this setting must be the same on all Sitecore instances accessing the serialized data. 
		      Important: When changing this value, you must reserialize all configurations!
		      Example: A value of "90" for this setting will mean that item paths longer than 150 characters will be shortened, since Sitecore 
		      reserves 8 characters (and 248 - 8 - 90 = 150). 
		      Default value: 90
		    -->

      <setting name="Rainbow.SFS.SerializationFolderPathMaxLength" value="150" />
      <setting name="Rainbow.SFS.MaxItemNameLengthBeforeTruncation" value="50" />
    </settings>
  </sitecore>
</configuration>

We need to configure standard configurations for modules in all layers, it can be done with a new configuration file created in “App_Config\Include” folder say Unicorn.Helix.config.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/">
  <sitecore role:require="Standalone or ContentManagement">
    <unicorn>
      <configurations>
        <!-- Base configuration for all modules -->
        <configuration name="Helix.Base" abstract="true">
          <predicate type="Unicorn.Predicates.SerializationPresetPredicate, Unicorn" singleInstance="true" />

          <targetDataStore physicalRootPath="$(sourceFolder)\$(layer)\$(module)\serialization" useDataCache="false" singleInstance="true" />
          <!--<roleDataStore type="Unicorn.Roles.Data.FilesystemRoleDataStore, Unicorn.Roles" physicalRootPath="$(sourceFolder)\$(layer)\$(module)\serialization\Roles" singleInstance="true" />
          <rolePredicate type="Unicorn.Roles.RolePredicates.ConfigurationRolePredicate, Unicorn.Roles" singleInstance="true">-->
            <!-- Include an invalid predicate or all roles will be synced -->
           <!-- <include domain="invaliddomain" pattern="none" />
          </rolePredicate>-->
        </configuration>

        <!-- Foundation modules -->
        <configuration name="Helix.Foundation" abstract="true" extends="Helix.Base">
          <predicate>
            <include name="Templates" database="master" path="/sitecore/templates/$(layer)/$(module)" />
          </predicate>
        </configuration>

        <!-- Feature modules -->
        <configuration name="Helix.Feature" abstract="true" extends="Helix.Base">
          <predicate>
            <include name="Templates" database="master" path="/sitecore/templates/$(layer)/$(module)" />
            <include name="Renderings" database="master" path="/sitecore/layout/renderings/$(layer)/$(module)" />
            <include name="Media" database="master" path="/sitecore/media library/$(layer)/$(module)" />
          </predicate>
        </configuration>

        <!-- Project modules -->
        <configuration name="Helix.Project" abstract="true" extends="Helix.Base">
          <predicate>
            <include name="Templates" database="master" path="/sitecore/templates/$(layer)/$(module)" />
            <include name="Renderings" database="master" path="/sitecore/layout/renderings/$(layer)/$(module)" />
          </predicate>
        </configuration>
        <syncConfiguration type="Unicorn.Loader.DefaultSyncConfiguration, Unicorn" singleInstance="true" updateLinkDatabase="true" updateSearchIndex="true" maxConcurrency="1" />
        <userDataStore type="Unicorn.Users.Data.FilesystemUserDataStore, Unicorn.Users" physicalRootPath="$(sourceFolder)\$(layer)\$(module)\serialization\Users\" singleInstance="true" />
      </configurations>
    </unicorn>
  </sitecore>
</configuration>

So you will need to create below files under zzz.Foundation in order to configure the Unicorn.

Sample folder structure will be like below-

App_Config – folder

                Include – Folder

                                Unicorn – folder

                                zzz.Foundation – Folder

                                                Foundation.Serialization.config

                                                Foundation.Serialization.Settings.config

                                                SourceFolder.config

                                Rainbow.config

                                Unicorn.Helix.config

Once we are done with Unicorn configuration in Foundation layer, we also need to do configuration while implementing the requirements under different layers like Project, Feature and Foundation wherever required.

e.g. Sample configuration from Project Layer serialization config. Comment/Uncomment based on your requirement. Here you will need to specify the dependencies also i.e. Foundation.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/">
    <sitecore role:require="Standalone or ContentManagement">
        <unicorn>
            <configurations>
                <configuration name="Project.Logistico.Website" description="Logistico content" dependencies="Foundation.*,Feature.*" extends="Helix.Project">
                  <targetDataStore physicalRootPath="$(sourceFolder)\$(layer)\$(module)\serialization" useDataCache="false" singleInstance="true"/>
                    <predicate>
                        <include name="Layouts" database="master" path="/sitecore/layout/layouts/Project/Logistico" />
                      <!--<include name="ProjectRenderings" database="master" path="/sitecore/layout/Renderings/Project/Logistico" />-->
                        <include name="PlaceholderSettings" database="master" path="/sitecore/layout/placeholder settings/Project/Logistico" />
                        <include name="Models" database="master" path="/sitecore/layout/models/Project/Logistico" />
                        <!--<include name="Languages.Danish" database="master" path="/sitecore/system/Languages/da" />
                        <include name="Languages.Japanese" database="master" path="/sitecore/system/Languages/ja-JP" />-->

                        <include name="Content" database="master" path="/sitecore/content/Home" />
                        <include name="GlobalSettings" database="master" path="/sitecore/content/Global" />
                        <!--<include name="Media" database="master" path="/sitecore/media library/Logistico" />-->
                        <!--<include name="Metadata" database="master" path="/sitecore/system/settings/feature/metadata/Habitat" />

                        <include name="Profiling" database="master" path="/sitecore/system/Marketing Control Panel/Profiles/Habitat" />
                        <include name="Outcomes" database="master" path="/sitecore/system/Marketing Control Panel/Outcomes/Habitat" />
                        <include name="Campaigns" database="master" path="/sitecore/system/Marketing Control Panel/Campaigns/Habitat" />
                        <include name="Goals" database="master" path="/sitecore/system/Marketing Control Panel/Goals/Habitat" />-->
                      
                    </predicate>
                    <!--<rolePredicate>
                        <include domain="extranet" pattern="^Project Logistico .*$" />
                    </rolePredicate>-->
<!--
                    TODO: Bug in Unicorn with SC v9?
                    <userPredicate type="Unicorn.Users.UserPredicates.ConfigurationUserPredicate, Unicorn.Users" singleInstance="true">
                        <include domain="extranet" pattern="^((?!Anonymous).)*$" />
                    </userPredicate>
                    <userSyncConfiguration defaultPassword="b" minPasswordLength="1" />
-->
                </configuration>
            </configurations>
        </unicorn>
    </sitecore>
</configuration>

Similarly you will need a Unicorn configuration at each Feature for serializing the Feature related items.

Eg. Sample configuration from Feature Layer serialization config.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/">
    <sitecore role:require="Standalone or ContentManagement">
        <unicorn>
            <configurations>
                <configuration name="Feature.Identity" description="Identity content" dependencies="Foundation.*">
                  <targetDataStore physicalRootPath="$(sourceFolder)\$(layer)\$(module)\serialization" useDataCache="false" singleInstance="true"/>
                    <predicate>
                      <include name="Identity.Renderings" database="master" path="/sitecore/layout/Renderings/Feature/Identity" />
                      <include name="Identity.Templates" database="master" path="/sitecore/templates/Feature/Identity" />
                      <include name="Identity.Media" database="master" path="/sitecore/media library/Logistico/Identity" />
                      <!--<include name="Identity.PlaceholderSettings" database="master" path="/sitecore/layout/placeholder settings/Project/Logistico" />-->
                    </predicate>
                </configuration>
            </configurations>
        </unicorn>
    </sitecore>
</configuration>

After making all these configurations, publish your changes to the website folder (Sitecore instance folder under wwwroot) so that site will use these configurations and Unicorn can be accessed.

Once installation and configuration are completed, Unicorn can be accessed with below url

https://yoursiteurl/unicorn.aspx

Before accessing this url, make sure that you have been logged in to the Sitecore with administrator user credentials, else you will not be able to see the unicorn admin page instead you will get Access Denied message.

If you have loaded the Unicorn for the first time after publishing the Config files, then you will need to perform that initial serialization.

In this blog we learnt about Installing and Configuring Unicorn for the Helix based Sitecore Project.

Thank you.. Keep Learning.. Keep Sitecoring.. 🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s