ASP.NET MVC exception handling, custom error pages and logging
I will continue our walk through the MVC’s world and today we will review how to manage our exceptions in MVC application.
Nobody wants to see the famous “yellow screen” of death page, when working in his application. It is getting bothering whilst working to get an ugly exception screen and either to refresh your current page or to use “back” navigation button of your browser. In addition I would like to note that nowadays just doing exception is not enough. Critical problems that occur in our code should be traced and persisted, because this information might be crucial and it is stateless if we decide to do nothing with it.
In classic ASP.NET you could manage your exceptions on page level, central lever through global.asax, or you can even do it via http module.
Well ASP MVC, can handle easily exceptions too. And it is very powerful. We will review how to use and explore what the framework gives and in addition will create our own Exception attribute and we will use log.net (which is pretty popular) for logging our exceptions in text file.
HandleError attribute in MVC can be applied to both controllers and action methods.
Let’s see what this attribute gives us.
I will add a method in my Home controller that will cause and exception and will handle this by various ways.
In Master page I will refer my method by adding next line of code:
<%= Html.ActionLink("Cause exception", "DoSomething", "Home") %>
If you take a look of controllers that are created with MVC template (HomeController.cs, AccountController.cs), you will see that they come for us with [HandleError] attribute applied to their definition.
In order to make HandleError attribute working for you (locally), go to the web.config and change the settings related to custom errors:
Now we need to provoke an exception, just to check what this HandleError attribute does for us.
In the sample action method I will cause DevideByZero exception.
public ActionResult DoSomething()
{
var j = 0;
var i = 5 / j;
return View("Index");
}
Screenshot below demonstrates very clear what the result is.
What we see, is actually the Error.aspx page placed in Views\Shared. When you provide only the HandleError attribute to your class (or to your action method), then when an unhandled exception occurs MVC will look for a corresponding View named "Error" first in the Controller's View folder. In our case we don’t have such one, so MVC ends up searching of view with this name in Shared folder.
If we turn of the custom error attribute by setting mode=”Off” we will see the “yellow page”.
HandleError attribute allows even more. It provides functionality to handle specific exception types and to visualize dedicated pages that can serve your action methods.
By adding a new view called “Test.aspx” in Views\Shared and declaring HandleError for our DoSomething action method, we supply dedicated page for displaying on exception in the controller method.
Important thing to note is that if we want both controller and action methods to be served by HandleError attribute, we should set Order = 0 to attribute of the controller. Otherwise it takes over and overrides the HandleError of action methods if no Order is set for their HandleError attributes.
The Order property of the HandleErrorAttribute attribute helps determine which HandleErrorAttribute filter is used to handle an exception. You can set the Order property to an integer value that specifies a priority from -1 (highest priority) to any positive integer value. The greater the integer value is, the lower the priority of the filter is.
Bear in mind that filters with the same order number are applied in an undetermined order.
In addition we can specify what exception type to handle the HandleError attribute.
[HandleError(View="Test", ExceptionType=typeof(DivideByZeroException))]
If another type of exception occurs, then exception will be handled by HandleError attribute of the controller (if such been set).
If you want one action method to be served by different pages on exception, or one page to server different exception types for an action method you should apply as many HandleError attributes as you want.
Another approach for handling your exceptions on controller level is by overriding the virtual method OnExcetion.
protected virtual void OnException(ExceptionContext filterContext); defined in System.Web.Mvc.Controller class.
If overriden, this method is called whenever exception occurs in an action method in the controller. It doesn’t rely on your HandleError attribute and execution of OnException method will take over. So, this could be one centralized place for handling your controllers exceptions.
This approach is much more straightful and powerful. It gives you one central place, where you can manage all unhandled exceptions and decide what to do with this information – email it, log it, store it, serve it to the user in friendly message, etc..
Since HandleError attribute just outputs a view and doesn’t let you run any code, it this not the ultimate solution for the exception management. It is still good from the perspective of the end user, who works with your application. But, do you really think this is the right way to do? How are you going to investigate a problem that occurred a day ago on your client’s production environment? Only by the memories of the users who faced the issue? Honestly I wouldn’t rely on this.
That is why using OnException method in my opinion, is the right way for exception management in ASP.NET MVC. You can implement a logic that perfectly fits your custom needs for tracking all unhandled issues.
We reviewed how we can do exception management, but we didn’t cover one very common scenario. What If I want to have a set of certain action attributes which should be treated in different manner on exception? Well, obviously I can do this in mine overriden OnException by inspecting the route data. But, If I have many action methods I may end up with a very big switch statement (for example). And I can go a bit futher, what if I expect to have very very specific exceptions that I don’t want at all to treat in the same place with all others? What if I want to render a custom action result if specific unhandled exception occurs in mine action method?
In this case, we should go for creating custom exception attribute and to apply it to the methods we need.
Just for the sake of the demo, let’s create custom attribute for logging exceptions with log4net.
After we download and refer log4net.dll in our project we need slight modifications in order to make it work and log for us.
Configure log4net in Global.asax :
In order to create a custom exception attribute we need to inherit FilterAttribute and to implement IExceptionFilter interface.
Exception filters run after all other action filters and result filter have been executed. This approach allows us to replace the action method's action result with a custom ActionResult, to redirect user to an error action method that can serve user friendly result or just to do logging as we do.
The last thing that left is to use the CustomExcetionAttribute.
If OnException method is overridden in the controller class, bear in mind that it will be executed prior to our custom exception method.
If we add next statement block, our custom exception attribute will never log a message since it is already been handled in BaseController OnException method.
if (filterContext.ExceptionHandled)
return;
So, be careful when you use such validations.
To download the source code of the example i built refer:
source code
Great article, covers all bases! Waiting for the next one ;)
ReplyDelete