Gal Segal's Blog

Thoughts of a programmer with a soul

Feb 28

ASP.NET MVC Global Error Filter Attribute

Tags: ,

Wonderful_Error_by_XxFiddleSticksxX

ASP.NET MVC3 introduced the global filters, allowing developers to register a filter across all controllers and action once – typically in “Application Start” event in the Global.asax.  This feature can be used to perform a “catch all” place for all application errors.

Update 08/03/2011

I rearranged the code for regular redirection. Now I am not using the “View” property of the “HandleError” attribute, but a route I created for the error pages.

A word about error handling:

I like to have one place where I handle most, if not all, errors in my web application. It can be the “Application Error” event or a global error filter.  I also try to avoid using the “try catch” routine and look at the action performed in the application from a different perspective, a more atomic one.

When I need to perform an action on my app, I ask myself: what will happen if this action will fail? If an exception will occur and the process will stop?

Sometimes you need to perform a complex action that involves few inner procedures, like querying the database, process the results, create new object etc. Many times, if one procedure fails, the whole process has to be stopped and could not produce the desired outcome. In cases like this I tend to avoid catching the errors, and let the process to crash.

This approach, of crashing quickly, as its benefits: you immediately know that something is wrong, and where it happened. You can also be sure that if the action performed successfully, the data that is displayed is accurate. So we get 2 birds with one stone: good data on success and quick analysis of the problem when something goes wrong.

Of course, there are times where it is not crucial to stop everything when an error occurs, and this is the place for the “try catch” phase.

Another aspect of this approach is the underlying layer of the IL: placing a lot of  “try catch” gives more instructions to the IL layer. It is a minor thing, but in the world of optimization, it makes a difference if a process has one or ten lines of this kind.

In ASP.NET MVC we have the “HandleError” attribute, and if we register it globally, it can be the “sink”  for our error handling.

Few things I want to achieve from this handler:

  1. Logging
  2. Falling back gracefully according the the nature of the request
  3. Sufficient data about the error, when needed.

I tend to differentiate between ajax calls and regular requests: regular requests that fail should be redirected to an error page, while ajax calls should return an error with some text describing the problem.

So, this is the handler:

#region Private Members

 private ILogger _logger;
 private Exception _serverException;
 private Exception _baseException;
 private const string GENERAL_ERROR_ROUTE = "GeneralError";
 private const string PAGE_NOT_FOUND_ROUTE = "PageNotFound";

 #endregion

 #region Methods

 #region Protected

 public override void OnException(ExceptionContext filterContext)
 {
 _serverException = filterContext.Exception;
 _logger = ContainerAccessor.Container.Resolve<ILogger>();

 //log
 LogErrorRecursive(_serverException);

 //handle ajax
 if (filterContext.HttpContext.Request.IsAjaxRequest() &&
 filterContext.Exception != null)
 {
 HandleAjaxRequestError(filterContext);
 }
 //handle regular requsts
 else
 {
 RedirectToErrorPage(filterContext);
 }
 filterContext.ExceptionHandled = true;
 base.OnException(filterContext);
 }

 #endregion

 #region Private

 /// <summary>
 /// Logs exception recursivty, starting with the inner most exception
 /// </summary>
 private void LogErrorRecursive(Exception ex)
 {
 if (ex.InnerException != null)
 {
 LogErrorRecursive(ex.InnerException);
 }
 else
 {
 _baseException = ex;
 }
 _logger.Log(eLogType.Error, "Application Error: ", ex);
 }

 /// <summary>
 /// Handles errors on ajax requests - returns internal server error and error message to caller
 /// </summary>
 private static void HandleAjaxRequestError(ExceptionContext filterContext)
 {
 filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
 filterContext.HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
 filterContext.Result = new JsonResult()
 {
 JsonRequestBehavior = JsonRequestBehavior.AllowGet,
 Data = new
 {
 filterContext.Exception.Message
 }
 };
 }

 /// <summary>
 /// redirects to error page based on internal server error
 /// (authorization, page not found and general error)
 /// </summary>
 private void RedirectToErrorPage(ExceptionContext filterContext)
 {
 string errorRoute = string.Empty;
 if (_baseException.GetType() == typeof(HttpException))
 {
 var httpException = (HttpException)_baseException;
 switch (httpException.GetHttpCode())
 {
 case 404:
 errorRoute = PAGE_NOT_FOUND_ROUTE;
 break;
 default:
 errorRoute = GENERAL_ERROR_ROUTE;
 break;
 }
 }
 else
 {
 errorRoute = GENERAL_ERROR_ROUTE;
 }
 filterContext.Result = new RedirectToRouteResult(errorRoute, null);
 }

 #endregion

 #endregion

Getting the error in ajax calls:

$.ajax({
 url: '...',
 type: 'POST',
 success: function (data) {

 },
 error: function (jqXHR, textStatus) {
 var msg = JSON.parse(jqXHR.responseText);
 console.log("Error Message: " + msg.Message);
 console.log("Status Code: " + msg.StatusCode);
 }
});

Happy coding :)

Back to top
  • http://www.facebook.com/people/Branimir-Bzenić/1316354468 Branimir Bzenić

    Hello Gall, is there any ability , if error occur on post action method, to hold user on current page and somehow append additional header to the top of the current page? Thank you in advance.

    • http://gal-segal.com Gal Segal

      Hi Branimir,
      you can try:
      filterContext.HttpContext.Response.Headers.Add(“some key”, “some value”);

  • http://virtser.net/blog/ David Virtser

    etoro.log ah? :)