Handle JSON Exceptions Gracefully in ASP.NET MVC 2

Posted by: Malcolm Sheridan , on 4/21/2010, in Category ASP.NET MVC
Views: 57768
Abstract: The following article demonstrates how to handle exceptions gracefully when calling Ajax methods using ASP.NET MVC 2.
One thing to keep in mind when using ASP.NET MVC is how to gracefully handle exceptions that are thrown when executing Ajax requests. Normally if an error is thrown on the server, without proper error handling, the client will never know about the error. Another thing I wanted to do was not to litter my action methods with try/catch/finally blocks. They're good, but I want my action methods to look elegant! By the end of this article, you'll be able to decorate your action method's that return a JsonResult with the following attribute:

 

C#
[HttpGet]
[HandleJsonException]       
public JsonResult ThrowAjaxError()
{        
       throw new Exception("An error has occured in this method");
}
 
VB.NET (Converted)
 
<HttpGet, HandleJsonException> _
Public Function ThrowAjaxError() As JsonResult
      Throw New Exception("An error has occured in this method")
End Function
 
The thing I really love about ASP.NET MVC is the ability to use ActionFilters.  These give you the ability to perform different actions on the HTTP request before or after the action is executed. By making the most of ActionFilters, your code will be more readable instantly. 
Ok let's get started! I'm using ASP.NET MVC 2 and Visual Studio 2010. If you haven't got it you can download it from here. The one thing you need to do when creating your own custom action filter is your class needs to derive from ActionFilterAttribute. After that you need to override the method you want to use. Here's the complete code:
C#
public class HandleJsonExceptionAttribute : ActionFilterAttribute
{
       public override void OnActionExecuted(ActionExecutedContext filterContext)
       {           
              if (filterContext.HttpContext.Request.IsAjaxRequest() && filterContext.Exception != null)
              {  
                     filterContext.HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
                     filterContext.Result = new JsonResult()
                     {
                           JsonRequestBehavior = JsonRequestBehavior.AllowGet,
                                  Data = new
                                  {
                                         filterContext.Exception.Message,
                                         filterContext.Exception.StackTrace
                                  }
                     };
                     filterContext.ExceptionHandled = true;
             }
       }       
}
VB.NET (Converted)
 
Public Class HandleJsonExceptionAttribute
      Inherits ActionFilterAttribute
      Public Overrides Sub OnActionExecuted(ByVal filterContext As ActionExecutedContext)
               If filterContext.HttpContext.Request.IsAjaxRequest() AndAlso filterContext.Exception IsNot Nothing Then
                              filterContext.HttpContext.Response.StatusCode = CInt(Fix(System.Net.HttpStatusCode.InternalServerError))
                              filterContext.Result = New JsonResult() With {.JsonRequestBehavior = JsonRequestBehavior.AllowGet, .Data = New With {Key filterContext.Exception.Message, Key filterContext.Exception.StackTrace}}
                              filterContext.ExceptionHandled = True
               End If
      End Sub
End Class
 
Ok let's go through what's happening in the code above. I'm deriving from the ActionFilterAttribute class. This allows me to override the OnActionExecuted method. The only argument to this method is ActionExecutedContext, which holds everything I need to know about the request:
 
C#
 
public override void OnActionExecuted(ActionExecutedContext filterContext)
 
VB.NET (Converted)
 
public override void OnActionExecuted(ActionExecutedContext filterContext)
 
I only want this code to handle exceptions where the client is expecting a JSON result. The HttpRequestBase class has a nice method called IsAjaxRequest that does that for me:
 
C#
 
if (filterContext.HttpContext.Request.IsAjaxRequest() && filterContext.Exception != null)
 
VB.NET (Converted)
 
If filterContext.HttpContext.Request.IsAjaxRequest() AndAlso filterContext.Exception IsNot Nothing Then
 
Next I set the StatusCode to reflect an internal error:
 
C#
 
filterContext.HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
 
VB.NET (Converted)
 
filterContext.HttpContext.Response.StatusCode = CInt(Fix(System.Net.HttpStatusCode.InternalServerError))
 
The next line replaces the result of the action method. I'm instructing the action to return a JsonResult, but with the exception information:
 
C#
 
filterContext.Result = new JsonResult()
{
              JsonRequestBehavior = JsonRequestBehavior.AllowGet,
              Data = new
              {
                     filterContext.Exception.Message,
                     filterContext.Exception.StackTrace
              }
};
 
VB.NET (Converted)
 
filterContext.Result = New JsonResult() With {.JsonRequestBehavior = JsonRequestBehavior.AllowGet, .Data = New With {Key filterContext.Exception.Message, Key filterContext.Exception.StackTrace}}
 
