Handling Errors Effectively in ASP.NET MVC

ASP.NET MVC gives you more options in the way that you handle exceptions. Error handling isn't intrinsically exciting, but there are many ways of avoiding the classic yellow page of death, even getting ELMAH to manage error handling for you.

Years ago,  ASP.NET’s error handling was one of the major things that made me wonder if ASP.NET MVC could give me something that ASP.NET Web Forms couldn’t. Web Forms is based on pages; so if something goes wrong, all that you can do is to redirect the user to another page and explain what the error was or just be generically sorry. ASP.NET Web Forms allow you to map an error page for each possible HTTP status code. You control the mapping through the <customErrors> section of the web.config file.

Because of the  different architecture of the view in ASP.NET MVC,  it  is possible to save the redirect command and then programmatically switch to an error view in the context of the same request. You have this in addition to the regular page-based error handling mechanism. I wouldn’t use HTTP code redirects in ASP.NET MVC; but only because more flexible solutions are possible.

Generally speaking, error handling in ASP.NET MVC is mainly necessary to handle program and route exceptions. Program exceptions refer to catching errors in controllers and in any code you may have in Razor views. Route exceptions refer to missing links and invalid URLs.

Program Exceptions

Any stack trace you can have out of an ASP.NET MVC application originates from a method call in a controller class. The controller class, therefore, is where any exceptions in your ASP.NET MVC code can be trapped. You can do that in a number of equivalent ways. For example, you can have a try/catch block surrounding the entire method body. It works, but it’s ugly to see too. A better option is probably to override the OnException method from the base Controller class. Yet another option is using the HandleError attribute at the controller class level. Better yet, the HandleError attribute-which is ultimately an action filter-can be set globally on just every controllers and actions you can have.

At the end of the day, an effective strategy for error handling is based on the following pillars:

  • All classes down the controller level just throw exceptions when something goes wrong.
  • Some of these classes, in some of their methods, may attempt to catch some of the exceptions but mostly when a given exception is intended to be swallowed or turned into some other exception.
  • At the application level you use the HandleError global filter to catch whatever bubbles up.

Swallowing exceptions is in general a dangerous practice; but in the end it is not more dangerous than crossing the street when it’s red but there are no cars in sight. It works well as long as it doesn’t become a common practice and as long as it’s applied with a grain, or maybe two, of salt. Swallowing an exception is fine for example if your code is trying to call an external HTTP endpoint and the call times out or fails for whatever reason. In this case it might be acceptable that the routine that takes care of the call just hides the actual HTTP status code and packs the event as a special case of the regular response. Here’s an example taken from a data access repository class:

In LINQ, the Single method just throws an exception if the response includes any number of items different from one. The internal try/catch block swallows the exception and returns a special version of the Order type that just evaluates to NULL.

The NullOrder class is an instance of the Special Case pattern and has the merit of not killing polymorphism in code as NULL would do. The caller of the aforementioned method will have then the following skeleton:

There are a few guidelines you might want to adhere to when it comes to handling exceptions. The first aspect to consider is that the catch block is quite expensive and raises a peak of CPU usage when your code gets into it. For this reason, over-catching may end up affecting the overall performance of the application. It’s probably not a big deal if your code is frontend; but for server-side code scaling up the performance of the entire system it might become problematic.

A guideline from the .NET Framework team is that you never throw an exception using the System.Exception class. You should use more specific exception types whether built-in types such as InvalidOperationException and NullReferenceException or your own application specific types. On the other hand, you should also resist the temptation of having your own exception types sprinkled everywhere and even replacing analogous .NET Framework native types.

When it comes to exceptions, you should be very specific about the exception-type that you pick up and should also create instances providing as much information as possible. For example, ArgumentNullException is way more specific than ArgumentException. If the problem consists in an unexpected NULL parameter then you should go for ArgumentNullException. Furthermore, be aware that any exceptions come with a message. Provide details within the message as the message itself is targeted to developers. Another parameter of exception types that is often neglected is the name of the parameter where the exception occurred-mention it every time. It can be a lifesaver sometimes.

The OnException Method

