Sitecore Xperiences - The things I've seen as a Sitecore Developer

Part 2 – XConnect Avatar Facet breaking Experience Profile (Follow Up)

If you read my last post – or if you just passing by got interested in the subject, here are a few follow-ups:

  1. When you create your custom Facet to store the bigger version of the image, make sure to decorate it with [DoNoIndex] as we don’t need the image to be indexed
    [FacetKey(DefaultFacetKey)]
    public class UserPicture : Facet
    {
        public const string DefaultFacetKey = "UserPicture";
    
        /// <summary>
        /// Base64 of the picture
        /// </summary>
        [DoNotIndex]
        public string Picture { get; set; }
    }
  2. xDB is not the best storage for images – Base64 encoded files are rawly 37% bigger than the original image. Although xDB can store files of any size, xConnect can eventually slow down when you have a high number of bigger files. Instead, you can save only the image reference on xDB, and store the file somewhere else.
  3. In the last article, when I say the Base64 was truncated on SQL Server, I was actually wrong.
    It is only truncated on Management Studio, but the full information is on xDB. You can even read and write it, but still, it breaks Experience Profile when you use the native “Avatar” facet.
Publicado em XConnect

XConnect Avatar Facet breaking Experience Profile

During the last Sitecore Hackathon, I’ve been through a very strange and annoying issue with my Contacts, while developing our Face Login module.

This happens in Sitecore 9.1, but previous versions should also be affected.

When the issue starts, your Experience Profile gets “frozen” with the contacts that you already have. Any new contact will not appear.

Experience Profile broken

Your Indexer log also will start to show this error:

