Pragmatic Web Error Handling in ASP.NET MVC

There is a pragmatic approach to error handling in ASP.NET MVC. This is the idea of recovering gracefully from those unhandled exception errors where remedial action cannot be taken under the current context, by passing control to a specified URL that is designed to deal with a particular category of application-specific error. Dino Esposito elaborates on a pattern that prevents unhandled exceptions from bubbling up well beyond the intended scope

There are many ways to handle errors and exceptions in an ASP.NET MVC application. I summarized all of them in an old article I wrote for Simple Talk. You can find it here: Handling Errors Effectively in ASP.NET MVC. That article offered a comprehensive view of all the possible techniques a developer can adopt including using the HandleError attribute on controller methods and providing an explicit implementation for the OnException method in a controller class. All these techniques work on top of any preliminary validation you can make over your data to prevent errors and any try/catch block you want to use to trap unwanted but expected exceptions.

In this article, I’ll take a slightly different, and wildly more pragmatic, route and focus exclusively on application-level recovery from unhandled exceptions. As I was saying in several books and articles, including the aforementioned Simple Talk article, every application should always include an application-level recovery procedure that serves as a sort of safety net for users and also saves the reputation of the coders. The world is full of examples of failures and disasters due to unhandled exceptions that bubbled up well beyond the intended scope, thereby causing undesired and unpredictable effects. So let’s just make it a rule that just every application should have its own Application_Error method right in global.asax.

What kind of code should we have there and to do what exactly? Let’s start by putting Application_Error into perspective.

The Application_Error Method

Application_Error is the conventional name for the routine that ASP.NET (not just ASP.NET MVC) calls right before displaying its own error screen—the notorious yellow screen of death. If you don’t have such a routine or if for some reason you let some events slip out of it your users will get the standard error page or the page that for common HTTP error codes (like 404 or 500) you may have defined in the web.config file.

Therefore, Application_Error is a sort of catch-all place where a number of unpleasant application events find their way. The overall role of Application_Error is quite controversial and it’s common to find companies where development teams have strong opinions about having or not such a centralized handler of application errors. Some find it just an ideal single place where handling all possible errors and exceptions. Others find the use of Application_Error a bit unprofessional as if it were the result of poor coding practices.

Technically speaking, Application_Error is a dumb event-handler that passes no specific information about the error event that just occurred.

To write an effective handler, you need to put some very specific code in action. This code is expected to do at least a couple of things. First, it is expected to learn as much as possible about what has just happened. Second, it has to decide what to do; whether to just log the error or to redirect it to a safe place where the user can resume the session. Another option is to run whatever compensation logic makes sense, given the error that occurred.

So far, I have deliberately used the two words “error” and “exception” interchangeably. However, defining errors and exceptions is merely the first step towards an effective, yet pragmatic, error handling strategy.

Errors vs. Exceptions

In the context of a web application, I define an “error” as being an action that the user attempts to perform, but which fails under the total control of the application code. A good example is when the user that edits the URL in the address bar is then stopped by authorization rules, missing endpoints or inaccurate and invalid data. I define an “exception” as being a failure that happen outside the control of the application code. A good example is a network failure or a database error. For example, when your server-code places a remote call to an HTTP endpoint, it may fail for a number of reasons that may not depend on your code. There’s no validation that you can perform beforehand to ensure the call will always succeed. So in these cases it is a safe practice to wrap the network call in a try/catch block. In a way, this becomes an “expected” exception. Depending on the nature of the expected exception there are a few things you can do. For one thing, you can compensate for the effects of the exception in the catch block; for example you can retry the call. As an alternative, you swallow the exception and return some feedback to the caller or you can just throw a different exception with more generic or more specific information. Finally, you can simply log the exception in some way and let ASP.NET do the job of bubbling up the exception until a handler is found. An error is an action that cannot be taken under the current conditions. In this regard, an error requires a strong reaction from the system such as a popup message or a redirection to a landing page.

To deal with errors and exceptions I suggest the following guidelines.

  • Wrap in try/catch blocks any calls that can possibly generate an “expected” exception. In the catch block, you may log and/or swallow in some way the exception or, when allowed, just implement some compensation logic right in the block.
  • Always have an Application_Error method, so that unexpected exceptions are stopped before they reach the outermost shell of ASP.NET MVC code and render as yellow screens of death.
  • Use large chunks of validation logic to prevent errors as much as possible. As mentioned, errors originate from violated business rules and you are supposed to know them very well. When users find a way to bypass validation and the outlined user interface (i.e., they type an invalid URL on the browser address bar) you throw yourself an exception and redirect the application flow to Application_Error.

As I have experienced sometimes, the option of throwing an exception to redirect the code to Application_Error is one that some developers don’t like much.

Use of Exceptions

