Elegantly Handling AJAX Errors in ASP.NET MVC using jQuery
One issue I've always struggled with when using AJAX is how to handle server side errors. I would usually wind up writing a bunch of code that would return some type of response code to indicate what happened on the server. As you can imagine this is incredibly tedious, messy, and horrific to maintain. Tonight as I was working on a project I came up with an elegant way of handling such scenarios that I thought I would share.
To begin, I always inherit all of my controllers from a custom base controller class. Within that base controller I added a method and event, shown below:
protected JsonResult AJAXError(string message)
{
if (String.IsNullOrEmpty(message))
message = "Oops! Something went wrong while processing your request.";
Response.StatusCode = 500;
return Json(new { ErrorMessage = message });
}
protected override void OnException(ExceptionContext filterContext)
{
if (Request.IsAjaxRequest())
{
filterContext.Result = AJAXError(filterContext.Exception.Message);
filterContext.ExceptionHandled = true;
}
base.OnException(filterContext);
}
The OnException event catches any exceptions that are thrown during the execution of controller actions. Within this event, we first check to see if the exception occurred during an AJAX request. If so, we set the result to the return value of our AJAXError method. The AJAXError method only does two things, it sets the StatusCode to 500 (Internal Error) so that the AJAX request shows itself to be an error and returns a JSON object with an ErrorMessage property that contains the current error.
Something else to keep in mind is that often times when a site is public, you won't want all of the details of the error being published to the user. This opens up a potential security risk if critical information is leaked out via errors. In my code (not shown above), I account for checking to see if the site is in production use or in debug and if so change the error message to a default error. This will prevent unwanted information from leaking out.
Onward!
The second part of the solution comes with the help of jQuery's global ajaxError event. Within a client-side javascript include, I have the following code:
$(document).ready(function() {
$(document).ajaxError(function(event, XMLHttpRequest, ajaxOptions, thrownError) {
var result = $.secureEvalJSON(XMLHttpRequest.responseText);
ShowDialog(result.ErrorMessage, "Oops! There was an error.");
});
});
jQuery provides the ajaxError event as a means to catch any errors that are fired from all AJAX requests on a page. Remember the JSON object we return from the server-side AJAXError method? It winds up in the XMLHttpRequest.responseText property as a JSON string. To make things easier, we need to convert that JSON string to a JSON object. Some people will suggest using the eval method to do this but I would urge strongly against that. Doing this could potentially allow an attacker to execute client-side code.
After a bit of research, I opted to use the jquery-json plugin (found here) to handle the conversion. The jquery-json library provides us with the $.secureEvalJSON method, allowing us to translate that JSON string into a JSON object.
Calling result.ErrorMessage will now give you the message that was set on the server side, allowing you to alert the user about the issue. The ShowDialog function above is simply a method I put together to display a jQuery Dialog Box rather than a plain alert.
I'm pretty happy with this solution because not only does it catch any unexpected exceptions on the server-side, but you can use the AJAXError method to return an error when doing data validation. For example, here is some code from one of my actions:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult UpdateUserDetails(FormCollection collection)
{
Contract.Requires(!String.IsNullOrEmpty(collection["username"]));
var profile = this.Users.GetUserProfile(collection["username"]);
if (profile == null)
return AJAXError("There was an issue updating user details. The users profile could not be found.");
/* Do some stuff... */
profile.Save();
return new EmptyResult();
}
If the users profile can not be found, we just return an AJAXError with the error details and that gets reflected on the client-side automagically.
Thursday, November 5, 2009 at 12:53AM |
Permalink
ASP.NET MVC,
Error Handling,
jQuery | in
Programming