2019-03-07 11:44:31.739 -03:00 [Error] The attempt to recover from previous failure has not been successful. There will be another attempt. Attempts count: 35
Sitecore.Xdb.Collection.Failures.DataProviderException: *** [xdb_collection.GetContactsChanges], Line 27. Errno 50000: Sync token is no longer valid for [Contacts] table. ---> System.Data.SqlClient.SqlException: *** [xdb_collection.GetContactsChanges], Line 27. Errno 50000: Sync token is no longer valid for [Contacts] table.
   at System.Data.SqlClient.SqlCommand.<>c.<ExecuteDbDataReaderAsync>b__180_0(Task`1 result)
   at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke()
   at System.Threading.Tasks.Task.Execute()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Sitecore.Xdb.Sql.Common.Extensions.DbCommandExtensions.<>c__DisplayClass1_0.<<ExecuteReaderWithRetryAsync>b__0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Sitecore.Xdb.Sql.Common.Extensions.DbCommandExtensions.<ExecuteReaderWithRetryAsync>d__1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Sitecore.Xdb.Sql.Common.Extensions.SqlCommandExtensions.<ExecuteReaderWithRetryAsync>d__1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Sitecore.Xdb.Sql.Common.Extensions.SqlCommandExtensions.<ExecuteReaderWithRetryAsync>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Sitecore.Xdb.Collection.Data.SqlServer.Managers.SqlDataRecordsManager`2.<>c__DisplayClass75_0.<<ReadChanges>b__0>d.MoveNext()
   --- End of inner exception stack trace ---
   at Sitecore.Xdb.Collection.Data.SqlServer.Managers.SqlDataRecordsManager`2.<>c__DisplayClass75_0.<<ReadChanges>b__0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Sitecore.Xdb.Collection.Data.SqlServer.Managers.SqlDataRecordsManager`2.<ReadChanges>d__75.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Sitecore.Xdb.Collection.Data.SqlServer.Managers.SqlDataRecordsManager`2.<GetChanges>d__51.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Sitecore.Xdb.Collection.Data.SqlServer.SqlDataProvider.<GetChanges>d__16.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Sitecore.Xdb.Collection.DataProviderCountersDecorator.<GetChanges>d__14.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Sitecore.Xdb.Collection.DataProviderCountersDecorator.<GetChanges>d__14.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Sitecore.Xdb.Collection.Indexing.Indexer.<>c__DisplayClass9_0.<<GetChanges>b__0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Sitecore.Xdb.Collection.Indexing.IndexerExtensions.<IndexNextChangesSimple>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Sitecore.Xdb.Collection.Indexing.SingleThreadedIndexer.<IndexNextChangesWithTiming>d__8.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Sitecore.Xdb.Collection.Indexing.SingleThreadedIndexer.<RunInThread>d__6.MoveNext()

What’s going on?

Good question… Wanted to take a look at my Shard databases, more specifically at the ContactFacets database:

SELECT * FROM [xdb_collection].[ContactFacets]
WHERE FacetKey='Avatar'

This will list the Avatar facet of my contacts:

Avatar Facets results

Look closer to the FacetData field – this is where the serialized Avatar Facet is stored. Due to the size of the image I uploaded, this will be a very long string.

In my case, this string was truncated:

{"@odata.type":"#Bookshelf.Repository.XConnect.Facets.BookshelfPicture","Picture":"/9j/4AAQSkZJRgABAQEASABIAAD (... ... ... ) 8QP7K1LxJHrUJm

Resulting in a value that cannot be de-serialized, and thus causing all issues previously described.

How to fix it?

In order to fix this issue, you will need to get rid of your broken Avatars from Shard0 and Shard1 databases.

  • Run this SQL (make sure to point to your database names)
    DELETE FROM [Collection.Shard0].[xdb_collection].[ContactFacets] where FacetKey='Avatar'
    DELETE FROM [Collection.Shard1].[xdb_collection].[ContactFacets] where FacetKey='Avatar'

    This script is deleting all Avatars from all Contacts. You can add more filters to delete only the corrupted ones

  • Restart your Indexer Service
  • Request an XConnect rebuild:
    XConnectSearchIndexer -rr

After that, your Experience Profile will get back to normal.

How to avoid this issue to re-appear?

The Avatar Facet was not designed to store huge files – an Avatar usually has a size of 200×200. In order to avoid this issue in the future, this is what I’ve done:

  • Created a custom Facet to store the Base64 information of the big picture;
  • Limited the upload of files to 1.5Mb maximum;
  • When a picture is uploaded, two versions are stored by XConnect:
    • Picture Facet (Custom) – Stores the original size of the image
    • Avatar Facet (OOTB) – Stores a smaller version of the image, resized to the width of 200px

By doing this, my new contacts are appearing correctly on Experience Profile.

Publicado em XConnect Marcado com:

Powershell script to find TDS files with length error

Powershell script to find TDS files with lenght error

A very well described problem with TDS is the File Name length error. This article from Hedgehog brings everything you need to know about the issue itself, and how to work it out. The solution is to use the “Alias” TDS feature to cut down the file path length.

For instance, if you have an item with a very long name such as “Subpage with Left Rail Without Footer”, you could choose an Alias as “sub3″ and save 33 characters. Of course, your original item name is preserved, as this only applies to the file system.

In short:

  • File path should not exceed 260 chars
  • Folder path should not exceed 248 chars

What causes and what to do about

  1. You have cloned the repository into a folder with a too large name
    What to do about: 

    1. Make sure you have something short like:
      C:srcABC
      instead of
      C:Source ControlMy Client Name Is LongMy Long Project Name
  2. TDS project name is too wide – due to the Helix standard TDS projects can have longer names (Eg: MyProject.Foundation.DependencyInjection.Master)
    What to do about:

    1. New projects: avoid long names (Eg: DI instead of DependencyInjection)
    2. Existent projects:
      1. Use the Powershell script to find long paths and apply Aliases
      2. Be proactive to apply Aliases to TDS long names
  3. The Sitecore Item Path added to TDS (including the item itself) is too long
    What to do about:

    1. Use the Powershell script to identify long paths, then apply a TDS Alias to each item that makes it longer
    2. EG: this path
      /sitecore/content/mywebsite/This page has a big name/But this page also has a big name/NotToBlame
      has 2 problematic items:

      1. “This page has a big name”
      2. “But this page also has a big name”
    3. Those 2 items must be added to TDS and have their “File System Alias” setup to something smaller

Powershell script

When a certain environment has this issue, make sure to follow the steps described earlier. However, to execute Step 3 you should first discover what items are problematic.

To quickly obtain such a list, use the following Powershell script:

# Script Setup
$pathToScan = “D:srcABC”;
$outputToCsv = $false;
$outputToPrompt = $true;
$maxLength = 225;
#########

cls;

if ($pathToScan.IndexOf(“src”) -eq -1){
$pathToScan = “$pathToScansrc”;
}

# Get all TDS folders
$tdsFolders = Get-ChildItem -Path $pathToScan -File -Recurse | Where-Object {($_.FullName -like ‘*.Master*’) -or ($_.FullName -like ‘*.Core*’)} | Where-Object {($_.FullName -notlike ‘*bin*’)};
#$tdsFolders = Get-ChildItem -Path $pathToScan -Directory -Recurse;

# Add properties
foreach ($folder in $tdsFolders){
$folder | Add-Member PathLength $folder.FullName.Length;
}

# Sort
$tdsFoldersSorted = $tdsFolders | sort FullName | sort PathLength -Descending;

# Get Max Length to filter those that will not match
#$maxLength = Read-Host -Prompt ‘Max Length allowed';
$tdsFoldersToShow = $tdsFoldersSorted | Where-Object {$_.PathLength -gt $maxLength};

# Output Loop
$fileName = “$PSScriptRootbeyond $maxLength.csv”;
$csvText = “”;
if ($outputToCsv){
$csvText = “$($csvText)Length,FullName`n”;
#Add-Content -Path $fileName -Value “Length,FullName”;
}
if ($outputToPrompt){
Write-Output(“Length,FullName”);
}