A golden rule of exception handling is that you should not use exceptions as control flow statements. I pretty much agree with the general meaning of the statement but in case of web applications and limited to errors I’m just doing that. When I detect an error—say, a violation of a well-known business rule—I throw an application exception and send user to a contextual view through the Application_Error handler.

Let’s be pragmatic. How would you react when you find that your controller action is being invoked with erroneous parameters? Or you find a logical error in a deeper layer of code that prevents the action from being completed? In the context of a web application, it is not an option to display a message box until it’s all JavaScript code: Worse yet, a controller method has to return some HTML views.

If you force yourself not to use exceptions as control flow statements, you then must have an error sub view in each and every view that can possibly face an error. And maybe a different sub view for each possible error in the view. I’m going to deny the purity of such an approach; but I find it a bit impractical. Throwing an application-specific exception every time you detect a logical error helps keeping your controller code as lean as possible. In addition, deeper layers of code—such as application logic but even more domain logic—get to have a cleaner design as their methods would simply reject through exceptions whatever scenario they can’t handle. The throw statement if not caught at some level bubbles up and is ultimately handled in Application_Error.

The deal is, cleaner code in controllers and one single place where all errors can be handled. With proper code in Application_Error you can redirect users to an appropriate error page.

Unless the exception is expected and you know how to handle it, it is not advisable to catch exceptions in every page, especially if you’re dealing with all of them in the same way. Most of the error handling code I’ve seen is about logging an error to some local or remote database and perhaps email the site admin. There’s no reason for repeating this code, even in the compact form of an attribute, in each controller method. By the way, this is the reason that made ELMAH a powerful and widely used tool for error handling in ASP.NET.

Typical Code to Have in Application_Error

In summary, Application_Error plays two main roles. It offers a chance to recover gracefully from any sort of unexpected exceptions like 404 errors, model binding errors, route errors or generic internal errors. In addition, it acts as a dispatcher of error views so that each error presents the user a friendly message as well as a list of options or links to resume the session. Here’s some sample code that’s useful to have in the Application_ Error method.

As obvious as it may sound, if you handle exceptions you should ideally try to do more than just logging it somewhere. Doing more than just logging means isolating a few classes of unexpected events for which you define a compensation policy. A 404 is clearly one kind of unexpected event you want to handle along with authorization issues such as when users try to reach pages they’re not authorized to see or invoke endpoints they’re should not be calling. Internal errors (HTTP status code 500) should be split into multiple categories and the best way to do that in my view of the world is through application-specific exceptions.

In your code, you define a base exception class and make it expose a friendly and articulated informative content as well as a collection property storing feasible links to recover from. The InvokeErrorAction subroutine in the above code snippet will then do the rest.

void InvokeErrorAction(HttpContext httpContext, Exception exception)

All that the method does is to invoke a controller action programmatically. The final effect is to display a custom view based on the information associated with the exception. Admittedly, such a code is a bit unusual to see and, of course, it is not the only possible way to just display a HTML view. However, I commonly opt for this code for one particular reason: the view is displayed in the context of the same HTTP request. In a way, it is the ASP.NET MVC counterpart of Server.Transfer you would use in ASP.NET Web Forms in the same scenario. Using a redirect to some URL like /home/error would achieve the same but at the cost of serializing in some way the exception. Unless you opt for serializing the exception core data somewhere on the server—preserving affinity in case of web farms—you lose that information or should pack it in some special places such as query string or headers.

Why not simply calling the method Error (or whatever other method) on the Home controller (or whatever other controller)? The reason is that a direct call to the Error method won’t trigger the view engine and won’t actually generate the expected HTML view. Calling the Execute method on the base controller interface triggers the ASP.NET MVC action invoker component that first calls the controller method and then executes the action result it gets from the controller action method. If you don’t much like to see HomeController in the code, the best you can do is to have an application-specific base controller class that exposes an Error method. Here’s a sample implementation.

The net effect is that, if the exception is an application-specific exception in the case of an HTTP 500 error, then you get the exception message: Otherwise you get a generic HTTP 500 error message plus the system generated unfiltered exception message which might even contain sensitive data. Finally, the exception might be associated with a list of links to be displayed in the error view for points in the application where the user can be safely redirected to continue the session. For example, the home page.

A Word or Two About ASP.NET Core

Everything in this article works for the current ASP.NET MVC 5.x. The interesting thing is that the approach to error handling in ASP.NET Core is pretty much the same approach that I described here as a pragmatic approach. In ASP.NET Core, when you register services in the startup of the application you typically use the following code:

The effect is that in case of unhandled exceptions the control is moved to the specified URL. Whether you go there through a redirect or an internal re-route it doesn’t change the emerging perspective of error handling in web applications. Throwing application-specific exceptions allows to show precise messages and breaks up the flow, saving you from dealing with many branches of code. More, most of the handling logic is in a single place (or a in just a few places) and this doesn’t even prevent logging or tracing.