Smart Error Handle Mechanism For Sitecore Rendering Components
Hello Everyone, I am back with another blog. In this blog we will discuss how Sitecore handle exceptions internally while rendering the the Sitecore components on the page. What error handling strategy it use internally and which pipeline is responsible for it. How we can pass our custom handler to existing pipeline processor.
We might have faced situations like, when exception occurs in any one component, it breaks the whole page and page is redirected to the custom error page defined.
We know, we can handle such scenario by writing proper try catch mechanism and handle the exceptions. But sometime it happens, we don't handle all type of exceptions properly which can lead to breaking of that page.
So let's start, how we can handle it for all Sitecore component in one go if exception handling is not done properly.
First we will see what is default pipeline to render the component and error strategy defined.
If we see in below image, there is a processor called Sitecore.Mvc.Pipelines.Response.RenderRendering.ExecuteRenderer.This processor takes care of executing or mount your component on the web page.If you closely see this pipeline is taking multiple parameter as input which include error rendering strategy and how it handle the exeception.We can also see there are hanlder defined which is passed as input while it handle the exception while executing the error handling stratgies.This pipeline is having HandleError method internally which hanldes you execeptions. You can refer the second image for the method implementation.
Now we will override the existing pipeline and add our custom handler to handle the execeptions if occurred. First we will override the pipeline by extending the existing pipeline.You can refer below code for the same.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Web;
using Sitecore.Diagnostics;
using Sitecore.Mvc.Pipelines.Response.RenderRendering;
using Sitecore.Mvc.Presentation;
namespace ShrikantPractice.Foundation.SitecoreExtentions.Pipeline.MvcGetRenderer
{
public class ExecuteRenderer : Sitecore.Mvc.Pipelines.Response.RenderRendering.ExecuteRenderer
{
public ExecuteRenderer()
{
}
public ExecuteRenderer(IRendererErrorStrategy errorStrategy) : base(errorStrategy)
{
}
protected override bool Render(Renderer renderer, TextWriter writer, RenderRenderingArgs args)
{
try
{
renderer.Render(writer);
}
catch (Exception ex)
{
Log.Error("Failed to render rendering", ex, this);
LatestRenderingError = args.Rendering;
if (!IsHandledByErrorStrategy(renderer, ex, writer))
{
throw;
}
}
return true;
}
public static Sitecore.Mvc.Presentation.Rendering LatestRenderingError
{
get
{
if (HttpContext.Current.Items.Contains("CurrentErroredRendering"))
{
return HttpContext.Current.Items["CurrentErroredRendering"] as Sitecore.Mvc.Presentation.Rendering;
}
return null;
}
set
{
if (HttpContext.Current.Items.Contains("CurrentErroredRendering"))
{
if (value == null)
{
HttpContext.Current.Items.Remove("CurrentErroredRendering");
}
else
{
HttpContext.Current.Items["CurrentErroredRendering"] = value;
}
}
else
{
HttpContext.Current.Items.Add("CurrentErroredRendering", value);
}
}
}
}
}
The code is pretty stratight forward. We are defining a static variable called LatestRenderingError which holds the value of rendering which has encountered exception. This will be used in our custom handler to handled the exception more swiftly.Now we will write our custom handler.
using Microsoft.Extensions.DependencyInjection;
using Sitecore.Abstractions;
using Sitecore.DependencyInjection;
using Sitecore.Mvc.Pipelines.Response.RenderRendering;
using Sitecore.Mvc.Presentation;
using Sitecore.Web;
using System;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Web.Mvc;
namespace ShrikantPractice.Foundation.SitecoreExtentions.Pipeline.MvcGetRenderer.ErrorHandling
{
public class SafeErrorHandler : IRendererErrorStrategy
{
/// <summary>
/// Sitecore Logger Implementation
/// </summary>
public virtual BaseLog Logger { get { return ServiceLocator.ServiceProvider.GetService<BaseLog>(); } }
/// <summary>
/// Are we in Normal Page Mode
/// </summary>
public virtual bool IsNormalMode { get { return Sitecore.Context.PageMode.IsNormal; } }
/// <summary>
/// Handle any exception directly. If we are in NormalMode, show nothing
/// but if we are not, let's show an error block to alert the Content Author
/// </summary>
public virtual bool HandleError(Renderer renderer, Exception ex, TextWriter writer)
{
if (this.IsNormalMode)
{
//get error handler settings from config/cache
var rendering = ShrikantPractice.Foundation.SitecoreExtentions.Pipeline.MvcGetRenderer.ExecuteRenderer
.LatestRenderingError;
if (rendering != null)
{
try
{
//default
var errorAction = "Default";
NameValueCollection parameters = null;
if (rendering?.RenderingItem?.Parameters != null)
{
parameters = WebUtil.ParseUrlParameters(rendering?.RenderingItem?.Parameters);
if (parameters != null && parameters.AllKeys.Contains("ErrorHandling"))
{
errorAction = parameters["ErrorHandling"];
}
}
switch (errorAction)
{
case "Show":
ShowRenderingError(rendering, ex, writer, parameters);
break;
case "Throw":
//fallback to the error pages!
return false;
//we hide all of the errors unless otherwise mentioned.
case "Hide":
default:
ShowHiddenRenderingError(rendering, ex, writer);
break;
}
return true;
}
finally
{
ShrikantPractice.Foundation.SitecoreExtentions.Pipeline.MvcGetRenderer.ExecuteRenderer
.LatestRenderingError = null;
}
}
}
return false;
}
private void ShowHiddenRenderingError(Sitecore.Mvc.Presentation.Rendering rendering, Exception ex, TextWriter writer)
{
var transactionId = LogAdditionalDetail(rendering, ex);
// write a transaction ID into the log so that we can find the detailed error message
writer.WriteLine($"<!-- Exception rendering view Rendering ID: {rendering.Id}|TransactionId: {transactionId} -->");
}
/// <summary>
/// Display an error message to the user so they know what the issue is.
/// </summary>
/// <param name="rendering"></param>
/// <param name="parameters"></param>
/// <param name="writer"></param>
private void ShowRenderingError(Sitecore.Mvc.Presentation.Rendering rendering, Exception ex, TextWriter writer, NameValueCollection parameters)
{
/*Some suggested CSS styling for showing an error.
.component-exception {
width: 100%;
padding: 10px;
color: red;
font-weight: 700;
text-align: center;
}
*/
var transactionId = LogAdditionalDetail(rendering, ex);
var message = "Sorry! We are encountering an issue displaying this component.";
if (parameters != null && parameters.AllKeys.Contains("ErrorHandlingMessage"))
{
message = parameters["ErrorHandlingMessage"];
}
writer.WriteLine("<div class='component-exception'>");
writer.WriteLine(message);
writer.WriteLine($"<!-- Exception rendering view Rendering ID: {rendering.Id}|TransactionId: {transactionId} -->");
writer.WriteLine("</div>");
}
private string LogAdditionalDetail(Sitecore.Mvc.Presentation.Rendering rendering, Exception ex)
{
string transactionId = Guid.NewGuid().ToString();
var detail = $"TransactionId: {transactionId}| Rendering: {rendering.Id}| Item: {rendering?.Item?.ID}| Datasource: {rendering.DataSource}| Exception: {ex.Message}| Exception Type: {ex.GetType().ToString()}";
Logger.Error($"Rendering Error - Additional Detail | {detail}", ex, this);
return transactionId;
}
}
}
If you go through the above code, it simply grab the rendering which has encountered the exception from the static variable defined in the processor and it performs certain action like whether to show the component, or to show as specific message defined at parameter at rendering level.It also logs the exceptions. Refer below image which describe what all parameters can be passed to our handler.
Below are the param which can be passed.
- ErrorHandling=Show
- ErrorHandling=Hide
- ErrorHandling=Throw
- ErrorHandlingMessage=Error Encountered.
Now we will patch our pipeline and handler and Yes we are done with it. You can break your component and you check the same. I have tested it for two three components and it is working fine
<sitecore>
<pipelines>
<mvc.renderRendering>
<processor type="Sitecore.Mvc.Pipelines.Response.RenderRendering.ExecuteRenderer, Sitecore.Mvc">
<patch:attribute name="type">ShrikantPractice.Foundation.SitecoreExtentions.Pipeline.MvcGetRenderer.ExecuteRenderer, ShrikantPractice.Foundation.SitecoreExtentions</patch:attribute>
<param type="Sitecore.Mvc.Pipelines.Response.RenderRendering.HttpExceptionWrappingRendererErrorStrategy, Sitecore.Mvc" desc="rendererErrorHandler">
<param type="Sitecore.Mvc.Pipelines.Response.RenderRendering.ChainedRendererErrorStrategy, Sitecore.Mvc" desc="rendererErrorHandler">
<Handlers hint="list">
<handler patch:before="*[@type='Sitecore.XA.Foundation.Presentation.Pipelines.RenderRendering.SxaPageModeRenderingErrorStrategy, Sitecore.XA.Foundation.Presentation']"
type="ShrikantPractice.Foundation.SitecoreExtentions.Pipeline.MvcGetRenderer.ErrorHandling.SafeErrorHandler, ShrikantPractice.Foundation.SitecoreExtentions" />
</Handlers>
</param>
</param>
</processor>
</mvc.renderRendering>
</pipelines>
</sitecore>
Hope this will help someone and thanks for reading. Happy learning. Happy Sitecoring.I will be back with some other interesting stuff in next blog
Comments
Post a Comment