Sitecore 10.4.0 - Sidekick 1.7 Compatibility Issue

Recently, I saw a thread in Sitecore slack channel wherein someone had raised an issue with regard to compatibility of Sitecore Sidekick with Sitecore 10.4.0 instance. So, as ever, i decided to take the issue in my own hands and see if I could deal with and find a fix! This blog note is as a result of that weekend adventure. Although I started this work as a troubleshooting session, I later realised there is so much to cover in terms of existing Sidekick technical design and interoperability with Sitecore. As a result, I spent quite a good time revisiting the related code as well as this post to make it interesting. This blog post also hinges on one of the differences of v10.4.0 compared with earlier versions - the EnsureLoggedInforPreview processor has been moved from mvc.requestBegin pipeline to mvc.actionExecuting pipeline.

First things first, I followed this blog post by Madhu to setup a local 10.4.0 instance and published the Sidekick configs and dlls to the 10.4.0 instance, as stated. Now, once I clicked the menu in the Sitecore desktop, bingo, the error was quite evident. Simultaneously, I setup a 10.3.0 instance side-by-side and did the same process of publishing the sidekick configs and dlls to confirm that the issue was very much specific to 10.4.0 instance. 

YSOD in Sitecore 10.4.0 instance:


10.3.1 Sitecore.MVC decompiled DLL:

10.4.0  Sitecore.MVC decompiled DLL:

RequestBegin pipeline processor is obsolete and new change is part of ActionExecuting process and the class is internal:


Juxtaposing the latest showconfig against a 10.3 version:


Actual Error:

Based on the error, I decided to patch the config to debug what causes the error and it looks like the context for PageMode.IsNormal isn't available in the new pipeline processor:


The issue seems to be that the Sidekick.core.config initialize pipeline processor is missing PageMode  context needed for Sitecore 10.4.0 when patched at that level. So, a better approach would be to patch it after the PageMode context is available but that didn't workout as easy as i thought since the sidekick dialog is built differently compared to other Sitecore dialogs. 

Here is an overview of the integration process:
Since Sidekick uses Angular concepts, it compiles all required resources and sets it up as a route that can be invoked by Sitecore command. 

1. Registration of Sidekick as  a service:
    - Initialize Sidekick from Sitecore side
    - Initialization kicks-in Sidekick service registration process that involves putting together the necessary artifacts/resources for the Sidekick UI
    - An scs route is registered so that it is accessible at runtime from the Sitecore desktop menu

2. Call the sidekick route from Sitecore desktop
    - Since the scs route is setup, the desktop menu click can invoke the controller action
    - Whenever scs controller action is invoked, mvc.actionExecuting gets fired

With respect to Sitecore 10.4.0, since there is an abrupt jump from initialize pipeline to mvc.actionExecuting process when an scs route action is invoked, as part of new 10.4.0 release, code changes are made to EnsureLoggedInforPreview process and as a result, the Sitecore pagemode context is not available. This also highlights an issue with the EnableLoggedInforPreview processor, it assumes that incoming request is from a page but the request could be for any resource, like in case of how Sidekick is designed. I added some logging to find this:


Workarounds:

With the above context, although the following approaches do the trick of invoking the scs route but they involve changes to Sitecore side with respect to Sidekick hence, they aren't perfect and are hacks since a better change would be to involve just the Sidekick side instead. Nevertheless, the advantage of Sitecore is its configuration capabilities and I've tapped into it here. If I've to setup Sidekick by any chance for 10.4.0, i'll definitely lean on these approaches but I don't see the need given the fact there are so many options available for Sitecore 10.x like Sitecore Content Serialization or razl. On the other hand, if a UI content migration/comparison tool like sidekick is available free-of-cost and helps me compare environments on-the-fly, I would want to have it in my back pocket and set it up locally, when in need.  I've therefore added these notes here to show the trail of my thoughts since the idea as a developer is to keep the mind and the Sitecore platform open for all types of integrations.


Approach-1:

Based on the error, via the patch:instead, I removed just the IsNormal check and that seemed to do the trick:

Patch:
+++++++
+++++++

cs code:
^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^




Or, the other hack is to completely patch out the mvc.actionExecuting process and retain the old mvc.requestBegin process. In case of the latter process, the IsNormal call doesn't error out.

Approach-2:

Related code for above approach -

Patch-1:

%%%%%%
%%%%%%

cs code:
////////
////////

Patch-2:

########
########

cs code:

////////
///////

Update:

