ASP.NET MVC: Handling Exceptions and 404 Errors

In a previous article, we saw how to use ELMAH for managing unhandled errors in MVC applications. If for some unique scenario, ELMAH does not handle your needs, you can fallback on the default HandleError Attribute in MVC. In this article, we will see how we can use the HandleError attribute to quickly put together an exception handling mechanism that helps hide the ‘dark’ underbelly of your application but helps you debug with the same configuration. We will also look at a solution to handle 404 errors gracefully.

Overall we try to achieve the following on IIS 7+

Goal 1 - Use MVCs HandleError action filter to take care of Unhandled/Unexpected errors
Goal 2- See only user friendly messages at Runtime
Goal 3 - See exception stack traces at debug time
Goal 4- Manage 404 error with a proper error message
Goal 5 - Keep a 404 Response status

The HandleError Attribute

The HandleError attribute helps mark controller classes for ‘Unhandled Error’ exceptions. The HandleErrorAttribute() attribute filter will pipe the Unhandled exceptions to this attribute filter enabling you to take appropriate exception. The attribute filter is put in place by the default MVC template in the Global.asax (or for MVC4 in the App_Start\FilterConfig.cs) by adding the Action filter as follows:

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
  
{
      filters.Add(new HandleErrorAttribute());
  
}

By default, if you turn on CustomErrors, the behavior of the HandleErrorAttribute is to redirect you to the default Error page.
Setting up the Custom Errors page
Setup of the Custom Errors page is easy. We simply turn on the CustomErrors in the web.config.

custom-errors-on

To Test the setting we throw an exception in the ‘About’ action method of the Home Controller


public ActionResult About()
  
{
  
   ViewBag.Message = "Your app description page.";
  
  throw new ApplicationException("Testing default ErrorHandler attribute");
  
    //return View();
  
}
Now when we run the application and click on ‘About’ we see the following
exception-default-500

This is the default output from Errors.cshtml in the Shared folder of a default MVC project. As we can see, it returns a correct 500 status message.

At this stage, we have achieved the first and second goals we set out to achieve. However we have lost the stack trace.

Let us fix that without loosing out on the current behavior.

Updating Error Page to show stack trace

- Open the Error.cshtml and update it as follows

error-page-update-1

This change will ensure we see a stack trace. But as of now, this is no better than the Yellow Screen of Death because End Users are going to see it as well. We have lost our second goal of presenting User Friendly error messages.

To fix that problem, we use the Request.IsLocal property that tells us if the Request is coming from the Local machine or a Remote machine.

error-local-and-remote

With this change we have our goals, 1,2 and 3 covered. On to 404 Handling!


Handling 404 Errors

404 Errors are a little trickier to handle in ASP.NET MVC. We will walk through the following steps to nail all kinds of 404 errors in our application

Step 1: To start off with, we will update our Web.config to route 404 errors to a different view called ‘FailWhale’

web-config-404

Step 2: Add an empty View in the Shared folder and call it FailWhale.cshtml

Step 3: Add an empty Controller called ErrorController and update it with an action method as follows

error-controller

We are updating the Response.StatusCode to 404 so that robots scanning the URL get the 404.
  • We are setting the TrySkipIisCustomErrors = true so that IIS doesn’t try to hijack the 404 and show it’s own error page. Without this, when remote users try to navigate to an invalid URL they will see the IIS 404 error page instead of your custom FailWhale page.
  • Finally we are returning the default “FailWhale” view.
Step 4: Update the Routes (RouteConfig.cs in MVC4) to send the 404 error view to the Error Controller

global-asax-fail-whale-route

Logging the 404 error

The 404 error is not available in the above ErrorController because ASP.NET has already handled the error, bubbled it up to the routing framework that is using the web.config setting to route the request to the ‘redirected page’. Essentially it is a 302 redirection. To log the error, we have to handle the global Application_Error event and update the Global.asax as follows

global-application-exception

Logging other Errors

To log all other errors centrally, we have two options

Option 1: Overriding the ErrorAttribute can create a custom ErrorAttribute class like HandleAndLogErrorAttribute and registering it as the Error Attribute Filter as shown below

handle-and-log-error-attribute

register-custom-error-attribute