foreach ($folder in $tdsFoldersToShow){
if ($outputToCsv){
$csvText = “$($csvText)$($folder.PathLength),$($folder.FullName)`n”;
#Add-Content -Path $fileName -Value “$($folder.PathLength),$($folder.FullName)”;
}
if ($outputToPrompt){
Write-Output(“$($folder.PathLength),$($folder.FullName)”);
}
}

if ($outputToCsv){
Add-Content -Path $fileName -Value $csvText;
Write-Output(“File $fileName saved”);
}

Instructions

  1. Open the script in Powershell ISE as Administrator
  2. Setup script changing variables at the top:
    1. $pathToScan – should point to your git folder
    2. $outputToCsv – Set as $true if you want the script to create a CSV file with results, $false otherwise
    3. $outputToPrompt – Set as $true if you want the script to display results at prompt, $false otherwise
    4. $maxLength – Max length tolerated to a TDS item path – the script will list everything that goes beyond this vale
  3. Run the script
  4. Use the list obtained to apply instructions Aliases, making paths shorter
Publicado em Powershell, TDS

Custom Reset Layout in Content and Experience Editor Modes

Custom-Reset-Layout-in-Content-and-Experience-Editor-Modes

Recently I had to customize the code that triggers when a Reset Layout is executed. If you ever had to attach any code to it, this post is for you!

You know, that sympathetic prompt:

Reset Layout Prompt

The first thing you need to know is that Content Editor and Experience Editor does that differently, so let’s go for them:

Round 1 – Content Editor

Content Editor uses a command for that (pagedesigner:reset), which is natively implemented by the class Sitecore.Shell.Applications.Layouts.PageDesigner.Commands.Reset. We first need to create a class that will inherit from the original, so we don’t lose the original behavior, and implement our custom logic on it.

Create a Command class like this:

using System.Web.Mvc;
using Sitecore.Shell.Applications.Dialogs.ResetLayout;
using Sitecore.Web.UI.Sheer;

namespace MyProject.Commands
{
    public class ResetLayoutCommand : Sitecore.Shell.Applications.Layouts.PageDesigner.Commands.Reset
    {
        protected override void Run(ClientPipelineArgs args)
        {
            // Runs the default behaviour
            base.Run(args);

            // Skips if the Reset Layout prompt is not submited or results is undefined
            if (!args.IsPostBack || string.IsNullOrEmpty(args.Result) || args.Result == "undefined")
                return;

            // Takes the item that has been reset
            var itemReset = DeserializeItems(args.Parameters["items"])[0];

            // And here is where your custom logic will be implemented
            // .....
        }
    }
}

Then we override the default command with our class in a config patch like this:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
    <sitecore>
        <commands>
            <command patch:instead="*[@name='pagedesigner:reset']" name="pagedesigner:reset" resolve="true" 
                type="MyProject.Commands.ResetLayoutCommand, MyProject"/>
        </commands>
    </sitecore>
</configuration>

And this is how we run the first leg. But it is not over yet, let’s go for…

Round 2 – Experience Editor

Experience Editor, on the other hand, uses a different approach for that. It has a request node named “ExperienceEditor.ResetLayout.Execute” under <sitecore.experienceeditor.speak.requests> that we need to replace. This request node is implemented by the native class Sitecore.ExperienceEditor.Speak.Ribbon.Requests.ResetLayout.Execute, which again we are going to inherit.

So our Request class will be like this:

using System;
using System.Web.Mvc;
using Sitecore.Diagnostics;
using Sitecore.ExperienceEditor.Speak.Attributes;
using Sitecore.ExperienceEditor.Speak.Server.Responses;
using Sitecore.Shell.Applications.Dialogs.ResetLayout;

namespace MyProject.ExperienceEditor
{
    public class ResetLayout : Sitecore.ExperienceEditor.Speak.Ribbon.Requests.ResetLayout.Execute
    {
        [HasItemPermissions(Id = "{BE98D7F0-7404-4F97-8E4C-8FEF4ACA5DA3}", Path = "/sitecore/content/Applications/WebEdit/Ribbons/WebEdit/Advanced/Layout/Reset")]
        public override PipelineProcessorResponseValue ProcessRequest()
        {
            // Instantiates the returning object and 
            var processorResponseValue = new PipelineProcessorResponseValue();

            try
            {
                // Executes the default RESET LAYOUT process
                processorResponseValue = base.ProcessRequest();

                // Takes the item that has been reset
                var itemReset = RequestContext.Item;

                // And here is where your custom logic will be implemented
                // ..... (OF COURSE, DON'T DUPLICATE YOUR LOGIC!)
            }
            catch (Exception e)
            {
                Log.Error(
                    $"[ResetLayout] Cannot execute post Layout Reset operations to item '{RequestContext.Item.Paths.Path}'", e, GetType());
            }
            return processorResponseValue;
        }
    }
}

And finally we patch it again with a config file like this:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
    <sitecore>
        <sitecore.experienceeditor.speak.requests>
            <request patch:instead="*[@name='ExperienceEditor.ResetLayout.Execute']"
                name="ExperienceEditor.ResetLayout.Execute"
                type="MyProject.ExperienceEditor.ResetLayout, MyProject" />
        </sitecore.experienceeditor.speak.requests>
    </sitecore>
</configuration>

And that’s all… You now have your custom code being triggered by both Content Editor and Experience Editor!

Publicado em Content Edition Experience, Development, Experience Editor

Sitecore 9 Update 1 – Bug saving Shared Layout in Experience Editor

Sitecore-9-Update-1-Bug-saving-Shared-Layout-in-Experience-Editor

A few weeks ago, while upgrading one of at Nish Tech’s early-stages projects into Sitecore 9 Update 1, a very strange behavior when you try to do a very simple task: edit a Shared Layout of a page in Experience Editor.

When you try to do that, the infamous screen “Layout not Set” shows right after saving.

Sitecore 9 Bug

Initially, I observed that when saving in a _Standard Values, but later I noticed it can also happen with ordinary items. If you want to see how to reproduce the error, check this video.

Interesting enough, when you look at your Layout and Final Layout fields in Raw mode, you notice that the Shared Layout has a value such as:

<r xmlns:p=”p” xmlns:s=”s” p:p=”1″><d id=”{FE5D7FDF-89C0-4D99-9AA3-B5FBD009C9F3}”><r uid=”{597C17AD-DEF3-4947-BE5B-4104A2143F17}” p:after=”*[1=2]” s:id=”{493B3A83-0FA7-4484-8FC9-4680991CF743}” s:ph=”/main/centercolumn/content” /></d></r>

Which seems to have a more appropriate syntax to be in the Final Layout field instead, as you can check if you match this against the original value:

<r xmlns:xsd=”http://www.w3.org/2001/XMLSchema”><d id=”{FE5D7FDF-89C0-4D99-9AA3-B5FBD009C9F3}” l=”{14030E9F-CE92-49C6-AD87-7D49B50E42EA}”><r ds=”” id=”{885B8314-7D8C-4CBB-8000-01421EA8F406}” par=”” ph=”main” uid=”{43222D12-08C9-453B-AE96-D406EBB95126}” /><r ds=”” id=”{CE4ADCFB-7990-4980-83FB-A00C1E3673DB}” par=”” ph=”/main/centercolumn” uid=”{CF044AD9-0332-407A-ABDE-587214A2C808}” /><r ds=”” id=”{493B3A83-0FA7-4484-8FC9-4680991CF743}” par=”” ph=”/main/centercolumn/content” uid=”{B343725A-3A93-446E-A9C8-3A2CBD3DB489}” /></d><d id=”{46D2F427-4CE5-4E1F-BA10-EF3636F43534}” l=”{14030E9F-CE92-49C6-AD87-7D49B50E42EA}”><r ds=”” id=”{493B3A83-0FA7-4484-8FC9-4680991CF743}” par=”” ph=”content” uid=”{A08C9132-DBD1-474F-A2CA-6CA26A4AA650}” /></d></r>

Sitecore Helpdesk heroes

Then I reported the issue to our amazing Sitecore Helpdesk, who after a couple weeks came out with a bugfix, that now I want to share with our Sitecore Community.

I would like to thank the whole gang of superheroes at Sitecore Helpdesk, for providing this good HotFix in a proper time and allowing me to share it, in special to Igor DenisenkoPavel Ivashchenko and Ielyzaveta Kalinchuk, the good guys who replied to my ticket. You are awesome, mates!

The cure: HotFix 203387

You can find the hotfix at this link.

  • Please be aware that the hotfix was built specifically for Sitecore XP 9.0 rev. 171219 (Update-1)! You should not install it on other Sitecore versions or in combination with other hotfixes unless explicitly instructed by Sitecore Support;
  • Note that you need to extract ZIP file contents to locate installation instructions and related files inside it.

If you have anything to add on this, please drop a comment and let me know!

Publicado em Bug fixing, Experience Editor, Support Ticket, Uncategorized

Rodrigo Peplau Wins Sitecore “Most Valuable Professional” Award

Elite distinction awarded for exceptional contributions to the Sitecore ecosystem

FLORIANÓPOLIS – SC, Brazil – January, 31th 2018 – Nish Tech today announced that Suresh Devanan, Himadri Chakrabarti, Rodrigo Peplau, and José Neto have all been named a “Most Valuable Professional (MVP)” in the Technology Category by Sitecore®, the global leader in experience management software. These four recipients are part of an elite group of only 208 Technology MVPs worldwide to be named a Sitecore MVP this year.

Now its 12th year, Sitecore’s MVP program recognizes individual technology, strategy, and commerce advocates who share their Sitecore passion and expertise to offer positive customer experiences that drive business results. The Sitecore MVP Award recognizes the most active Sitecore experts from around the world who participate in online and offline communities to share their knowledge with other Sitecore partners and customers.

“I am honored to be named a Sitecore MVP for the fourth year in a row.  I am very proud of Nish Tech’s recurring MVPs (Himadri, Rodrigo and myself) for maintaining our MVP status by being actively involved in the Sitecore Community, and excited for our most recent hire Jóse Neto, as he earns MVP designation for the first time.  We strive to share this expertise with our clients and the Sitecore community.”, stated Suresh Devanan, President of Nish Tech.

Nish Tech excels at creating efficient, flexible digital solutions to execute web marketing strategies and eCommerce plans. Through system integrations of best-in-class technology, we strive to perfect your web presence.

“The Sitecore MVP awards recognize and honor those individuals who make substantial contributions to our loyal community of partners and customers,” said Pieter Brinkman, Sitecore Senior Director of Technical Marketing. “MVPs consistently set a standard of excellence by delivering technical chops, enthusiasm, and a commitment to giving back to the Sitecore community. They truly understand and deliver on the power of the Sitecore Experience Platform to create personalized brand experiences for their consumers, driving revenue and customer loyalty.”

The Sitecore Experience Platform™ combines web content management, omnichannel digital delivery, insights into customer activity and engagement, and strategic digital marketing tools into a single, unified platform. Sitecore Experience Commerce™ 9, released in January 2018, is the only cloud-enabled platform that natively integrates content and commerce so brands can fully personalize and individualize the end-to-end shopping experience before, during, and after the transaction. Both platforms capture in real time every minute interaction—and intention—that customers and prospects have with a brand across digital and offline channels. The result is that Sitecore customers are able to use the platform to engage with prospects and customers in a highly personalized manner, earning long-term customer loyalty.

Since 2011 Nish Tech has been a digital agency who strives to help our clients gain a competitive advantage in their industry. Using enterprise technology and big data we deliver personalized web experiences for our clients’ site visitors. We focus on understanding our clients’ business needs and finding ways to meet them. For more information, please visit www.nishtech.com for more information.

Publicado em MVP

Still have ‘ERROR SCRIPTDOM NEEDED FOR SQL PROVIDER’ when installing Sitecore 9?

Still-have-ERROR-SCRIPTDOM-NEEDED-FOR-SQL-PROVIDER-when-installing-Sitecore-9-

My friend Daniel Govier was having the following error when installing Sitecore 9 Update 1 using XP0:

[————————————————————————————— InstallWDP : WebDeploy —————————————————————————————-]
[WebDeploy]:[Path] <C:Program FilesiisMicrosoft Web Deploy V3msdeploy.exe>
msdeploy.exe : Error Code: ERROR_SCRIPTDOM_NEEDED_FOR_SQL_PROVIDER
At <C:Program FilesWindowsPowerShellModulesSitecoreInstallFramework1.1.0PublicTasksInvoke-CommandTask.ps1:31> char:13
+ & $Path $Arguments | Out-Default
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (Error Code: ERR…OR_SQL_PROVIDER:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError

More Information: The SQL provider cannot run because of a missing dependency. Please make sure that Microsoft SQL Server Transact-SQL ScriptDom is installed. Learn more at:

http://go.microsoft.com/fwlink/?LinkId=221672#ERROR_SCRIPTDOM_NEEDED_FOR_SQL_PROVIDER.

Following the traditional path of any Sitecore developer, we googled that error and ended up in this blog article from Naveed Ahmad. Problem is that Daniel had already followed his article and installed SQL Server Data-Tier Application Framework, Transact-SQL ScriptDom, and the System CLR Types.

But just like Naveed, that doesn’t work, but we found a different solution instead of modifying Windows Registry.

Do you guys think there are any issues with the following approach?

Register ‘Microsoft.SqlServer.TransactSql.ScriptDom.dll’

More googling sent us to this article. The valid answer is that we still need to register ‘Microsoft.SqlServer.TransactSql.ScriptDom.dll‘. However, of course, the answer was pointing to local folders, so we need to:

  1. Find out where ‘gacutil.exe’ is. It can be somewhere like ‘C:Program Files (x86)Microsoft SDKsWindowsv10.0AbinNETFX 4.6.2 Tools‘ as shown at the article, but it can be somewhere else. Use Windows Explorer to find it, and if you have multiple entries use the one that looks more recent.
    1. Copy the whole path to it – we will need it in step 3
  2. Find your ‘Microsoft.SqlServer.TransactSql.ScriptDom.dll‘ – again look for the newest version
    1. Copy the whole path again to use in step 3
  3. Open your command prompt and type:
    1. cd “Path_from_step_1” (enter)
    2. gacutil” /i “Path_from_step_2Microsoft.SqlServer.TransactSql.ScriptDom.dll” (enter)

If everything goes right your library is now registered on GAC and you will be able to install Sitecore 9!

Publicado em SIF, Sitecore 9

Site-specific RichText Editor Profiles – custom Source in native RichText fields

Site-specific RichText Editor Profiles - custom Source in native RichText fields

Everybody knows how to select different Editor Profiles using the Source property in a Rich Text field. Out of the box, you can only pass a hardcoded path to the Editor Profile, making the template totally linked to one and only one Profile.

Rich Text Source property

Hardcoded???

But what if we could make this dynamic and site-specific? That way, the same template could present different Editor Profiles for different websites.

Something like this:

Rich Text Site-specific Source property

Then at each of your websites you can have your own Source definition item:

Site-Specific RichText Editor Source

Cool?

Site-specific field Sources

If you ever happen to play with Habitat you might have noticed it has a Multisite Module, that implements something similar for Component’s Datasources. My solution is based on that, working perfectly with Droplists and other listing fields.

Not so fast…

But unfortunately, it doesn’t work with the RichText field since it is attached to the “<getLookupSourceItems>” pipeline. The RichText field has a different implementation which does not trigger this pipeline.

Custom Source for Rich Text fields!

So how can this be customized? After a long web research and decompiling Sitecore DLLs, here is what I came out.

If you take a look at the Field item definition under core:/sitecore/system/Field types/Simple Types/Rich Text you will notice it has the following content at the Control field:

Rich Text definition item

I found out that this is similar to how a Command is registered, however no such a Command called “content:RichText” exists anywhere at my Sitecore configs. This is actually registered by the following config entry:

<controlSources>
    <source assembly="Sitecore.Kernel" namespace="Sitecore.Shell.Applications.ContentEditor" prefix="content" mode="on"/>

The secret here is the prefix=”content” which is used as the basis to register the whole namespace, taking classes dynamically by its name (“RichText” in this case).

Step 1 – Extend native class

Create your class that extends the Rich Text field – we will inherit from the original “Sitecore.Shell.Applications.ContentEditor.RichText” and only overwrite the Source property. Notice that I am re-using the same code used at my <getLookupSourceItems> to apply the same logic to the Rich Text field.

using Sitecore.Diagnostics;
using Sitecore.Pipelines.GetLookupSourceItems;
using Website.Core.Multisite.Pipelines;
namespace Website.Core.Fields
{
   public class RichText : Sitecore.Shell.Applications.ContentEditor.RichText
   {
     /// <summary>Gets or sets the source.</summary>
     /// <value>The source.</value>
     public new string Source
     {
       get { return GetViewStateString(nameof(Source)); }
       set
       {
         Assert.ArgumentNotNull(value, nameof(value));
         // Reuse the same Pipeline implementation here as at my <getLookupSourceItems> pipeline
         var processor = new LookupItemsFromField();
         var args = new GetLookupSourceItemsArgs()
         {
           Source = value,
           Item = Sitecore.Context.ContentDatabase.GetItem(ItemID)
         };
         processor.Process(args);
         value = args.Source;
         SetViewStateString(nameof(Source), value);
       }
    }
  }
}

Step 2 – Patch Sitecore to use our custom class

Now we need to patch Sitecore. Our custom class needs to be registered BEFORE the native one so it can assume its place.

<?xml version="1.0"?>
  <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
    <sitecore>
      <controlSources>
        <source patch:before="source[@namespace='Sitecore.Shell.Applications.ContentEditor']" mode="on" namespace="Website.Core.Fields" assembly="Website.Core" prefix="content"/>
      </controlSources>
    </sitecore>
</configuration>

Now that you can intercept the native RichText implementation, you can implement your own logic for Source resolving!

You know a better way of doing this? Please let me know!

Publicado em Content Edition Experience, Experience Editor, Richtext Editor

A Helix/TDS Sitecore Solution you can use

So that finally happened (sorry for the delay!): source code for the Helix/TDS solution presented at the two Sitecore Usergroups – Brazil (watch video) and Cincinnati – is available for everyone to be used.

  • A Helix-Compliant solution, not based in a Habitat but with various “inspirations” (stealing) from there
  • TDS is used to both serialization and publishing to the Website folder
  • The “Foundation” Layer is renamed to “Basis” for alphabetic sorting purposes
  • Runs in Sitecore 8.2 update 3 – but can be easily updated to Sitecore 9 IR
  • Fresh installed with SIM and populated with Visual Studio+TDS thru Solution Deploy command

To access the code check this Git repository:
https://github.com/peplau/helix-solution

Feel free to contribute the way you want!

Publicado em Helix, SUG

Prevent orphan components in Experience Editor when deleting composed components

Prevent-a-component-to-be-deleted-in-Experience-Editor-when-it-has-inner-components-banner

If an editor deletes a component in Experience Editor and that component has inner components, only the outer component will be actually deleted from the page rendering. Inner components will not show anymore (and often Content Editors will be happy), so why would that be a problem?

The problem

Take for instance this set of renderings of a certain page:

Orphan Renderings-before

If in Experience Editor someone tries to delete it:

Remove Components

The result will be a “ghost” orphan item, which will not render when the page loads because its placeholder does not exist anymore.

Orphan Renderings-after

That rendering will continue at the page rendering, being either forgotten forever or re-appearing when someone tries to add a new Carousel to the same Full-Width Container placeholder.

Not just me

I’ve seen more people concerned with that – such as the author of this question at Sitecore Exchange – and some other people suggested possible solutions.

One approach I tried was to follow this blog post by Mike Tschida and implement a pipeline processor to clean-up the Page Rendering, removing orphan components when the page is saved. That would be perfect, however during development or debugging, it is very common to temporarily move a component to an unexistent placeholder to make it vanish from the page. In cases like that, the component would be deleted, which is not desired.

Acting at the other end

So what if, instead of acting later at the server, we stop content editors at the root of the problem: the Experience Editor? At that case, when an editor attempts to delete a component which has inner components, a prompt like this would be displayed. In order to delete the outer component, the editor now needs to delete the inner components first.

Orphan Renderings-prompt

Inspired by this simple Stack Overflow post,  in which the author was looking for a way to simply ask for a confirmation before deleting a component, I ended up with the solution below.

Nip the evil in the bud

The deletion of a component is handled by javascript code located at sitecoreshellApplicationsPage ModesChromeTypesRenderingChromeType.js – more specifically at the deleteControl method. Our fix will comment out the original code (I will leave there for future reference in case of an upgrade) and add the following code:

    deleteControl: function () {
        var canDelete = this.canDeleteControl(this.chrome);
        if (canDelete === false) {
            alert("Please delete inner controls before deleting this.");
            return false;
        }
        var placeholder = this.getPlaceholder();
        if (placeholder) {
            placeholder.type.deleteControl(this.chrome);
            return true;
        }
        return false;
    },

Along of course with the new canDeleteControl method, which does the trick of verifying if the component attempted to be deleted has inner components.

    canDeleteControl: function (chrome) {
        var canDelete = true;
        var childChromes = chrome.getChildChromes();
        for (var i = 0; i < childChromes.length; i++) {
            if (childChromes[i].type.key() == "placeholder") {
                canDelete = this.canDeleteControl(childChromes[i]);
                if (canDelete === false)
                    break;
            }
            else if (childChromes[i].type.key() == "rendering") {
                canDelete = false;
                break;
            }
        }
        return canDelete;
    },

And that’s all we need! Replace this file in all instances, clean up your browser caches and don’t worry about orphan components anymore!

Publicado em Content Edition Experience, Development, Experience Editor, Uncategorized
Social Media Auto Publish Powered By : XYZScripts.com