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.

Web.config customErrors element with ASP.NET explained

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):

Server Error

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:

Runtime Error on production

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.

Would your users appreciate fewer errors?

➡️ Reduce errors by 90% with elmah.io error logging and uptime monitoring ⬅️

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 (500s 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.

elmah.io: Error logging and Uptime Monitoring for your web apps

This blog post is brought to you by elmah.io. elmah.io is error logging, uptime monitoring, deployment tracking, and service heartbeats for your .NET and JavaScript applications. Stop relying on your users to notify you when something is wrong or dig through hundreds of megabytes of log files spread across servers. With elmah.io, we store all of your log messages, notify you through popular channels like email, Slack, and Microsoft Teams, and help you fix errors fast.

See how we can help you monitor your website for crashes Monitor your website