Exception Handling In Sitecore multi-site application

In this post we will know how to handle exception on Sitecore MVC based application. Here in this article we will discuss about both ServerError(500) PageNotFound(400) exception handling at application level, where we have different layer like Feature(MVC) and Foundation.

Below code is based on habitat architecture(Helix Principle) but can use same concept in plain MVC application also.

There are two types of server exceptions:

MVC exception

We need to implement mvc.exception pipeline, if any error throw at MVC rendering, request will redirect to error page which is configure at site level, Please see below code:

using SC = Sitecore;

namespace Sitecore.Foundation.ExceptionHandler
{
/// <summary>
/// Class RenderingExceptionHandler.
/// </summary>
/// <seealso cref="Sitecore.Mvc.Pipelines.MvcEvents.Exception.ExceptionProcessor" />
public class RenderingExceptionHandler : ExceptionProcessor
{
/// <summary>
/// Processes the specified arguments.
/// </summary>
/// <param name="args">The arguments.</param>
public override void Process(ExceptionArgs args)
{
if(!HandleException.CanShowErrorPage())
{
return;
}
var rootItem = SC.Context.Database.GetItem(SC.Context.Site.RootPath);
var errorPageUrl = rootItem.Fields[Constant.ExceptionPageUrl];
HandleException.LogException(rootItem, args.ExceptionContext.Exception);
args.ExceptionContext.ExceptionHandled = true;
if (errorPageUrl != null)
{
Web.WebUtil.Redirect(HandleException.GenerateRedirecturl(errorPageUrl), false);
}
}
}
}

Foundation.RenderingExceptionHandling.config: Here using this patch file we are configuring mvc.exception pipeline.

<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<settings>
<setting name="EnableExceptionHandling">
<patch:attribute name="value">false</patch:attribute>
</setting>
</settings>
<pipelines>
<mvc.exception>
<processor type="Sitecore.Mvc.Pipelines.MvcEvents.Exception.ShowAspNetErrorMessage, Sitecore.Mvc">
<patch:attribute name="type">Sitecore.Foundation.ExceptionHandler.RenderingExceptionHandler, Sitecore.Foundation.ExceptionHandler</patch:attribute>
</processor>
</mvc.exception>
</pipelines>
</sitecore>
</configuration>

Application exception

Non-MVC exception we can handle using application level error handling mechanism.if any error request will redirect to error page configure at site level. We are using application_error event. Also keep in mind we need to override Global.ascx and replace into website folder.

<%@ Application Codebehind=”Global.asax.cs” Inherits=”Sitecore.Foundation.ExceptionHandler.Application” Language=”C#” %>

using System;
using System.Web;
using SC = Sitecore;

namespace Sitecore.Foundation.ExceptionHandler
{
public class Application : SC.Web.Application
{
/// <summary>
/// Handling non-mvc rendering exceptions
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void Application_Error(object sender, EventArgs e)
{
Exception exception = Server.GetLastError();

if (!HandleException.CanShowErrorPage() || exception == null)
{
return;
}

HttpException httpException = exception as HttpException;
int? statusCode = httpException?.GetHttpCode();
bool IsPageNotFound = statusCode.HasValue && statusCode.Value.Equals(Constant.PageNotFound);

var rootItem = SC.Context.Database.GetItem(SC.Context.Site.RootPath);
var errorPageUrl = IsPageNotFound ? rootItem.Fields[Constant.PageNotFoundUrl] : rootItem.Fields[Constant.ExceptionPageUrl];

if (statusCode.HasValue && !statusCode.Value.Equals(Constant.PageNotFound))
{
HandleException.LogException(rootItem, exception);
}
Response.Clear();
Server.ClearError();

if (errorPageUrl != null)
{
Web.WebUtil.Redirect(HandleException.GenerateRedirecturl(errorPageUrl), false);
}
}
}
}

Page Not Found(404)

If user trying to access some page which is not exist, this type request we are handling through httpRequestBegin pipeline, and redirecting to 404 page which is configure at site level.

Please see below code.

using Sitecore.Pipelines.HttpRequest;
using System.IO;
using System.Net;
using System.Web;

namespace Sitecore.Foundation.ExceptionHandler
{
/// <summary>
/// Handle 404/PageNotFound error
/// </summary>
public class Handle404PageNotFound : HttpRequestProcessor
{
public override void Process(HttpRequestArgs args)
{
if (!HandleException.CanShowErrorPage())
{
return;
}
//Skip Api request
if (HttpContext.Current.Request.Url.Segments.Length > 1 && HttpContext.Current.Request.Url.Segments[1].Equals(Constant.ApiSegment))
{
return;
}
if (Context.Item != null || Context.Site == null)
{
return;
}
//Item is valid or file is exist
if (IsValidItem(args) || File.Exists(HttpContext.Current.Server.MapPath(args.Url.FilePath)))
{
return;
}

if (Context.Database != null)
{
var rootItem = Context.Database.GetItem(Context.Site.RootPath);
var pageNotFoundUrl = rootItem.Fields[Constant.PageNotFoundUrl];
//404 item is configured for site
if (pageNotFoundUrl != null)
{
// return 404 HTTP status code in either cases, to be picked up further ahead
HttpContext.Current.Response.TrySkipIisCustomErrors = true;
HttpContext.Current.Response.StatusCode = (int)HttpStatusCode.NotFound;
Web.WebUtil.Redirect(HandleException.GenerateRedirecturl(pageNotFoundUrl), false);
}
}
}

private bool IsValidItem(HttpRequestArgs args)
{
if (Context.Item == null || Context.Item.Versions.Count == 0 || Context.Item.Visualization.Layout == null)
{
return false;
}
return true;
}
}
}