Finally I let the request know I've handled the exception:
 
filterContext.ExceptionHandled = true;
 
To use this new attribute, I've created a new action called ThrowAjaxError. Obviously this is only a good naming convention for this article! I can decorate the action with the HandleJsonException attribute now:
C#
[HttpGet]
[HandleJsonException]       
public JsonResult ThrowAjaxError()
{        
       throw new Exception("An error has occurred in this method");
}
 
VB.NET (Converted)
 
<HttpGet, HandleJsonException> _
Public Function ThrowAjaxError() As JsonResult
      Throw New Exception("An error has occurred in this method")
End Function
 
In the view, I've got the following code to call this action via jQuery's Ajax function:
<form method="get" action="/Home/ThrowAjaxError" id="RunAjaxForm">
       <input type="submit" id="RunAjax" value="Run Ajax" />
    </form>       
   
    <script language="javascript" type="text/javascript">
        $(function () {
            $("#RunAjaxForm").submit(function (e) {
                e.preventDefault();
                $.ajax({
                    type: "GET",
                    url: this.action,
                    data: {},
                    dataType: "json",
                    success: function (msg) {
                        alert(msg);
                    },
                    error: function (XMLHttpRequest, textStatus, errorThrown) {
                        var msg = JSON.parse(XMLHttpRequest.responseText);
                        alert(msg.Message);
                    }
                });
            });
        });       
    </script>
 
In the script above, I'm hijacking the form's get request so I can post the form via Ajax. Normally when an exception is thrown in an action and the client is expecting JSON, you get no feedback. This is not the case thanks to the HandleJsonException attribute. The exception will be sent back to the client, and by using a library such as the open source JSON parser, I can parse the request and use it like any other JSON object.
 

This is one way of implementing this functionality. As with anything there are multiple ways of doing this, but it works for me. The entire source code of this article can be downloaded over here

Give me a +1 if you think it was a good article. Thanks!
Recommended Articles
Malcolm Sheridan is a Microsoft awarded MVP in ASP.NET, a Telerik Insider and a regular presenter at conferences and user groups throughout Australia and New Zealand. Being an ASP.NET guy, his focus is on web technologies and has been for the past 10 years. He loves working with ASP.NET MVC these days and also loves getting his hands dirty with jQuery and JavaScript. He also writes technical articles on ASP.NET for SitePoint and other various websites. Follow him on twitter @malcolmsheridan


Page copy protected against web site content infringement by Copyscape


User Feedback
Comment posted by Maxim Voronitskiy on Wednesday, April 21, 2010 11:58 AM
Cool! Thanks very much!
Comment posted by Burke on Thursday, April 22, 2010 11:39 PM
awesome! me love such articles on mvc
Comment posted by Seth Petry-Johnson on Monday, April 26, 2010 8:40 AM
I literally just implemented this same thing, usually pretty much the same concepts, 2 days ago. The only thing I did differently was override the controller's OnException method, rather than create a new attribute. If an error occurs and the request is an AJAX request then I return a JSON formatted error object, otherwise I delegate to the base exception handler (which likely bubbles it up to ASP.NET). This way I get the error handling for "free", without having to remember an attribute.

Did you identify a scenario in which an AJAX request might NOT want the error returned like this?
Comment posted by Wayne Brantley on Monday, April 26, 2010 5:20 PM
Why would you want to publish you exception details (message and call stack) to the browser?
Comment posted by Malcolm Sheridan on Tuesday, April 27, 2010 7:14 AM
@Wayne
Well you may not want to do that normally, but when you're debugging an application, this is a must.

@Seth
I haven't found a time when I didn't want to notify the client an error happened on the server.
Comment posted by Cristovão on Friday, May 14, 2010 1:27 PM
And if we use JSONP?
Comment posted by Malcolm Sheridan on Monday, May 17, 2010 6:26 AM
@Cristovão
Give it a try.
Comment posted by Acauan on Tuesday, July 20, 2010 3:37 PM
Thank you very much! Just a simple copy/paste of your code solved my problem!
Comment posted by Jhonny on Thursday, January 6, 2011 9:09 AM
Great article, it solved my exception handling error
Comment posted by Ben on Wednesday, April 6, 2011 10:03 AM
What about Model errors?
Comment posted by Alex on Wednesday, March 28, 2012 3:59 PM
You may want to add this to your code to prevent problems:
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
Comment posted by Fernando on Thursday, November 8, 2012 12:24 PM
Nice dude. This help me a lot.

Congratulations. Greetings from Brazil.

Post your comment
Name:  
E-mail: (Will not be displayed)
Comment:
Insert Cancel