Taxonomies not deployed - Sitecore misconfiguration after upgrade

Working with a Sitecore implementation that has been upgraded from an earlier Sitecore version than version 8 can sometimes lead to issues related to an imcomplete or incorrect upgrade, such as missing settings and configurations.

One common oversight is failling to update:
Html.Sitecore().Placeholder() to
Html.Sitecore().DynamicPlaceholder().

Additionally, there can be instances where certain configurations are not correctly defined, particularly those introduced in the current major version of the specific implementations, which in this project is version 9.

We encountered issues with the taxonomies not being deployed, resulting in Channels, Devices, etc. being marked as "Unknown" in the Experience Profiler:


Once examining the database, we noticed that all taxonomy entries were null:

SELECT * FROM [prod_ReferenceData].[xdb_refdata].[DefinitionTypes] dt left join [prod_ReferenceData].[xdb_refdata].[Definitions] d on d.TypeID=dt.id where d.ID is null

We initiated the "Deploy marketing definitions" from the control panel. This deployment process runs asynchronously. 

Initially, it appeared that everything was deployed as expecgted - there were no errors in the Sitecre Client. however, no taxonomies were actually deployed, and the taxonomy values remained null in the database

We also attempted to deploy the taxonomies from the Content Editor:


But here we encountered an error:


The error: 
Exception: System.NotSupportedException
Message: Getting all taxon entities under the specified root is currently not supported.
Source: Sitecore.Marketing.Operations.Xdb.ReferenceData.Service
   at Sitecore.Marketing.Operations.Xdb.ReferenceData.Service.Taxonomy.TaxonomyReferenceDataRepository.GetAll(Guid rootId)
   at Sitecore.Marketing.Taxonomy.TaxonomyManager.GetAll()
   at Sitecore.Marketing.xMgmt.ReferenceData.Observers.Activation.Taxonomy.Deployment.DeployManager.DeployAsync(Guid rootId)
   at Sitecore.Marketing.xMgmt.ReferenceData.Observers.Activation.Taxonomy.Deployment.DeployManager.Deploy(Guid rootId)

This error clarified the situation. It was a well-known and reported issue from Sitecore: Known Issues - "Getting all taxon entities under the specified root is currently not supported." error in the logs (sitecore.com)

After following the steps described in the Sitecore article...

  1. Open the Sitecore.Marketing.Taxonomy.Xdb.ReferenceData.config file.
  2. Find the line following line which determines the server role:
  3. <sitecore role:require="Processing or Reporting">
  4. change it to:
  5. <sitecore role:require="(Processing or Reporting) AND !ContentManagement">
  6. Redeploy the Marketing definitions.

... we verified that the config file in the Content Management server now included the necessary changes: 


We did one more deployment of the marketing taxonomies and the Experience Profiler start working as expected. 

Sitecore CLI and content migration from 8.2 to 10.2

During one of the many Sitecore upgrades this year (to Sitecore version 10.2), I participated in a team of developers where one of the developers strongly wanted a specific strategy for moving content from the old solution into the new Sitecore 10.2 vanilla solution (instead of using Sitecore standard upgrade scripts/wizard for migrating/upgrading databases).


The implemented solution was a history/result of previously different upgrades with different item serialization implementations (both unicorn and TDS was in use at the same time - making it hard for developers to manage items to be serialized to code repo for deployment etc). 


The process was decided to be an installation of Sitecore CLI in a vanilla Sitecore 10.2 solution, but attaching the source databases of the Sitecore 8.2 solution containing the content to be serialized - templates, layout ref. items etc. Then move the Sitecore CLI *.module.json files and serialized items (all the *.yml files) from the vanilla Sitecore 10.2 solution to the code upgraded solution in the respective Helix folder structures, and then after that serialize all the items into the new upgraded Sitecore solution (no comments on this process... tsk, tsk).


Anyway, the source Sitecore 8.2 solution contained customization in the core database (for custom commands in the Content Editor ribbon etc.). As I initially tried to install the Sitecore CLI in the vanilla Sitecore 10.2 solution but attached with Sitecore 8.2 databases, I was not able to log into the Sitecore client (by trying to login to Sitecore client via Sitecore CLI for serializing items). I got the following error in powershell: 




And the Sitecore client:


The Identity Server log stated: 

Error Number:208,State:1,Class:16"

