Skip to the content Back to Top

I've encountered many twists and turns in my journey towards handling application errors with aplomb. You have to make sure:

  1. Users are happy (by providing informative error pages) - well, as happy as they can be under the circumstances.
  2. Developers who need to fix the problem are happy (extensive logging and notification to help re-enact, locate, and then fix the bug so it doesn't happen again).
  3. Search bots are happy (by sending them the right message).

For example, when a user requests a resource that can't be found you want to help the user out with at least a pretty page that explains what has happened. Most users don't have a clue what a "404 error" is and could care less. And almost just as important, when a search bot encounters an error it needs to receive a proper error code back or else it gets all uppity on you and knocks down the page's ranking. In other words, when a page can't be found:

  1. Redirect to a helpful "Sorry, we can't find the resource you're looking for" page that hopefully looks somewhat decent and gives the user some options on how to proceed (perhaps like our "smart" page not found functionality on our soon-to-be-launched www.andornot.com).  
  2. Log the 404 so that it can be dealt with (if applicable).
  3. Set the proper 404 error code for SEO purposes.

However, sometimes getting the three above objectives dealt with in one fell swoop is problematic. For example, if you just redirect after having an error occur (say a "500 Internal Server Error") to a pretty page, users will be happy, but search bots will receive something like the following:

Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/7.5
X-AspNet-Version: 2.0.50727
X-Powered-By: ASP.NET
Date: Fri, 02 Oct 2009 18:30:47 GMT
Content-Length: 7719

200 OK

200 OK for something that really is not okay. It should be "500 Internal Server Error"! Erk. So how about we code in something like:

Response.StatusCode = 500;
Response.StatusDescription = "500 Internal Server Error";

But then things start to get interesting. In IIS 6 all appears to look good, but when you're using MasterPages, Internet Explorer 6 (in a rare case of actually being helpful) shows that what's being sent back is wacked as it wraps the custom error page with the default IIS error page. Gee, now two out of three of the above objectives are out the window. Suffice it to say, the result is grotesque, and I can't imagine what search bots think of it!

Moving on to IIS 7 and its Integrated Pipeline (doesn't apply to Classic Pipeline mode), things get even more interesting as it ignores your custom stuff altogether and sends back the applicable default IIS error page. What the? Rick Strahl has an excellent post addressing this issue. Essentially, the error is still trapped by ASP.NET (so it's logged, redirected, and whatever else you ask it to do), but then it is ultimately handled by IIS which sees the error code and hijacks the entire response and returns its ugly default error page (and who designed those anyway? Even IIS 6's were better).

So where do we go from here? In IIS 6, the problem only occurs when using MasterPages in your error page. Over simplifying, the page's MasterPage renders later on in the page lifecycle and resets the error code to 200 OK. And it just gets messier from there (see http://stackoverflow.com/questions/347281/asp-net-custom-404-returning-200-ok-instead-of-404-not-found for more info). So reset it back again further along in the page's lifecycle!

protected override void Render(HtmlTextWriter writer)
{
    base.Render(writer);
    Response.StatusCode = 500;
    Response.StatusDescription = "500 Internal Server Error";
}

But what about IIS 7? If you look further along in Rick Strahl's post mentioned above, there's a new flag in ASP.NET 3.5 called Response.TrySkipIisCustomErrors. As Rick says, "In a nutshell this property when set to true at any point in the request prevents IIS from injecting its custom error pages. This flag is new in ASP.NET 3.5 - so if you're running 2.0 only you're out of luck."

    Response.TrySkipIisCustomErrors = true;
    Response.StatusCode = 500;
    Response.StatusDescription = "500 Internal Server Error";

And Shazam!

HTTP/1.1 500 500 Internal Server Error
Cache-Control: private
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/7.0
X-AspNet-Version: 2.0.50727
X-Powered-By: ASP.NET
Date: Fri, 02 Oct 2009 20:02:48 GMT
Content-Length: 6879

Error handled and logged, developers notified, user somewhat mollified, and Google/Bing happy. How's that for making the best of a bad situation?

For more information, see some of the following:

Of course, there's a ton of caveats in the above. However, one that is definitely worth addressing is how 404 handling is extra-special-different in ASP.NET. Very extra-special-different. And badly very different. But we'll save that for the next post.

UPDATE: If for some reason you cannot change your code, a good option to pursue is flipping a switch in your applicationHost.config file that passes your response through without IIS hijacking it. This is the sledgehammer approach, but could be applicable for your situation. For example, in order to allow Umbraco's alternative status code responses to work, this is the only way to get them to avoid being hijacked by IIS (i.e. NotFoundHandlers, booting screens, etc.).

<location path="Site Description">
    <system.webServer>
        <httpErrors existingResponse="PassThrough" />
    </system.webServer>
</location>

Thanks to Fabian Heussser's comment on Rick Strahl's post that helped with this.

Let Us Help You!

We're Librarians - We Love to Help People