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.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
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
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.
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 applicationStep 1: To start off with, we will update our Web.config to route 404 errors to a different view called ‘FailWhale’
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
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.
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
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
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
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
Now if we I publish the site on IIS, the Home Page looks as follows
Clicking on any of the ‘Invalid Controller’, ‘Invalid Action’ and ‘Invalid Values’ node will take us to the ‘Fail Whale Page’ like below:
Navigating to the same page from a remote machine will show us the same error
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.
Unhandled exception as seen on a browser in a remote machine.
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
Tweet
13 comments:
For custom error setup in the beginning, why not say:
<customErrors mode="RemoteOnly" ...
This way, I can still see yellow page in local.
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..?
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/?
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?
Very useful article. Thank you for writing this up.
Excellent article. This is exactly what I was looking for and very well explained. Much appreciated.
Thanks alot, very helpfull article
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
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
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!
Custom route creation is not necessary, you just need to leave the Web.config like this:
...
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"
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!
Post a Comment