There's a lot of misinformation and/or out-of-date solutions regarding error handling in IIS 7+. The main things to understand are:-
- .NET 4.0 made breaking changes to ASP.NET's request validation.
- IIS 7+ introduced a new way of handling custom error pages.
Most people are using hodge-podge solutions involving all of customErrors
, httpErrors
the Application_Error
handler, and often setting requestValidationMode="2.0"
on the httpRuntime
property and/or disabling request validation entirely! This makes it really difficult to use other people's solutions because any and all of these can affect the behaviour. I had a quick search around and I found several semi-duplicates without accepted answers, probably for this reason.
The reason that these two errors give you different behaviour is that they occur at different stages in the request pipeline. The customErrors
node in your web.config
interacts with errors "inside" your application, while request validation takes place "outside" your application. IIS rejects the dangerous request before it gets to your application code, and so your customErrors
replacement doesn't happen.
So how do we fix that?
Ideally you want a solution with as few moving parts as possible. IIS7 gives us a new way to specify error page replacement at the IIS level instead of at the application level - the httpErrors
node. This lets us catch all our errors in one place:-
<configuration>
...
<system.webServer>
...
<httpErrors errorMode="Custom" existingResponse="Replace">
<clear />
<error statusCode="400" responseMode="ExecuteURL" path="/ServerError.aspx"/>
<error statusCode="403" responseMode="ExecuteURL" path="/ServerError.aspx" />
<error statusCode="404" responseMode="ExecuteURL" path="/PageNotFound.aspx" />
<error statusCode="500" responseMode="ExecuteURL" path="/ServerError.aspx" />
</httpErrors>
...
</system.webServer>
...
</configuration>
If you care about SEO (and you should!), you still have to make sure that your controller/page sets an appropriate status code:-
this.Response.StatusCode = 500; // etc.
You should remove your customErrors
node entirely. It is normally used for backwards-compatibility. You should also ensure that requestValidationMode
is not set on the httpRuntime
node.
This should catch most errors (excluding, obviously, errors in parsing your web.config!)
Related:- ASP.NET MVC Custom Errors
MSDN Documentation:- http://www.iis.net/configreference/system.webserver/httperrors
Note: in your case, if you want to set defaultPath
on the httpErrors
node, you'll get a lock violation because of ApplicationHost.config
settings. You can either do as I did and just set path
individually for the error codes you care about, or you can have a look at unlocking the node:-
My intuition is that it's more trouble than it's worth in low-control environments like Azure App Service / Azure Web Sites. You might as well set the paths for individual status codes.
I've put a full working example of using httpErrors for custom error pages up on github. You can also see it live on azure web sites.