ASP.net provides facilities in web.config for specifying error pages when an exception occurs, for example:
<system.web>
<customErrors mode="RemoteOnly" defaultRedirect="~/error-generic.aspx">
<error statusCode="403" redirect="~/error-403.aspx" />
<error statusCode="404" redirect="~/error-404.aspx" />
</customErrors>
</system.web>
This example redirects error 403 & 404 to specific pages, and all other errors to a generic page. This works great for the most part, but there are a couple of problems with it:
1. It doesn't allow for custom logging, and
2. Because of how the page is served, it returns with a HTTP status of 200 instead of 500 (or something useful).
However, both of these problems can be overcome fairly easily, using a custom HttpModule to handle your errors. First, create a new class:
public class ErrorModule : IHttpModule
{
public void Dispose() { }
public void Init(HttpApplication context)
{
context.Error += new EventHandler(Context_Error);
}
public void Context_Error(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
HttpContext context = application.Context;
System.Configuration.Configuration configuration = WebConfigurationManager.OpenWebConfiguration("~");
SystemWebSectionGroup systemWeb = (SystemWebSectionGroup)configuration.GetSectionGroup("system.web");
CustomErrorsSection customErrorsSection = systemWeb.CustomErrors;
// If customerrors mode == off, then just let IIS handle it
if (customErrorsSection.Mode != CustomErrorsMode.Off)
{
// If mode == on or its a remote request, we want to get the pretty page
if (customErrorsSection.Mode == CustomErrorsMode.On || !context.Request.IsLocal)
{
Exception ex = context.Error;
HttpException httpException = (HttpException)ex;
string sURL = customErrorsSection.DefaultRedirect;
if (httpException != null)
{
context.Response.StatusCode = httpException.GetHttpCode();
CustomErrorCollection customErrorsCollection = customErrorsSection.Errors;
CustomError customError = customErrorsCollection[context.Response.StatusCode.ToString()];
if (customError != null)
sURL = customError.Redirect;
// Log the actual cause
ex = httpException.GetBaseException();
}
else
{
context.Response.StatusCode = 500;
}
/*
Put custom logging here!
*/
context.ClearError();
context.Server.Transfer(sURL);
}
}
}
}
Then, in web.config, add the following:
<system.web>
<httpModules>
<add name="ErrorModule" type="ErrorModule" />
</httpModules>
</system.web>
When ISS loads the application, it loads the ErrorModule class and execute it's Init() method, which adds a custom handler for the Application.Error event. The handler is where the magic happens.
First, it reads the customErrors section of web.config, so it can use your settings. Next it checks the error mode, so that when you don't want to bypass the default error page, it just returns without doing anything.
After setting the default error URL, it checks if the error is a HttpException, if so it sets the response HTTP status to the code returned by the exception, and checks the code against the any custom pages you've defined in the customErrors section. If it finds a match, it updates the URL with the one from web.config. If it is not a HttpException, it sets the HTTP status to 500 (Internal server error). At this point you can add custom error logging. It then clears the error, so IIS won't do it's thing, and redirects to the selected page.