Option 2: Create a base Controller type and override the OnException method and Log there. Then make sure all your controllers derive from this custom controller type. This is probably a roundabout way of doing things. Managing a Loggable ErrorAttribute keeps the responsibility of logging with the dedicated ErrorLogging filter.

Putting it together and testing it out

To test it out we need the following errors

1. A generic unhandled exception to test out the ErrorHandler attribute filter

2. A 404 because of an invalid controller

3. A 404 because of an invalid action on a valid controller

4. A 404 thrown because of a server side result like Id not found

For each of these, we setup the Index.cshtml with additional Action links as follows

invalid-action-links

As seen above, the First link points to a non existent Controller called ‘UnHandledError’

The Second link points to a valid controller Home but invalid action called ‘InvalidAction’

The Third link point to a valid controller and Action method called ServerSide404 to which it passes the Id value 99.

Finally we have the About link, for which we will manipulate the Action method to throw an exception that we will not handle and let it propagate all through the stack.

The Controller code looks as follows

server-side-404

Now if we I publish the site on IIS, the Home Page looks as follows

home-on-iis

Clicking on any of the ‘Invalid Controller’, ‘Invalid Action’ and ‘Invalid Values’ node will take us to the ‘Fail Whale Page’ like below:

fail-whale-local

Navigating to the same page from a remote machine will show us the same error

fail-whale-remote

More interestingly if we click ‘About’ we get different content based on where we are calling it. The local page shows the stack trace whereas the remote file only shows a message.

error-remote

Unhandled exception as seen on a browser in a remote machine.

error-local

Same unhandled error as seen on the browser in local/development machine.

Conclusion

Managing unhandled exceptions in Web Applications gracefully adds a level of polish to any app. ASP.NET MVC comes with a barebones setup out of the box via the HandleError action filter. In this article, we explored how to leverage this filter as well as one (of many) technique to handle HTTP 404 (not found) errors gracefully.

Even better, the configuration is seamless and we need to do any setting change from Dev to Production.

Download the code here




13 comments:

Anonymous said...

For custom error setup in the beginning, why not say:
<customErrors mode="RemoteOnly" ...

This way, I can still see yellow page in local.

Steve said...

I agree with the Anonymous comment above - surely you are better off defining the different local/remote behaviour in a config file than in code..?

Anonymous said...

This gives a 302 on /badpath/ and a 404 on /FailWhale/?aspxerrorpath=/badpath/. Is there a way to keep the URL at /badpath/ so search engines get a 404 for /badpath/?

Anonymous said...

Hi,

Thanks for writing this post. I learned a lot, however I'm not sure where to place the option 1 code for handling the other type of errors. By the way I checked your zip files. Please can you explain?

Mike Strother said...

Very useful article. Thank you for writing this up.

Anonymous said...

Excellent article. This is exactly what I was looking for and very well explained. Much appreciated.

upile said...

Thanks alot, very helpfull article

upile said...

Hi i tried your code but its giving me a runtime Error,
Description: An exception occurred while processing your request. Additionally, another exception occurred while executing the custom error page for the first exception. The request has been terminated.

do you have any idea why? please help

SwastikMS said...

In my case 404 error handling is working fine.But in the URL it always shows the Error404 i.e., my action method/view name before any other action method/view name for example Create as follows:

http://localhost:50697/Error404/Create
http://localhost:50697/Error404/Edit/2063

Ukjim said...

You have mistakes in code.

The links added to the menu options (in _Layout.cshtml) have the ActionName and ControllerName parameters the wrong way round.

Also is there supposed to be a corresponding view for the ServerSide404 method created in HomeController?

And you say nothing (other than to setup an Empty view) about the extra content in FailWhale.cshtml which can only be found by looking at the GIT source.

Otherwise it was an interesting learning experience, thanks!

Sebastián Rojas said...

Custom route creation is not necessary, you just need to leave the Web.config like this:





...

Sebastián Rojas said...

Custom route creation is not necessary, you just need to leave the Web.config error tag attribute redirect like this redirect="~/Error/FailWhale" instead of redirect="FailWhale"

Unknown said...

Spent all day trying to get nice custom error messages into our web application.
When i stumbled onto this article.
I have implemented your article's suggestions and now it works the way I intended.

Thanks for the great article!