Skip to content
April 13, 2021 / kiranpatils

SXA Rendering Global Exception Handler

Challenge

We’ve been happily using SXA OOTB Components [and still doing the same :)]. But suddenly one day we started getting errors on the Site. Where the page would give an error. After troubleshooting further we figured out that. The data source item was deleted and the component was still looking for that data source item.

If it had been a custom component or MVC Component. We would have handled that exception. But unfortunately, that’s not the case with OOTB SXA Components.

One way to resolve this is. Configure Custom Error page: https://doc.sitecore.com/developers/sxa/93/sitecore-experience-accelerator/en/generate-a-custom-static-error-page.html. So, it shows our custom error page.

But we didn’t want to break a full page because of an error in one of the components. Instead, we wanted to keep the full page working without an error-ed component.

If it had been an MVC solution there are plenty of blogs to handle this using the Global MVC Rendering exception handling approach. But unfortunately, we couldn’t found any blog for SXA to do the same.

That’s why this blog post is coming to an existence on this earth!

Solution

We stumbled upon following posts to do the same in MVC (Thank you so much for this post):

And tried to fit it in SXA architecture.

Quick show config revealed that SXA has “SxaPageModeRenderingErrorStrategy” to handle such error in Experience editor mode. We noticed that the errored component was throwing and showing errors in Experience editor mode.

That was our Eureka Moment! We fired up the reflector and figured out the next steps.

Putting it together for you. So, you can go home (Or as per New Normal, go to the living room from the working room) on time to spend time with your loved ones!

Here you go!

using Microsoft.Extensions.DependencyInjection;
using Sitecore;
using Sitecore.Abstractions;
using Sitecore.DependencyInjection;
using Sitecore.Diagnostics;
using Sitecore.Mvc.Pipelines.Response.RenderRendering;
using Sitecore.Mvc.Presentation;
using Sitecore.XA.Foundation.Abstractions.Configuration;
using Sitecore.XA.Foundation.Multisite.Extensions;
using Sitecore.XA.Foundation.Presentation;
using System;
using System.IO;
using System.Web.Mvc;
namespace SCBasics.Foundation.SitecoreExtensions.Pipelines.RenderRendering
{
public class CustomPublishModeRenderingErrorStrategy : PageModeRenderingErrorStrategy, IRendererErrorStrategy
{
private readonly BaseLog _log;
public CustomPublishModeRenderingErrorStrategy(BaseLog log)
{
_log = log;
}
protected override string GetExceptionDetails(string prefix, string message, string stackTrace)
{
string pageModeRenderingErrorClass = ServiceProviderServiceExtensions.GetService<IConfiguration<PresentationConfiguration>>(ServiceLocator.ServiceProvider).GetConfiguration().PageModeRenderingErrorClass;
return this.GetPreCode(stackTrace);
}
public bool HandleError(Renderer renderer, Exception ex, TextWriter writer)
{
bool isCustomErrorHandlerEnabled = Sitecore.Configuration.Settings.GetBoolSetting("SCBasics.IsCustomErrorEnabled", true);
if (!isCustomErrorHandlerEnabled)
return false;
/*
* Sitecore.XA.Foundation.Presentation.Pipelines.RenderRendering.SxaPageModeRenderingErrorStrategy
* NON SXA References:
* https://www.teamdevelopmentforsitecore.com/Blog/robust-mvc-rendering-exception-handler
* https://www.sitecorenutsbolts.net/2015/10/23/Rendering-Exception-Handling-The-Right-Way/
*/
if (this.IsPageModeNormal())
{
return PublishModeHandleError(renderer, ex, writer);
}
if (!Context.Site.IsSxaSite())
{
return false;
}
return base.HandleError(renderer, ex, writer);
}
private bool PublishModeHandleError(Renderer renderer, Exception ex, TextWriter writer)
{
Assert.IsNotNull(renderer, "renderer");
Assert.IsNotNull(ex, "ex");
Assert.IsNotNull(writer, "writer");
if (!this.IsPageModeNormal())
{
return false;
}
string prefix = string.Format("Error Rendering {0}: ", renderer);
string str3 = this.GetExceptionDetails(prefix, ex.Message, ex.StackTrace);
// Log Error
_log.Error(string.Format("Failed to render component '{0}'", prefix), ex, this);
// Log main error in comment
writer.WriteLine("<!– Exception rendering component '{0}': {1} –>",
prefix + ":" + ex.Message,
str3);
// Log Inner exception in comment
Exception innerException = ex.InnerException;
string str2 = "Inner Exception: ";
while (innerException != null)
{
string str4 = this.GetExceptionDetails(str2, innerException.Message, innerException.StackTrace);
writer.WriteLine("<!– Inner Exception: {0} –>", innerException.Message);
innerException = innerException.InnerException;
}
return true;
}
}
}
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"&gt;
<sitecore>
<settings>
<!–Default true–>
<setting name="SCBasics.IsCustomErrorEnabled" value="true"/>
<!– USE SERVER-SIDE REDIRECT FOR REQUEST ERRORS
If true, Sitecore will use Server.Transfer instead of Response.Redirect to redirect request to service pages
when an error occurs (item not found, access denied etc).
Default value: false
–>
<setting name="RequestErrors.UseServerSideRedirect" value="true"/>
</settings>
<pipelines>
<mvc.renderRendering>
<processor type="Sitecore.Mvc.Pipelines.Response.RenderRendering.ExecuteRenderer, Sitecore.Mvc">
<param desc="rendererErrorHandler" type="Sitecore.Mvc.Pipelines.Response.RenderRendering.HttpExceptionWrappingRendererErrorStrategy, Sitecore.Mvc">
<param desc="rendererErrorHandler" type="Sitecore.Mvc.Pipelines.Response.RenderRendering.ChainedRendererErrorStrategy, Sitecore.Mvc">
<Handlers hint="list">
<handler patch:after="*[@type='Sitecore.XA.Foundation.Presentation.Pipelines.RenderRendering.SxaPageModeRenderingErrorStrategy,
Sitecore.XA.Foundation.Presentation']"
type="SCBasics.Foundation.SitecoreExtensions.Pipelines.RenderRendering.CustomPublishModeRenderingErrorStrategy,
SCBasics.Foundation.SitecoreExtensions" resolve="true"/>
</Handlers>
</param>
</param>
</processor>
</mvc.renderRendering>
</pipelines>
</sitecore>
</configuration>

