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

Index Searching - Find-Item using custom fields with Sitecore Powershell


UPDATE: Michael West likes this solution and confirmed that it will be part of the next release of SPE (6.0), becoming my first contribution to SPE! Here is the ticket where this implementation is being tracked.


If you are a big fan of Sitecore Powershell Extensions like me, you probably know the Find-Item command, which allows searching your search index in Powershell scripts. This command, however, has an important limitation: you cannot use custom fields to filter and sort from the index. Due to this limitation, extended usage of Find-Item with custom code is usually not possible.

Internally Find-Item uses the base class “Sitecore.ContentSearch.SearchTypes.SearchResultItem“, which does not include any custom fields. What we usually do with C# is to create a class that inherits from “SearchResultItem“, adding our custom fields, such as below:

public class CustomSearchResultItem : SearchResultItem
{
   [IndexField("_templates")]
   [DataMember]
   public virtual List<ID> TemplateIds { get; set; }
}

The Find-Item command, however, does not allow using this field. For instance, the following script:

$props = @{
 Index = "sitecore_master_index"
 Where = "Paths.Contains(@0) And TemplateIds.Contains(@1)"
 WhereValues = [ID]::Parse("{D5B8857B-DE30-4616-84F5-812A7129ACD5}"), [ID]::Parse("{50FFE147-4F48-428B-A417-4D96DD2048FF}")
}
Find-Item @props

Will give the following error:

Find-Item : No property or field 'TemplateIds' exists in type 'SearchResultItem'

Because, yeah… that property is only defined at our custom class, not at SearchResultItem. To circumvent this limitation I built this special command.

Find-CustomItem

This command extends the original Find-Item, allowing you to pass a type that will be used instead of the base SearchResultItem class:

$props = @{
 Index = "sitecore_master_index"
 Type = [PSWebsite.Foundation.Indexing.SearchTypes.BaseSearchResultItem]
 Where = "Paths.Contains(@0) And TemplateIds.Contains(@1)"
 WhereValues = [ID]::Parse("{D5B8857B-DE30-4616-84F5-812A7129ACD5}"), [ID]::Parse("{50FFE147-4F48-428B-A417-4D96DD2048FF}")
}
Find-CustomItem @props

Create the custom command

In order to use Find-CustomItem in your project, you should:

  1. Add NuGet references to Sitecore.ContextSearch and Sitecore.ContextSearch.Linq;
  2. Add a reference to Cognifide.Powershell.dll;
  3. Create a custom FindItemCommand class extending Cognifide.PowerShell.Commandlets.Data.Search.FindItemCommand;
    You can download the whole class here
  4. Register your new command with an include patch – make sure the content is according to the following
<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
   <sitecore>
      <powershell>
         <commandlets>
            <add Name="Find Item" type="PSWebsite.Foundation.Indexing.Powershell.Commandlets.FindItemCommand, PSWebsite.Foundation.Indexing" />
         </commandlets>
      </powershell>
   </sitecore>
</configuration>

Limitations

Not all parameters from Find-Item are supported by Find-CustomItem. If you use any of the unsupported parameters along with any custom properties, it will cast the query to the base SearchResultItem, and give the same error as the original Find-Item command.

Supported parameters:

  • Index
  • Where
  • WhereValues
  • OrderBy
  • First
  • Last
  • Skip

Unsupported parameters:

  • Criteria
  • Predicate
  • ScopeQuery

 

Posted in Powershell, SPE

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.
Posted in 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.

Posted in XConnect Tagged with:

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:\src\ABC
      instead of
      C:\Source Control\My Client Name Is Long\My 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:\src\ABC”;
$outputToCsv = $false;
$outputToPrompt = $true;
$maxLength = 225;
#########

cls;

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

# 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 = “$PSScriptRoot\beyond $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
Posted in 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!

Posted in 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!

Posted in 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.

Posted in 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 Files\iis\Microsoft Web Deploy V3\msdeploy.exe>
msdeploy.exe : Error Code: ERROR_SCRIPTDOM_NEEDED_FOR_SQL_PROVIDER
At <C:\Program Files\WindowsPowerShell\Modules\SitecoreInstallFramework\1.1.0\Public\Tasks\Invoke-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 SDKs\Windows\v10.0A\bin\NETFX 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_2\Microsoft.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!

Posted in 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!

Posted in 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!

Posted in Helix, SUG
Social Media Auto Publish Powered By : XYZScripts.com