In ASP.NET MVC, any method of any controller runs under the aegis of a system component known as the action invoker. The invoker runs all the code within a try/catch block and simply re-throws a thread-abort exception. For all other exceptions, instead, it goes through the list of registered action filters and gives each a chance to recover from the exception. At the end of the loop, if the exception has not been marked as handled, the exception originally caught is re-thrown. What happens next depends on whether you have other mechanism of exception handling set to watch over the entire application. If none is in place, which is the default, users will experience the ASP.NET classic yellow page of death or any other error page you arranged.

An action filter enabled to handle exceptions can be a separate class defined as an action filter (inherit from the ActionFilter class) or it can simply be a controller class that overrides the OnException method. This method is always invoked when an unhandled exception occurs in the course of the action method.

It’s important to be aware that no exception that originates outside the controller will be caught by OnException. An excellent example of an exception not being caught by OnException is a ‘null reference’ exception that results in the model-binding layer. Another example is ‘route not-found’ exception.

The code in OnException has the power of controlling the entire response for the request that failed. As shown above, the method receives a parameter of type ExceptionContext which has an ActionResult property named Result. This property just refers to the next view or result past the method. If you set the Result property you can control the next screen; if you omit setting any result, then the user will see just a blank screen. Here’s a typical implementation of OnException:

The SwitchToErrorView method is an extension method for the ExceptionContext class with the following signature:

As you can see, you’re allowed to indicate the next view and even its layout.

The HandleError Attribute

If you don’t like the explicit override of OnException you can decorate the class (or just individual methods) with the HandleError attribute. 

As mentioned, HandleError is an action filter and not a plain attribute carrying just a bunch of metadata. In particular, HandleError implements the IExceptionFilter interface:

Internally, HandleError implements OnException using a piece of code very similar to the SwitchToErrorView method discussed earlier. A minor difference is that HandleError doesn’t trap any exceptions resulting from child actions. Properties on the attribute lets you select the exceptions to trap and views to redirect to.

Each method can have multiple occurrences of the attribute, one for each exception you’re interested in. By default, also HandleError redirects to the same view named error we considered earlier. Note that such a view is purposely created by the ASP.NET MVC templates in Visual Studio.

When using HandleError at development time, it’s crucial to be aware that the attribute doesn’t have any effect unless you enable custom errors at the application level:

When you go live, remote users will correctly receive the selected error page regardless. To test the feature, though, you need to change the configuration file.

HandleError can be automatically applied to any method of any controller class by registering it as a global filter in global.asax:

Global filters are automatically added to the list of filters before the action invoker calls out any action method.

All Other Possible Errors

An error can always find its way to the user. For this reason, we’ve been given the Application_Error method in global.asax  ince the very first version of the ASP.NET runtime. It is just there to handle any possible errors that passed through try/catch blocks. The Error event fires whenever an unhandled exception reaches the outermost shell of ASP.NET code.  It’s the final call for developer’s code before the yellow screen of death.

You could do something useful in this event handler, such as sending an email or writing to the event log. 

While you can always write the Error handler yourself, ASP.NET developers often use ELMAH. At the very end of the day, ELMAH is an HTTP module that, once configured, intercepts the Error event at the application level and logs it according to the configuration of a number of back-end repositories. The bottom line is that with ELMAH you can handle errors in many more ways and change /add actions with limited work; and without writing much code yourself.  ELMAH also offers some nice facilities, such as a web page you can use to view all recorded exceptions and drill down into each of them.  

ELMAH is an open-source project available at http://code.google.com/p/elmah. It is so popular that it counts a number of extensions, mostly in the area of repositories. To integrate it in your applications the easiest path you can take is the Nuget package you find at http://www.nuget.org/packages/elmah/1.2.2.

Summary

Error handling is one of the most bothersome parts of software development. One of the reasons that developers avoid it is that it doesn’t seem to require much creativity. In this regard, I think that ELMAH is emblematic. Nearly any developers knows that an HTTP module could do the trick of saving rewriting the same code over and over again to send emails and log errors on ASP.NET sites. And I guess as many developers had, in the past, a thought crossing their minds about writing a sort of simple but effective infrastructure for error handling and reporting. That’s just what ELMAH is-and that’s what ASP.NET developers need. Oh well, in addition to ad hoc try/catch blocks in the code.