Quick gist of above gist :

We are handling our custom pipeline after SxaPageModeRenderingErrorStrategy which is logging exception as HTML comment on the page when the site is in normal mode and show error on page when the site is in experience editor mode (That part is done by OOTB handler). You can change it as per your need.

In case you want to disable this functionality in any environment. you can set SCBasics.IsCustomErrorEnabled to false

That’s it – Happy Exception Handling! 🙂

January 22, 2021 / kiranpatils

Sitecore Learning Mind map

The Sitecore learning mind map is a list of topics organized by high-level category. Which should help newbie/experienced Sitecore enthusiasts to explore sources by category.

Read more : https://klpatil.github.io/sc-learning-mind-map/

January 13, 2021 / kiranpatils

PowerShell script to delete wrong Solr Config using Search Stax API

Challenge:

I’m a big fan of SearchStax as it removes all challenges to install, configure and manage Solr and let experts do experts job!

To set up Solr Collections: I always prefer to use Search Stax Plugin: https://github.com/searchstax/searchstax-sitecore-plugin

I made a mistake once where I had a typo in my config.yml and It uploaded config with the wrong name. (Garbage IN, Garbage OUT) Which I wanted to delete. But there was no way to do this via the plugin. So, I had to write a power shell using Search Stax API to do it and thought to share it with you!

Solution:

Disclaimer: I’m not yet proficient in Powershell. So, feel free to modify this script if you are Powershell NINJA!

You need to provide few values in the script and you should be good!

