ASP.NET Custom Errors: Preventing 302 Redirects To Custom Error Pages

by Colin Cochrane 1/25/2008 9:53:42 PM
 
You can download the HttpModule here.
 
Defining custom error pages is a convenient way to show users a friendly page when they encounter an HTTP error such as a 404 Not Found, or a 500 Server Error.  Unfortunately ASP.NET handles custom error pages by responding with a 302 Temporary redirect to the error page that was defined. For example, consider an example application that has IIS configured to map all requests to it, and has the following customErrors element defined in its web.config:
 
<customErrors mode="RemoteOnly" defaultRedirect="~/error.aspx">
<error statusCode="404" redirect="~/404.aspx" />
</customError>

If a user requested a page that didn't exist, then the HTTP response would look something like:

http://www.domain.com/non-existant-page.aspx --> 302 Found
http://www.domain.com/404.aspx  --> 404 Not Found
Date: Sat, 26 Jan 2008 03:08:21 GMT
Server: Microsoft-IIS/6.0
Content-Length: 24753
Content-Type: text/html; charset=utf-8
X-Powered-By: ASP.NET
 
As you can see, there is a 302 redirect that occurs to send the user to the custom error page.  This is not ideal for two reasons:

1) It's bad for SEO

When a search engine spiders crawls your site and comes across a page that doesn't exist, you want to make sure you respond with an HTTP status of 404 and send it on its way.  Otherwise you may end up with duplicate content issues or indexing problems, depending on the spider and search engine.

2) It can lead to more incorrect HTTP status responses

This ties in with the first point, but can be significantly more serious.  If the custom error page is not configured to response with the correct status code then the HTTP response could end up looking like:

http://www.domain.com/non-existant-page.aspx --> 302 Found
http://www.domain.com/404.aspx  --> 200 OK
Date: Sat, 26 Jan 2008 03:08:21 GMT
Server: Microsoft-IIS/6.0
Content-Length: 24753
Content-Type: text/html; charset=utf-8
X-Powered-By: ASP.NET
 
Which would almost guarantee that there would be duplicate content issues for the site with the search engines, as the search spiders are simply going to assume that the error page is a normal page, like any other.Furthermore it will probably cause some website and server administration headaches, as HTTP errors won't be accurately logged, making them harder to track and identify.
I tried to find a solution to this problem, but I didn't have any luck finding anything, other than people who were also looking for a way to get around it.  So I did what I usually do, and created my own solution.
 
The solution comes in the form of a small HTTP module that hooks onto the HttpContext.Error event.  When an error occurs, the module checks if the error's type is an HttpException.  If the error is an HttpException, then the following process takes place:
  1. The response headers are cleared (context.Response.ClearHeaders() )
  2. The response status code is set to match the actual HttpException.GetHttpCode() value (context.Response.StatusCode = HttpException.GetHttpCode())
  3. The customErrorsSection from the web.config is checked to see if the HTTP status code (HttpException.GetHttpCode() ) is defined.
  4. If the statusCode is defined in the customErrorsSection then the request is transferred, server-side, to the custom error page. (context.Server.Transfer(customErrorsCollection.Get(statusCode.ToString).Redirect) )
  5. If the statusCode is not defined in the customErrorsSection, then the response is flushed, immediately sending the response to the client.(context.Response.Flush() )

Here is the source code for the module.

   1: Imports System.Web
   2: Imports System.Web.Configuration
   3:  
   4: Public Class HttpErrorModule
   5:   Implements IHttpModule
   6:  
   7:   Public Sub Dispose() Implements System.Web.IHttpModule.Dispose
   8:     'Nothing to dispose.
   9:   End Sub
  10:  
  11:   Public Sub Init(ByVal context As System.Web.HttpApplication) Implements System.Web.IHttpModule.Init
  12:     AddHandler context.Error, New EventHandler(AddressOf Context_Error)
  13:   End Sub
  14:  
  15:   Private Sub Context_Error(ByVal sender As Object, ByVal e As EventArgs)
  16:     Dim context As HttpContext = CType(sender, HttpApplication).Context
  17:     If (context.Error.GetType Is GetType(HttpException)) Then
  18:       ' Get the Web application configuration.
  19:       Dim configuration As System.Configuration.Configuration = WebConfigurationManager.OpenWebConfiguration("~/web.config")
  20:  
  21:       ' Get the section.
  22:       Dim customErrorsSection As CustomErrorsSection = CType(configuration.GetSection("system.web/customErrors"), CustomErrorsSection)
  23:  
  24:       ' Get the collection
  25:       Dim customErrorsCollection As CustomErrorCollection = customErrorsSection.Errors
  26:  
  27:       Dim statusCode As Integer = CType(context.Error, HttpException).GetHttpCode
  28:  
  29:       'Clears existing response headers and sets the desired ones.
  30:       context.Response.ClearHeaders()
  31:       context.Response.StatusCode = statusCode
  32:       If (customErrorsCollection.Item(statusCode.ToString) IsNot Nothing) Then
  33:         context.Server.Transfer(customErrorsCollection.Get(statusCode.ToString).Redirect)
  34:       Else
  35:         context.Response.Flush()
  36:       End If
  37:  
  38:     End If
  39:  
  40:   End Sub
  41:  
  42: End Class

The following element also needs to be added to the httpModules element in your web.config (replace the attribute values if you aren't using the downloaded binary):

<httpModules>
<add name="HttpErrorModule" type="ColinCochrane.HttpErrorModule, ColinCochrane" />
</httpModules>

And there you go! No more 302 redirects to your custom error pages.

Currently rated 4.9 by 272 people

  • Currently 4.878677/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: ,

ASP.NET | SEO | Web Development

Comments

1/25/2008 10:32:33 PM

James

Thank you so much! I've been bashing my head against the wall trying to figure out why one of our client's sites kept responding with all of those 302's. I'll definitely be giving this a try.

James us

1/25/2008 11:12:34 PM

Eric

Tried it out tonight and it worked like a charm.

Thank you!

Eric us

2/5/2008 5:13:18 AM

Abhiahek

Hi I like this Article too much , but can u provide this Article in C#, I will b too thankfull to you.

Abhiahek in

3/4/2008 1:30:55 AM

Sammy

Here is the C# translation for anyone to use

using System.Web;
using System.Web.Configuration;
using System;

public class HttpErrorModule : IHttpModule
{

private void Context_Error(object sender, EventArgs e)
{
HttpContext context = ((HttpApplication)sender).Context;
if((object.ReferenceEquals(context.Error.GetType(),typeof(HttpException))))
{
// Get the Web application configuration.
System.Configuration.Configuration configuration = WebConfigurationManager.OpenWebConfiguration("~/web.config");
// Get the section.
CustomErrorsSection customErrorsSection = (CustomErrorsSection)configuration.GetSection("system.web/customErrors");
// Get the collection
CustomErrorCollection customErrorsCollection = customErrorsSection.Errors;
int statusCode = ((HttpException)context.Error).GetHttpCode();

//Clears existing response headers and sets the desired ones.
context.Response.ClearHeaders();
context.Response.StatusCode = statusCode;

if ((customErrorsCollection.Get(statusCode.ToString()) != null))
{
context.Server.Transfer(customErrorsCollection.Get(statusCode.ToString()).Redirect);
}

else
{
context.Response.Flush();
}
}
}

#region IHttpModule Members

public void Dispose()
{
//Do nothing here
}

public void Init(HttpApplication context)
{
context.Error += new EventHandler(Context_Error);
}

#endregion
}

Sammy ca

3/4/2008 8:35:35 AM

Colin Cochrane

Thanks Sammy!

Colin Cochrane ca

3/10/2008 5:33:43 PM

Atif Aziz

Some months backs, I had a discussion about the same topic in the following thread over at the ELMAH discussion group:
groups.google.com/.../d01b003b37b023c7

I'm glad to see that someone has finally brought the problem to public light. I'll be sure to point to this post in the future as I hop from one ASP.NET soul to another trying to hopelessly explain how custom error handling in ASP.NET is broken.

Apart from spiders, the 302-style custom error handling breaks any kind of client-side code that deals with parsing or downloading of web resources. Imagine using System.Net.WebClient.DownloadFile to download a ZIP file from a URL that results in an error (perhaps denial of authorization). The web site will respond with a 302 followed by 200. The ZIP will be reported to have downloaded successfully yet any decompression tool will find the archive to be corrupted. It will be corrupted because its content will reflect the customer error page. Tong I have found that the corruption scenario gets people more nervous and itching to address the issue in their sites than, say, the argument regarding search engines and SEO.

BTW, you might want to add a check for Context.IsCustomErrorEnabled in the module. This way, it will adapt automatically between production and development environments since one tends to configure customer error pages such that they're disabled during development (to get the yellow screen of death) but enabled in production.

Atif Aziz

4/22/2008 2:19:56 PM

Kenpachi

Awesome module! I've been testing it out on my local machine and the 404 errors work flawlessly, but I can't get the 500 errors to work without redirecting. Every time I trigger an error somewhere it ignores your module and does that redirect spiel. Does anyone have a work around? Thanks!

Kenpachi us

4/25/2008 9:05:40 AM

Colin Cochrane

Kenpachi - Would you mind showing us your web.config?

Colin Cochrane ca

Add comment


(Will show your Gravatar icon)  

  Country flag

[b][/b] - [i][/i] - [u][/u]- [quote][/quote]



Live preview

5/16/2008 5:53:20 AM

Powered by BlogEngine.NET 1.3.1.0

All Content and Intellectual Property is under Copyright Protection | Colin Cochrane ©2007

About the author

Colin Cochrane

Colin Cochrane

SEO and ASP.NET Developer.

Recent comments

Disclaimer

This is a personal weblog. The opinions expressed here represent my own and not those of my employer. © Copyright Colin Cochrane 2008

Sign in