Creating 403, 404 & 500 error Pages

I needed to create 403, 404 and 500 HTTP error pages for a multi-lingual Sitecore project. I thought I’d easily find the way online, but it proved to be not that easy. These are my requirements:

  • 403: rewrite response from a Sitecore page (/403)
  • 404: rewrite response from a Sitecore page (/404)
  • 500: rewrite response from static error pages where we have 1 for each language
  • Show the exception details and ignore the 500 response rewrite when the web.config’s customError is set to Off (for developers to debug errors)
  • Test each of them before pushing code

Note: the steps in this article apply to non-Sitecore ASP.NET web application too, except the section on the 404’s HttpRequestProcessor, which could be skipped for non-sitecore sites.

Implementing HTTP Error 403

403 means the requested page is forbidden. So when I request a forbidden page I want to see a custom error with our theme instead of IIS’s default ‘forbidden’ page.

To do this, all I had to do is add the httpErrors section into the web.config as the following:

<httpErrors errorMode="Custom" existingResponse="Replace">
  <remove statusCode="403" />
  <error statusCode="403" responseMode="ExecuteURL" path="/403" />
</httpErrors>

Of course, you should have already created a new page for that, e.g. /403.

That’s it. 403 custom page is quite simple, however testing it is not that straightforward.

Testing HTTP Error 403

To test the new 403 custom error handler, you’ll end to attempt to access a page that you shouldn’t access as an IIS user. I test it by trying to access the App_Config folder: hostname/App_Config. If you have set up the above configurations correctly, you should see the same URL (/App_config) but the server would return the 403 page content.

Implementing HTTP Error 404

404 means the requested page does not exist. This is probably the most common error you’ll see while surfing the net. When you try to access a page in a site with a wrong URL or access a page using a very old URL and the page has been removed, the server would throw an HTTP Error 404, indicating that this page does not exist. It is more friendly to display a custom error page with the site’s theme instead of the default IIS page.

This includes two parts: 1) Sitecore HttpRequestProcessor and 2) Web.config’s HTTPErrors section

Any new request in a Sitecore site will be passed to the HttpRequestProcessor. So we’ll have to check if the requested item is available or not in there:

<sitecore>
 <pipelines>
  <httpRequestBegin>
   <processor type="Enter your class type"
    patch:after="processor[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']" />
  </httpRequestBegin>
 <pipelines>
</sitecore>

public class PageNotFoundResolver : HttpRequestProcessor
{
public override void Process(HttpRequestArgs args)
{
if (Sitecore.Context.Database == null || Sitecore.Context.Database.Name == "core")
return;

if (Sitecore.Context.Item != null || Sitecore.Context.Database == null)
return;

if (args.Url.FilePath.StartsWith("/-/"))
return;

var notFoundPage = ItemReferences.Http404Page;
if (notFoundPage == null)
return;

Sitecore.Context.Item = notFoundPage;
}
}

What the above code does is check that the requested page has not been found by Sitecore and switches the Context Item to the 404 page without redirecting to the 404 page.

The second part is adding to our HTTPError section in the web.config:

<httpErrors errorMode="Custom" existingResponse="Replace">
  <remove statusCode="403" />
  <remove statusCode="404" />
  <error statusCode="403" responseMode="ExecuteURL" path="/403" />
  <error statusCode="404" responseMode="ExecuteURL" path="/404" />
</httpErrors>

Testing HTTP Error 404

To test this all you have to do is try to navigate to a page that does not exist: hostname/pageThatDoesNotExist. This should load the 404 page content.

Implementing HTTP Error 500

500 means an ‘Internal Server Error’ which occurs when an unhandled exception is found or the site is not configured correctly.

To customize this, we’ll have to add an Application_Error method into the global.asax which catches all the application’s exceptions and then return the customized error page.

public void Application_Error(Object sender, EventArgs e)
 {
 var customErrorsSection = (CustomErrorsSection)ConfigurationManager.GetSection("system.web/customErrors");

if (customErrorsSection.Mode != CustomErrorsMode.Off)
 {
 Exception exception = Server.GetLastError();
 if (exception == null)
 return;
 Server.ClearError();
 try
 {
 Server.Transfer(string.Format("/static/{0}500.html", GetLanguage()));
 }
 catch { }
 }
 }

private string GetLanguage()
 {
 try
 {
 return Sitecore.Context.Language.CultureInfo.TwoLetterISOLanguageName + "_";
 }
 catch
 {
 }
 return string.Empty;
 }

Note that I used Server.Transfer to preserve the request URL in the browser.

The above code will return the content of the static 500 error pages which should be created under ‘static’ folder, e.g. en_500.html, ar_500.html etc… for all the site’s languages plus a global one (500.html) in case the language is unretrievable.

Also note that I check of the custom error section’s mode, so that when developers need to see the actual exception, they can set custom errors to false and thus allow the exception to be viewable instead of the custom page. However, to prevent the HTTPErrors from handling the exception in this case, I had to change the value of the ‘existingResponse’ from ‘Replace’ to ‘Auto’

<httpErrors errorMode="Custom" existingResponse="Auto">
  <remove statusCode="403" />
  <remove statusCode="404" />
  <error statusCode="403" responseMode="ExecuteURL" path="/403" />
  <error statusCode="404" responseMode="ExecuteURL" path="/404" />
</httpErrors>

Testing HTTP Error 500

In order to test this, I had to edit some code so that when I navigate to a specific page with a specific query string, the code throws an exception which does not get handled anywhere. The exception would be sent to the Application_Error handler and then our custom page would be returned. However, don’t forget to remove this before pushing your code.

At this point, I was able to fulfill all our requirements perfectly.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s