# "Please provide authentication information."
$uname = Read-Host Prompt 'Username – '
$password = Read-Host AsSecureString Prompt 'Password – '
$password = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
Write-Host "Asking for an authorization token for $uname"
Write-Host
$body = @{
username=$uname
password=$password
}
Remove-Variable PASSWORD
$body = $body | ConvertTo-Json
try {
$token = Invoke-RestMethod uri "https://app.searchstax.com/api/rest/v2/obtain-auth-token/" Method Post Body $body ContentType 'application/json'
$token = $token.token
Remove-Variable body
Write-Host "Obtained token" $token
Write-Host
$searchstaxUrl = 'https://app.searchstax.com'
$ACCOUNT = 'TODO' # <- CHANGE YOUR VALUE HERE
$uid = 'TODO' # <- CHANGE YOUR VALUE HERE
$NAME = 'sitecore_sitecore' # <- CHANGE YOUR VALUE HERE
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Authorization", "Token $token")
#Delete Config
$RESULTS = Invoke-RestMethod Method Delete Headers $headers `
uri "https://app.searchstax.com/api/rest/v2/account/$ACCOUNT/deployment/$uid/zookeeper-config/$NAME/"
$RESULTS = $RESULTS | ConvertTo-Json
#return $token
} catch {
Write-Error Message "Unable to get Auth Token. Error was: $_" ErrorAction Stop
}

You can find API Documentation from here : https://www.searchstax.com/docs/staxapi2ZK/#zkdelete

January 13, 2021 / kiranpatils

SXA Lower case URL

Challenge:

As per SEO Best practice, We were trying to set all URLs to lower case for our SXA Solution.

Based on my NON-SXA Learning. I thought it will be changed in one place. But it had changed at one more place. Which I couldn’t locate easily from the documentation. So, thought to share with you to save your time!

Solution:

We made following change:

But that didn’t give us the desired results. A quick search helped us to find this post which outlines a few approaches: https://sitecore.stackexchange.com/questions/16504/sxa-search-results-render-link-url-lowercase (Thank you!)

We tried following config file and it worked!

<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<linkManager>
<providers>
<add name="localizedProvider">
<patch:attribute name="lowercaseUrls">true</patch:attribute>
</add>
</providers>
</linkManager>
<links>
<urlBuilder>
<lowercaseUrls>true</lowercaseUrls>
</urlBuilder>
</links>
</sitecore>
</configuration>
view raw LowerCase.Config hosted with ❤ by GitHub
<rules>
<!– Take redirectType="Temporary" out of production if needed – Current live site has same configuration –>
<rule name="LowerCaseRule – not including querystring" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{PATH_INFO}" pattern=".*[A-Z]" ignoreCase="false" />
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_URI}" pattern="^/(api)" negate="true" />
<add input="{REQUEST_URI}" pattern="^/(sitecore)" negate="true" />
<add input="{REQUEST_URI}" pattern="^/(sitecore modules)" negate="true" />
<add input="{REQUEST_URI}" pattern="^/-/" negate="true" />
<add input="{REQUEST_URI}" pattern=".*(-/media).*" negate="true" />
<add input="{REQUEST_URI}" pattern=".*(~/media).*" negate="true" />
<add input="{REQUEST_URI}" pattern=".*(~/icon).*" negate="true" />
<add input="{REQUEST_URI}" pattern="WebResource.axd" negate="true" />
<add input="{REQUEST_URI}" pattern="ScriptResource.axd" negate="true" />
<add input="{REQUEST_URI}" pattern="jsnlog.logger" negate="true" />
<add input="{REQUEST_URI}" pattern="Telerik.Web.UI.WebResource.axd" negate="true" />
<add input="{REQUEST_METHOD}" matchType="Pattern" pattern="POST" ignoreCase="true" negate="true" />
<add input="{REQUEST_URI}" pattern="^/(identity)" negate="true" />
</conditions>
<action type="Redirect" url="/{ToLower:{R:1}}" appendQueryString="true" redirectType="Temporary" />
</rule>
</rules>

One more thing — If you are also looking for a way to redirect any URL to lower case then this is a great post: https://blog.coates.dk/2018/01/15/lower-casing-rewrite-rules-breaks-the-sitecore-client/ – I had to modify it for Identity service – Added our version (Tested on Sitecore 9.3) in the gist above.

Happy SEO!

January 13, 2021 / kiranpatils

Workflow bundling bug

Challenge:

Since Sitecore 8.2 there is a hidden gem which allows you to do workflow bundling.

Workflow bundling means – When you move a page from one state to another using Experience editor only. All the associated component’s data sources on that page also move along with the page.

I’m surprised that this is not very well documented feature :

Following blog post has done good job outlining this (Thanks jammykam)

https://jammykam.wordpress.com/2016/09/05/sitecore-8-2-managing-datasource-components-and-workflow-from-the-experience-editor/ – Refer to “Workflow Bundling” section

If you don’t want to read the above post then for you – This flag controls workflow bundling behavior (Default value is true): “WebEdit.AffectWorkflowForDatasourceItem”

We have been happily using this functionality. But it stopped working and we couldn’t easily figure out when that happened as we were in between of new implementation.

If same is going on with you guys/girls as well. Then this post is for you!

Solution:

We started tracking all changes we made around this feature and we figured out that we had to disable comments as per our client’s request:

And that change was causing this behavior – Where, If we enable comment then it will work, and as soon as we disable comment it stopped working.

We raised the Sitecore support ticket and after some back and forth. Finally, Sitecore support was able to reproduce the issue and registered this as a bug (#452470). Unfortunately, there is no hotfix to fix this.

You must have to keep Workflow comments enabled (Which is as such a good practice) to use workflow bundling.

Hope this helps you figure out why your workflow bundling stopped working! 🙂

One more thing, There is also another bug with this feature where it doesn’t approve children items of a data source – https://trnktms.com/2018/03/20/webedit-affectworkflowfordatasourceitems-collect-datasource-children-items/ (“by default, this only collects the data source items and does not the children items of a data source. This is an issue when you have something like a list component which shows the children items”)

Happy Workflow bundling! 🙂

%d bloggers like this: