True ASP.Net (and IIS) 404 errors

UPDATE: I should found out that in ASP.Net 3.5 the <customErrors> section in web.config now supports the attribute redirectMode which default to ResponseRedirect but can be changed to ResponseRewrite. This will force the proper HTTP status codes to come through for errors.

So I launched a new site porting it from static .html files to dynamic .aspx. I then created an entry for 404’s in web.config and also told IIS to send 404’s to the same page for everything that ASP.Net didn’t handle (images, html files, etc). So simple, right? Unfortunately both ASP.Net and IIS don’t appear to actually send a 404 status code for 404’s if you have a 404 handler specified. IIS actually sends a 200 OK and performs the equivalent of a Server.Transfer. ASP.Net actually sends a 302 Found which is a client-side redirect.

Now, you may not care about this, but if you’re trying to purge search engines of old content you should.

The fix, at first glance, is simple. In Page_Load on the 404 handler page just send the proper status code using Response.StatusCode = 404. This actually fixes the problem with IIS but we’ve still got an ASP.Net problem. ASP.Net will actually still send the 302 redirecting people to the 404 page and then send the 404 code. So if page Blah.aspx doesn’t exist you’ll get a 302 code saying to go to PageNotFound.aspx. When the browser requests PageNotFound.aspx they finally get the 404 response. Now maybe that’s good enough for you, but not for me. When I 404, I want to know right away, not after a redirect.

I found the solution here and have reproduced the code below for easy copy-and-paste. They don’t explicitly mention it in the article but you MUST set the <customErrors> to Off or at least RemoteOnly. I know that sounds weird but the code below still actually processes the <customErrors>, it just does it instead of letting ASP.Net handle it for you.

The article talks about creating a DLL but you can just as easily drop it into App_Code as a file named HttpErrorModule. Then just add <add name="HttpErrorModule" type="HttpErrorModule" /> to your <httpModules>.

One other thing to note, this module actually overrides all errors and sends their proper codes in the response stream. This shouldn’t cause any problems but you should be aware of it when debugging. You could very easily add some logic to check for 404’s only, too.

'Code from: http://www.colincochrane.com/post/2008/01/ASP-NET-Custom-Errors-Preventing-302-Redirects-To-Custom-Error-Pages.aspx
Option Strict On
Option Explicit On

Imports System.Web
Imports System.Web.Configuration

Public Class HttpErrorModule
    Implements IHttpModule

    Public Sub Dispose() Implements System.Web.IHttpModule.Dispose
        'Nothing to dispose.
    End Sub
    Public Sub Init(ByVal context As System.Web.HttpApplication) Implements System.Web.IHttpModule.Init
        AddHandler context.Error, New EventHandler(AddressOf Context_Error)
    End Sub

    Private Sub Context_Error(ByVal sender As Object, ByVal e As EventArgs)
        Dim context As HttpContext = CType(sender, HttpApplication).Context
        If (context.Error.GetType Is GetType(HttpException)) Then
            ' Get the Web application configuration.
            Dim configuration As System.Configuration.Configuration = WebConfigurationManager.OpenWebConfiguration("~/web.config")

            ' Get the section.
            Dim customErrorsSection As CustomErrorsSection = CType(configuration.GetSection("system.web/customErrors"), CustomErrorsSection)

            ' Get the collection
            Dim customErrorsCollection As CustomErrorCollection = customErrorsSection.Errors
            Dim statusCode As Integer = CType(context.Error, HttpException).GetHttpCode

            'Clears existing response headers and sets the desired ones.
            context.Response.ClearHeaders()
            context.Response.StatusCode = statusCode
            If (customErrorsCollection.Item(statusCode.ToString) IsNot Nothing) Then
                context.Server.Transfer(customErrorsCollection.Get(statusCode.ToString).Redirect)
            Else
                context.Response.Flush()
            End If
        End If
    End Sub
End Class
  • chrisjhaas

    Does this comment system work?