Custom error pages with HTTP status codes in Umbraco

I’ve spent a bit of time the last couple of weeks exploring the caveats of using custom error pages in Umbraco.  It’s pretty easy to set up the basics, i.e. a 404 page which points to an Umbraco node, but sorting out the HTTP status codes can be tricky.  So, I decided to thoroughly document my preferred method for nice 404 & 500 errors.

404 (Page Not Found)

First things first, create your 404 page.  I like to define the 404 page as a node in Umbraco – the benefit is you don’t need a separate masterpage, and of course it will be editable in the CMS.  Furthermore, you can have multiple 404 pages (this might be useful for localised sites). 

From here, you could easily just fill in the settings/content /errors/error404 element of the umbracoSettings.config and be done with it:

<settings>

    <content>

        <errors>

            <error404>1074error404>

But instead, I like to keep my node ID constants in the Web.config so they can be transformed when deploying for different environments:

<configuration>
    <appSettings>

        <add key='404NodeId' value='1074'/>

For this we need to implement a custom handler to plug into Umbraco: 

namespace MySite.Handlers
{
public class PageNotFoundHandler : INotFoundHandler
{
    private int redirectId = 1;
 
    public bool Execute(string url)
    {
        var redirectToNodeId = false;
 
        // Determine if custom errors are enabled
        var config = WebConfigurationManager.OpenWeb
	Configuration('~');
        CustomErrorsSection customErrorsSection = 
	(CustomErrorsSection)config.GetSection('system.web/
	customErrors');
 
        if (customErrorsSection != null
            && (customErrorsSection.Mode == CustomErrorsMode.On
                || (customErrorsSection.Mode == CustomErrors
	Mode.RemoteOnly && !HttpContext.Current.Request.IsLocal)))
        {
            redirectId = int.Parse(ConfigurationManager.App
	Settings['404NodeId']);
            HttpContext.Current.Response.StatusCode = 404;
            redirectToNodeId = true;
        }
 
        return redirectToNodeId;
    }
 
    public bool CacheUrl
    {
        get { return false; }
    }
 
    public int redirectID
    {
        get { return redirectId; }
    }
}

} 

The Execute method is where you would decide which, if any, node to redirect to.  Note that the above implementation takes direction from your section in the Web.config – you won’t see the fancy 404 page on your local machine unless you have custom errors set to “On”.  Configure your handler in the 404handlers.config

<NotFoundHandlers>
    <notFound assembly='umbraco' type='SearchForAlias' />
    <notFound assembly='umbraco' type='SearchForTemplate'/>
    <notFound assembly='umbraco' type='SearchForProfile'/>
    <notFound assembly='MySite' namespace='MySite.Handlers' type='PageNotFoundHandler' />
  <\NotFoundHandlers>

That’s it!

500 (Server Error)

Server errors are a bit trickier – IIS7 likes to hijack your exceptions and spit out a very boring page.  The first step is to tell IIS that your application is handling errors (in the Web.config): 

<system.webServer>
        <httpErrors existingResponse='PassThrough' /> 

Note that the responsibility for handling all HTTP errors now lies on your application.   Define a catch-all error page, which I’m calling 500.aspx:

<system.web>
        <customErrors mode='On'
                      defaultRedirect='~/500.aspx' 

                      redirectMode='ResponseRewrite' />

Create the 500.aspx page like so:

<%@ Page Trace='false' %>
<% 
    this.Response.StatusCode = 500; // server error
%>
doctype html>
<html>
<head>
    <title>Server Errortitle>
    <style type='text/css'>
        h1 {
            colorRed;
        }
    style>
head>
<body>
    <h1>Server error!h1>
body>

html>

The reason I set Trace=”false” is to ensure Umbraco doesn’t spit out a trace – which it does even when umbracoDebugMode is set to false.  Yikes!  Additionally, this is where you need to set the status code to 500, otherwise you get a 200 (something I fail to understand…)