Web.config customErrors element with ASP.NET explained
The CustomErrors
element inside an ASP.NET/MVC/Web API Web.config
file, is something almost everyone uses somehow, however, many people’s experiences don’t work like they expect. During my years coding primarily ASP.NET MVC, I've used the custom errors element again and again. I have also spent countless hours Googling issues and browsing through StackOverflow. This post is an attempt to put down all of the things I have learned on "paper," so you can avoid having to go through the same pain I did.
Let's start by discussing what we can do with the customErrors
element. When errors happen on your ASP.NET application, you want to tell the user that an error happened. Out of the box, ASP.NET provides an error page often referred to as the "Yellow Screen of Death" (YSoD):
The YSoD is the default fallback when no custom error page has been configured. YSoD works after deploying it to another server, but it looks different:
It's the same page, but one is accessed through localhost and the other through a remote name. ASP.NET hides an application’s specific details like the stack trace, file locations, .NET version, etc., as a security feature. While the first example is quite fine when running locally and where you want to track down errors, the second is not very user-friendly.
Before we start digging down into custom error pages, let me put a few words in on the possibilities listed in the screenshot above. As mentioned, you can add the following to the system.web
element:
<configuration>
<system.web>
<customErrors mode="Off"/>
...
</system.web>
...
</configuration>
Doing so will give you the detailed error message from the first screenshot, even when running in production. This approach is used by some developers while setting up the production environment or if everything fails.
I discourage you from disabling custom errors on the production environment. Everyone will be able to inspect details about your application that could be potential fuel for hackers. Always use an error logging solution (like ELMAH or elmah.io) instead, since they will log the details and still only show the generic error message to the user.
Finally, let's talk about some custom errors. So what exactly is a custom error? Actually, it's just an HTML (typically) document like everything else. The page should explain to the user that something went wrong and it's preferable to help the user move on by either proposing alternatives or giving them a way to contact support.
Different versions of ASP.NET come with different features. I'll explain how MVC defines error pages later in this post, and why I'll focus on ASP.NET WebForms first. We have already seen a possible value of the mode
attribute (mode="Off"
). To enable an error page, set the value to On
and specify an error page in the defaultRedirect
attribute:
<configuration>
<system.web>
<customErrors mode="On" defaultRedirect="~/Error.aspx"/>
...
</system.web>
...
</configuration>
When an exception occurs, ASP.NET will automatically redirect the user to /Error?aspxerrorpath=/default.aspx
. The aspxerrorpath
parameter is appended to the URL specified in defaultRedirect
to help identify the page that caused the error. If you want ASP.NET to keep the user on the failing URL, but still show the error page, you can use the redirectMode
attribute:
<configuration>
<system.web>
<customErrors mode="On" defaultRedirect="~/Error.aspx" redirectMode="ResponseRewrite"/>
...
</system.web>
...
</configuration>
When setting redirectMode
to ResponseRewrite
the error page is shown, but the URL stays on /Default.aspx
(which is the page where I'm throwing an exception). If you want to go back to the old behavior, either remove the redirectMode
attribute or set it to ResponseRedirect
.
If you are coding along you may have noticed that ASP.NET now shows the custom error page when running localhost. If you are using elmah.io or something similar to inspect your errors, this may not be a problem. But in most cases, having the YSoD when running locally is a huge advantage. To get this behavior, set mode
to RemoteOnly
:
<configuration>
<system.web>
<customErrors mode="RemoteOnly" defaultRedirect="~/Error.aspx"/>
...
</system.web>
...
</configuration>
As expected, ASP.NET now only redirects the user to the error page if the web application is accessed on a remote name other than localhost
.
Another feature worth mentioning is the option of having multiple error pages. You may have a "funny" 404 page and another page reassuring users who experience a 500 that you are indeed looking at the error. To do just this, use error
child elements:
<configuration>
<system.web>
<customErrors mode="RemoteOnly" defaultRedirect="~/Error.aspx">
<error statusCode="404" redirect="~/Notfound.aspx" />
</customErrors>
...
</system.web>
...
</configuration>
The added error
element tells ASP.NET to redirect the user to /Notfound.aspx
if an exception is thrown and the status code is 404
. You can define multiple error
elements with each "listening" for an individual status code. If the returned status code doesn't match one already specified in an error
element, ASP.NET uses the value in defaultRedirect
as a fallback.
ASP.NET MVC
ASP.NET MVC introduces new features for custom error pages, but everything is still built on top of the ASP.NET pipeline. When creating a new project using the template available in Visual Studio, you get an error page generated (Views/Shared/Error.cshtml
) which you can style to meet your needs. The page is triggered using a combination of setting mode
to On
or RemoteOnly
in web.config
(as shown multiple times already) and by adding the MVC filter HandleErrorAttribute
either on individual controllers and/or actions or simply as a global filter in FilterConfig.cs
:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
}
This kind of custom error page can be fine for simple projects. However, in real life you want to look into something more elaborate since the HandleErrorAttribute
only handles errors that happen within the context of MVC (500
s basically).
Another downside of using the HandleErrorAttribute
is that it may end up swallowing exceptions. This means that error logging platforms like ELMAH and elmah.io don't work as intended. To overcome this, people have been specifying their own specialization of the HandleErrorAttribute
that logs the error to ELMAH as discussed here. If you MUST use the error attribute available in MVC, I recommend you use Alexander Beletsky's Elmah.MVC package, which automatically handles this issue for you. In case you are an elmah.io user, install the Elmah.Io.Mvc package. This package installs the Elmah.MVC
package to make everything run smoothly.
An alternative solution is to not use the HandleErrorAttribute
anywhere. Instead, you can define a new controller named StatusCodeController
:
public class StatusCodeController : Controller
{
public ActionResult NotFound()
{
return View();
}
public ActionResult InternalServerError()
{
return View();
}
}
In the web.config
file use the routes given by MVC:
<configuration>
<system.web>
<customErrors mode="RemoteOnly" defaultRedirect="/statuscode/internalservererror">
<error statusCode="404" redirect="/statuscode/notfound" />
</customErrors>
...
</system.web>
...
</configuration>
Setting up custom error pages like this ensures that any errors thrown in your application are redirecting to the correct error page.