Given the situation on hand and the fact that there has been some shuffling of code that has happened in 10.4.0 pipeline processors, a better approach seemed to me is adding a filtering condition in EnsureLoggedInPreview patched process because it is unnecessary to execute the process logic when it is known that the sidekick dialog is accessed prematurely while pagemode context is unavailable. This way, we are not only retaining the existing Sitecore 10.4.0 functionality but also ensuring it is robust against rogue sidekick errors.


Approach-3:

The filtering logic is for bypassing resource files that get served from scs route.

Related code for above approach -

cs code:
+++++++
+++++++

Patch:
//////
//////

Github - deploy config under app_config/include and dll to bin folder

If running the Github project locally, ensure that System.Web.Mvc dll points to compatible version for Sitecore instance. In other words, reference the dll from web root folder. For that matter, in case of future versions of Sitecore, ensure correct nuget package is referenced since this project is compatible with 10.4.0.

Conclusion:

Understandably, out of the above 3 approaches, approach-3 is more appropriate since it not only handles the sidekick compatibility for Sitecore 10.4.0 but keeps EnsureLoggedInPreview process pristine for other scenarios. To me, it is the simplest solution compared with refactoring sidekick to accommodate v10.4.0 by replacing the route dependency, across the code, with a file system publish of resources to the application web root and load the assets from there. To understand the refactoring effort involved, I have added below the sidekick/sitecore log trail and EnsureLoggedInPreview process logging with "scs" entry means as many calls/changes.

YSOD’ Detailed error in the log:

 ********

1733568 12:34:31 ERROR Application error.
Exception: System.NullReferenceException
Message: Object reference not set to an instance of an object.
Source: Sitecore.Mvc
at Sitecore.Mvc.Pipelines.Request.ActionExecuting.EnsureLoggedInForPreview.Process(ActionExecutingArgs args)
at (Object , Object )
at Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args)
at Sitecore.Pipelines.DefaultCorePipelineManager.Run(String pipelineName, PipelineArgs args, String pipelineDomain, Boolean failIfNotExists)
at Sitecore.Pipelines.DefaultCorePipelineManager.Run(String pipelineName, PipelineArgs args, String pipelineDomain)
at Sitecore.Mvc.Pipelines.PipelineService.RunPipeline[TArgs](String pipelineName, TArgs args)
at Sitecore.Mvc.Filters.PipelineBasedRequestFilter.OnActionExecuting(ActionExecutingContext actionExecutingContext)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.InvokeActionMethodFilterAsynchronouslyRecursive(Int32 filterIndex)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.InvokeActionMethodFilterAsynchronouslyRecursive(Int32 filterIndex)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass7_0.<BeginInvokeActionMethodWithFilters>b__0(AsyncCallback asyncCallback, Object asyncState)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase`1.Begin(AsyncCallback callback, Object state, Int32 timeout)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.BeginInvokeActionMethodWithFilters(ControllerContext controllerContext, IList`1 filters, ActionDescriptor actionDescriptor, IDictionary`2 parameters, AsyncCallback callback, Object state)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass3_1.<BeginInvokeAction>b__0(AsyncCallback asyncCallback, Object asyncState)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase`1.Begin(AsyncCallback callback, Object state, Int32 timeout)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.BeginInvokeAction(ControllerContext controllerContext, String actionName, AsyncCallback callback, Object state)
at System.Web.Mvc.Controller.<>c.<BeginExecuteCore>b__152_0(AsyncCallback asyncCallback, Object asyncState, ExecuteCoreState innerState)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncVoid`1.CallBeginDelegate(AsyncCallback callback, Object callbackState)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase`1.Begin(AsyncCallback callback, Object state, Int32 timeout)
at System.Web.Mvc.Controller.BeginExecuteCore(AsyncCallback callback, Object state)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase`1.Begin(AsyncCallback callback, Object state, Int32 timeout)
at System.Web.Mvc.Controller.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state)
at System.Web.Mvc.MvcHandler.<>c.<BeginProcessRequest>b__20_0(AsyncCallback asyncCallback, Object asyncState, ProcessRequestState innerState)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncVoid`1.CallBeginDelegate(AsyncCallback callback, Object callbackState)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase`1.Begin(AsyncCallback callback, Object state, Int32 timeout)
at System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state)
at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStepImpl(IExecutionStep step)
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

********

Log trail for Sitecore 10.4.0 - Sidekick inter-dependency:

#########

#########

Comments