Microsoft.Data.SqlClient.SqlException (0x80131904): Invalid object name 'PersistedGrants'.

   at Microsoft.Data.SqlClient.SqlCommand.<>c.<ExecuteDbDataReaderAsync>b__164_0(Task`1 result)

   at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke()

   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)

--- End of stack trace from previous location where exception was thrown ---

   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)

--- End of stack trace from previous location where exception was thrown ---

   at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)

   at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)

   at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)

   at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.AsyncEnumerator.InitializeReaderAsync(DbContext _, Boolean result, CancellationToken cancellationToken)

   at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)

   at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()

ClientConnectionId:10e2a893-524b-442c-9cea-6a4fbc67be9f

Error Number:208,State:1,Class:16


Stated that a PersistantGrants table in the Sitecore 8.2 core database was missing (the PersistedGrants table was introduced in Sitecore 10) - used by the IdentityServer4. 

By adding the table into the source Sitecore 8.2 Core database using: 


USE [Customer.Sitecore.Core] GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [dbo].[PersistedGrants] (
[Key] nvarchar(200) NOT NULL,
[Type] nvarchar(50) NOT NULL,
[SubjectId] nvarchar(200) NULL,
[SessionId] nvarchar(100) NULL,
[ClientId] nvarchar(200) NOT NULL,
[Description] nvarchar(200) NULL,
[CreationTime] datetime2 NOT NULL,
[Expiration] datetime2 NULL,
[ConsumedTime] datetime2 NULL,
[Data] nvarchar(max) NOT NULL,
CONSTRAINT [PK_PersistedGrants] PRIMARY KEY ([Key])
);

The table was added. But then leaving to the next missing table (stated in a log error similar to the one above for the missing PersistedGrants table). The DeviceCodes. Once again adding the DeviceCodes table:

USE [Customer.Sitecore.Core] GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [DeviceCodes] (
[UserCode] nvarchar(200) NOT NULL,
[DeviceCode] nvarchar(200) NOT NULL,
[SubjectId] nvarchar(200) NULL,
[SessionId] nvarchar(100) NULL,
[ClientId] nvarchar(200) NOT NULL,
[Description] nvarchar(200) NULL,
[CreationTime] datetime2 NOT NULL,
[Expiration] datetime2 NOT NULL,
[Data] nvarchar(max) NOT NULL,
CONSTRAINT [PK_DeviceCodes] PRIMARY KEY ([UserCode])
);

After that, the tables was created, and the Sitecore CLI could log into the Sitecore Client, and I could continue serializing the customization made in the Sitecore 8.2 core database...

Sitecore and client time out

So some month ago I had a customer that was contacted by Sitecore (version 10.2), because of heavy use of Sitecore user boosting (you know, when the limit of concurrent users has been reached - the one trying to login can boost the number of concurrent users. Typically by boosting five more temp users). 


As the customer didn't have more content "contributors" (editors, marketers, admins etc) working in the client, than the number of concurrent users in the license subscribed, the issue was not because of incorrect license, but more of contributors not logging out or correctly. 


After changing the session timeout in the /App_Config/Sitecore.config file from two hours (it was an old change and the customer was one handed over by another Sitecore implementation partner) to 20 minutes:


<!--  AUTHENTICATION CLIENT SESSION TIMEOUT
    Specifies the number of minutes before Sitecore considers user authentication session tickets as expired.
    This setting is only relevant for users logging in to Sitecore Client and when the Sitecore license has a limited number
    of concurrent editors. 
    All expired sessions will automatically be removed when a new user tries to log in and the maximum
    number of concurrent editors has been reached. 
    The default is 60 minutes (1 hour).
--> 

<setting name="Authentication.ClientSessionTimeout" value="20" />


We still had the issue with exceeding the number of concurrent users. As the customer was using Sitecore Identity and Sitecore Identity Server the user at the Sitecore Identity Server was still active. But by aligning the Sitecore Identity Server session timeout / accesstoken/identitytoken lifetime with the Authentication client session timeout, the content contributor was correctly logged out after 20 minutes of idle  - making the use of Sitecore User Boosting much rare.



Sitecore 10 Forms and special characters in list controls

When you create forms in Sitecore Forms, it provides you with functionality to managing multilingual forms setup. Creating a new form each control in the form will be created as a separate item for each specific control. But in the case of list fields where each list item is created as a static list item (and list items not rendered from existing items), for the item name Sitecore uses the field value if the field name/label is empty. All good, all good – until you want to use a not valid character in the field value/label like ë, ä etc. Then Sitecore overrides the value to be item name compliant. And therefor, the values in the list control will be changed to item name compliant values. Sitecore simply removes/deletes the incompliant characters. But is that really what you want? Don’t you want to be able to use language specific characters to be displayed or stored from your list control?


The issue is that Sitecore uses the ItemUtil.ProposeValidItemName for creating the display name for the item. Making Sitecore Forms compliant with these characters, then instead of using the ProposeValidItemName when creating the items, we will simply use the listFieldItem.Text or listFieldItem.Value.



As an example we replace the I"temUtil.ProposeValidItemName(listFieldItem.Text) : ItemUtil.ProposeValidItemName(listFieldItem.Value)" with "listFieldItem.Text : listFieldItem.Value"

(from Sitecore.ExperienceForms.Mvc.DataSource.DataSourceSettingsManager.UpdateStaticItems()):  

using System;

using System.Collections.Generic;

using System.Globalization;

using Sitecore;

using Sitecore.Data;

using Sitecore.Data.Items;

using Sitecore.Diagnostics;

using Sitecore.ExperienceForms.Extensions;

using Sitecore.ExperienceForms.FieldSettings;

using Sitecore.ExperienceForms.Mvc.Constants;

using Sitecore.ExperienceForms.Mvc.DataSource;

using Sitecore.ExperienceForms.Mvc.Models;

 

namespace Norican.Infrastructure.Web.Sc.Forms

{

    public class DataSourceSettingsManagerWithDisplayName : DataSourceSettingsManager

    {

        protected override void UpdateSettings(

            ListFieldItemCollection settings,

            Item fieldSettingsItem,

            FieldSettingsContext settingsContext)

        {

            Assert.ArgumentNotNull((object)settings, nameof(settings));

            Assert.ArgumentNotNull((object)fieldSettingsItem, nameof(fieldSettingsItem));

            Assert.ArgumentNotNull((object)settingsContext, nameof(settingsContext));

            if (settingsContext.FieldItem == null)

                return;

            if (MainUtil.GetBool(settingsContext.FieldItem.Fields["Is Dynamic"]?.Value, false))

            {

                foreach (Item child in fieldSettingsItem.Children)

                    child.Recycle();

            }

            else

            {

                string displayFieldName = settingsContext.FieldItem.Fields["Display Field Name"]?.Value;

                string valueFieldName = settingsContext.FieldItem.Fields["Value Field Name"]?.Value;

                this.UpdateStaticItemsInternal((List<ListFieldItem>)settings, fieldSettingsItem, displayFieldName, valueFieldName);

            }

        }

 

        protected void UpdateStaticItemsInternal(

      List<ListFieldItem> items,

      Item fieldSettingsItem,

      string displayFieldName,

      string valueFieldName)

        {

            Assert.ArgumentNotNull((object)items, nameof(items));

            Assert.ArgumentNotNull((object)fieldSettingsItem, nameof(fieldSettingsItem));

            for (int index = 0; index < items.Count; ++index)

            {

                ListFieldItem listFieldItem = items[index];

                if (string.IsNullOrEmpty(listFieldItem.Value))

                {

                    listFieldItem.ItemId = "";

                }

                else

                {

                    string str = ItemUtil.ProposeValidItemName(listFieldItem.Value);

                    Item obj = ID.IsID(listFieldItem.ItemId) ? fieldSettingsItem.Database.GetItem(listFieldItem.ItemId, fieldSettingsItem.Language) : (Item)null;

                    if (obj == null)

                        obj = this.AddItem(str, fieldSettingsItem, new TemplateID(TemplateIds.ListFieldTemplateId));

                    else if (!obj.Axes.IsDescendantOf(fieldSettingsItem))

                        obj = obj.CopyTo(fieldSettingsItem, str);

                    listFieldItem.ItemId = obj?.ID.ToString() ?? string.Empty;

                    if (obj != null)

                    {

                        if (string.IsNullOrEmpty(valueFieldName))

                            valueFieldName = obj.Template.IsBasedOnTemplate(TemplateIds.ListFieldTemplateId) ? "Value" : "__ItemName";

                        obj.Editing.BeginEdit();

                        obj.Name = str;

                        obj.Fields[valueFieldName]?.SetValue(listFieldItem.Value, false);

 

                        /* Use item text or item value as displayName.

                           Original Sitecore code uses ItemUtil.ProposeValidItemName:

                           ... ItemUtil.ProposeValidItemName(listFieldItem.Text) : ItemUtil.ProposeValidItemName(listFieldItem.Value); */

                        (string.IsNullOrEmpty(displayFieldName) || obj.Fields[displayFieldName] == null ? obj.Fields[FieldIDs.DisplayName] : obj.Fields[displayFieldName])?.

                            SetValue(!string.IsNullOrEmpty(listFieldItem.Text) ? listFieldItem.Text : listFieldItem.Value, false);

                        

                        obj.Fields[FieldIDs.Sortorder]?.SetValue((index * 100).ToString((IFormatProvider)CultureInfo.InvariantCulture), false);

                        obj.Editing.EndEdit();

                    }

                }

            }

            foreach (Item child1 in fieldSettingsItem.Children)

            {

                Item child = child1;

                if (!items.Exists((Predicate<ListFieldItem>)(li => child.ID.ToString().Equals(li.ItemId, StringComparison.OrdinalIgnoreCase))))

                    child.Delete();

            }

        }

    }

}



And adding it to the config file: 

<sitecore>

  <services>

      <register patch:instead="register[@implementationType='Sitecore.ExperienceForms.Mvc.DataSource.DataSourceSettingsManager, Sitecore.ExperienceForms.Mvc']" serviceType="Sitecore.ExperienceForms.FieldSettings.IFieldSettingsManager`1[[Sitecore.ExperienceForms.Mvc.Models.ListFieldItemCollection, Sitecore.ExperienceForms.Mvc]], Sitecore.ExperienceForms" implementationType="Norican.Infrastructure.Web.Sc.Forms.DataSourceSettingsManagerWithDisplayName, Norican.Infrastructure" lifetime="Transient" />

    </services>

</sitecore>