After much reading and experimentation I found this combination of settings to work effectively:
The Friendly Error Pages
One aspx and one html page for each response status code (here's mine):
- 404.aspx
- 404.html
- 500.aspx
- 500.html
The only difference between the two pages is that the aspx pages contain the following lines:
<% Response.StatusCode = 500 %>
<% Response.TrySkipIisCustomErrors = true %>
The first line sends the correct HTTP status code back to the client, and the second line attempts to persuade IIS that it doesn't need to handle the response itself.
Web.config Setup
Custom Errors should be on
, or remoteonly
, and point to the aspx files:
<customErrors mode="On" defaultRedirect="500.aspx" redirectMode="ResponseRewrite">
<error statusCode="404" redirect="404.aspx" />
</customErrors>
IIS custom errors should be on too, and point to the html files in the system.webServer
section:
<httpErrors errorMode="Custom" existingResponse="Auto">
<remove statusCode="404" subStatusCode="-1" />
<remove statusCode="500" subStatusCode="-1" />
<error statusCode="404" path="404.html" responseMode="File" />
<error statusCode="500" path="500.html" responseMode="File" />
</httpErrors>
The existingResponse="Auto"
tells IIS to only return the friendly error pages if the SetStatus
flag is set. Effectively this allows ASP.net to send back a custom response, its own custom error page from the customErrors
section, or allow IIS to return the configured friendly error page.
FilterConfig.cs Setup
A default ASP.net MVC/WebAPI project is configured with a HandleErrorAttribute
filter that handles exceptions raised from actions and returns the correct configured custom error page. I have extended this class to handle exceptions from WebAPI actions by deriving from this class:
filters.Add(new HandleExceptionAttribute());
HandleExceptionAttribute
public class HandleExceptionAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest() && filterContext.Exception != null)
{
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
filterContext.HttpContext.Response.StatusDescription = filterContext.Exception.Message;
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
}
else
{
base.OnException(filterContext);
}
}
}
This class handles exceptions from WebAPI actions and returns the message of the exception as a JSON response (with the correct HTTP status) to the caller. You might not want to do this if your exception messages aren't user friendly, or if the client doesn't know how to interpret these messages.