Foundation.404PageNotFound.config: This is patch file for custom implementation of httpRequestBegin pipeline.

<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<pipelines>
<httpRequestBegin>
<processor patch:before="processor[@type='Sitecore.Pipelines.HttpRequest.LayoutResolver, Sitecore.Kernel']"
type="Sitecore.Foundation.ExceptionHandler.Handle404PageNotFound, Sitecore.Foundation.ExceptionHandler"
patch:after="processor[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']" />
</httpRequestBegin>
</pipelines>
</sitecore>
</configuration>

Common helper class — HandleException which has many common methods which we are using on many places:

using System;
using System.Web;
using System.Web.Configuration;
using SC = Sitecore;

namespace Sitecore.Foundation.ExceptionHandler
{
public class HandleException
{
internal static void ExceptionRediect(Exception exception)
{
HttpException httpException = exception as HttpException;
if (!CanShowErrorPage() || exception == null)
{
return;
}
int? statusCode = httpException?.GetHttpCode();
bool IsPageNotFound = statusCode.HasValue && statusCode.Value.Equals(Constant.PageNotFound);

var rootItem = SC.Context.Database.GetItem(SC.Context.Site.RootPath);
var errorPageUrl = IsPageNotFound ? rootItem.Fields[Constant.PageNotFoundUrl] : rootItem.Fields[Constant.ExceptionPageUrl];

LogException(rootItem,exception);

if (errorPageUrl != null)
{
Web.WebUtil.Redirect(GenerateRedirecturl(errorPageUrl), false);
}
}
/// <summary>
/// Generate URL
/// </summary>
/// <param name="link"></param>
/// <returns></returns>
internal static string GenerateRedirecturl(InternalLinkField link)
{
string redirectUrl = null;
if (link != null)
{
if (link.TargetItem != null)
{
var options = new UrlOptions
{
AlwaysIncludeServerUrl = true,
LanguageEmbedding = LanguageEmbedding.Always,
LowercaseUrls = true
};
redirectUrl = LinkManager.GetItemUrl(link.TargetItem, options);
redirectUrl = StringUtil.EnsurePostfix('/', redirectUrl).ToLower();
}
}
return redirectUrl;
}
/// <summary>
/// Exception has Page Url and userId
/// </summary>
/// <param name="rootItem"></param>
/// <param name="exception"></param>
internal static void LogException(Data.Items.Item rootItem,Exception exception)
{
var exceptionSubject = $"Exception on Website:{rootItem.Name}, Page:{HttpContext.Current.Request.Url.AbsoluteUri}";
var userId = HttpContext.Current.Session == null ? null : HttpContext.Current.Session["UserID"];
if (userId!= null)
{
exceptionSubject = exceptionSubject + $", UserId:{userId.ToString()}";
}
Diagnostics.Log.Error(exceptionSubject, exception, "Error");
}
/// <summary>
/// CanShowErrorPage
/// </summary>
/// <param name="exception"></param>
/// <returns></returns>
internal static bool CanShowErrorPage()
{
CustomErrorsMode mode = SC.Configuration.Settings.CustomErrorsMode;
//Local Environment/Sitecore CMS URL then do not handle any exceptions.
if (mode == CustomErrorsMode.Off || (mode == CustomErrorsMode.RemoteOnly && HttpContext.Current.Request.IsLocal)
|| IsCMSRequest()
)
{
return false;
}
return true;
}

/// <summary>
/// Is CMS related request
/// </summary>
/// <returns></returns>
private static bool IsCMSRequest()
{
return (HttpContext.Current.Request.Url.Segments.Length>1 && HttpContext.Current.Request.Url.Segments[1].Contains(Constant.Sitecore)) // Starting request with "sitecore/"
|| Context.PageMode.IsExperienceEditor || Context.PageMode.IsDebugging || Context.PageMode.IsPreview || Context.User.IsAdministrator;
}
}

In multi site scenario where we have common source code but have different site, we need to show site specific error pages.

Last thing,now we need to setup error page at site level, what we will do add page error template section at interface template like feature template which will be inherit by all sites root template.

Then we will be added two error pages ex- 404 and 500 at each sites, so error page will be site specific.

Thanks for reading. Happy coding.

--

--

--

Technical Architect Sitecore,.NET full stack developer

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Java Core Concepts (JDK, JRE, JVM)

How to optimize MirrorMaker2 for High Performance Apache Kafka Replication

What is the true cost of a piece of software?

Total Cost of Ownership

Delivering a strategic data center at international scale

Weekly Report #1

5 Best Programming Practices for PHP Programmers

Php Programmer coding php application

How to build your own tradebot on hybrix

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Abhishek Malaviya

Abhishek Malaviya

Technical Architect Sitecore,.NET full stack developer

More from Medium

TEST Title of the story TEST

Factory Method Design Pattern

Design Pattern- Builder Pattern

Functional Programming in Java