120

How do I handle exceptions thrown in a controller when jquery ajax calls an action?

For example, I would like a global javascript code that gets executed on any kind of server exception during an ajax call which displays the exception message if in debug mode or just a normal error message.

On the client side, I will call a function on the ajax error.

On the server side, Do I need to write a custom actionfilter?

Shawn Mclean
  • 56,733
  • 95
  • 279
  • 406
  • 8
    See [beckelmans post](http://beckelman.net/post/2010/03/18/Handling-Errors-During-Ajax-Calls-With-ASPNET-MVC.aspx) for a good example. Darins answer to this post is good but don't set the correct status code for an error. – Dan Oct 11 '11 at 09:50
  • 6
    Sadly that link is now broken – Chris Nevill Oct 07 '14 at 10:09
  • 1
    Here is the link on wayback machine: https://web.archive.org/web/20111011105139/http://beckelman.net/post/2010/03/18/Handling-Errors-During-Ajax-Calls-With-ASPNET-MVC.aspx – BruceHill Jun 27 '16 at 12:14

6 Answers6

164

If the server sends some status code different than 200, the error callback is executed:

$.ajax({
    url: '/foo',
    success: function(result) {
        alert('yeap');
    },
    error: function(XMLHttpRequest, textStatus, errorThrown) {
        alert('oops, something bad happened');
    }
});

and to register a global error handler you could use the $.ajaxSetup() method:

$.ajaxSetup({
    error: function(XMLHttpRequest, textStatus, errorThrown) {
        alert('oops, something bad happened');
    }
});

Another way is to use JSON. So you could write a custom action filter on the server which catches exception and transforms them into JSON response:

public class MyErrorHandlerAttribute : FilterAttribute, IExceptionFilter
{
    public void OnException(ExceptionContext filterContext)
    {
        filterContext.ExceptionHandled = true;
        filterContext.Result = new JsonResult
        {
            Data = new { success = false, error = filterContext.Exception.ToString() },
            JsonRequestBehavior = JsonRequestBehavior.AllowGet
        };
    }
}

and then decorate your controller action with this attribute:

[MyErrorHandler]
public ActionResult Foo(string id)
{
    if (string.IsNullOrEmpty(id))
    {
        throw new Exception("oh no");
    }
    return Json(new { success = true });
}

and finally invoke it:

$.getJSON('/home/foo', { id: null }, function (result) {
    if (!result.success) {
        alert(result.error);
    } else {
        // handle the success
    }
});
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • 1
    Thanks for this, The latter was what I was looking for. So for the asp.net mvc exception, is there a specific way I need to throw it so it can be caught by the jquery error handler? – Shawn Mclean Jan 16 '11 at 20:16
  • 1
    @Lol coder, no matter how you throw an exception inside the controller action the server will return 500 status code and the `error` callback will be executed. – Darin Dimitrov Jan 16 '11 at 20:22
  • 1
    Wouldn't a Status code of 500 be kind of wrong? To quote this chap http://broadcast.oreilly.com/2011/06/the-good-the-bad-the-ugly-of-rest-apis.html : "Failing to realize that a 4xx error means I messed up and a 5xx means you messed up" - where I is the client and you is the server. – Chris Nevill Oct 07 '14 at 10:07
  • This answer still valid for the newer versions of ASPNET? – gog May 29 '15 at 17:36
  • @DarinDimitrov I preferred the second method, but I could not find neither the exception type, nor a firendly message i.e. constraint error in **filterContext**. So, is it possible to receive these error details from filterContext or etc? – Jack Sep 20 '16 at 18:27
  • @When testing the second code using throw ne Exception(), I encounter "null reference exception" error on OnException. Is this normal? How can I test the second approach (**MyErrorHandlerAttribute**) by raising a mock exception? – Jack Sep 22 '16 at 13:50
  • Which one is preferable? – Gergely Bakos Feb 27 '20 at 12:02
76

After googling I write a simple Exception handing based on MVC Action Filter:

public class HandleExceptionAttribute : HandleErrorAttribute
{
    public override void OnException(ExceptionContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsAjaxRequest() && filterContext.Exception != null)
        {
            filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
            filterContext.Result = new JsonResult
            {
                JsonRequestBehavior = JsonRequestBehavior.AllowGet,
                Data = new
                {
                    filterContext.Exception.Message,
                    filterContext.Exception.StackTrace
                }
            };
            filterContext.ExceptionHandled = true;
        }
        else
        {
            base.OnException(filterContext);
        }
    }
}

and write in global.ascx:

 public static void RegisterGlobalFilters(GlobalFilterCollection filters)
 {
      filters.Add(new HandleExceptionAttribute());
 }

and then write this script on the layout or Master page:

<script type="text/javascript">
      $(document).ajaxError(function (e, jqxhr, settings, exception) {
                       e.stopPropagation();
                       if (jqxhr != null)
                           alert(jqxhr.responseText);
                     });
</script>

Finally you should turn on custom error. and then enjoy it :)

Leniel Maccaferri
  • 100,159
  • 46
  • 371
  • 480
Arash Karami
  • 632
  • 1
  • 8
  • 19
  • I can see the error in Firebug butIt is not redirecting to Error page.? – user2067567 Apr 26 '13 at 14:20
  • 1
    Thanks for this! should be marked as the answer IMO as its filtering on ajax requests and inherits the correct class rather than what the HandleErrorAttribute inherits – m.t.bennett Apr 29 '13 at 05:22
  • 1
    I think the "Request.IsAjaxRequest()" is not that reliable sometimes. – Will Huang Apr 06 '16 at 06:35
  • For debug configuration it always works but not working always in Release configuration & returns html instead anybody have workaround for such case? – Hitendra Sep 27 '16 at 13:40
9

Unfortunately, neither of answers are good for me. Surprisingly the solution is much simpler. Return from controller:

return new HttpStatusCodeResult(HttpStatusCode.BadRequest, e.Response.ReasonPhrase);

And handle it as standard HTTP error on client as you like.

alehro
  • 2,198
  • 2
  • 25
  • 41
4

I did a quick solution because I was short of time and it worked ok. Although I think the better option is use an Exception Filter, maybe my solution can help in the case that a simple solution is needed.

I did the following. In the controller method I returned a JsonResult with a property "Success" inside the Data:

    [HttpPut]
    public JsonResult UpdateEmployeeConfig(EmployeConfig employeToSave) 
    {
        if (!ModelState.IsValid)
        {
            return new JsonResult
            {
                Data = new { ErrorMessage = "Model is not valid", Success = false },
                ContentEncoding = System.Text.Encoding.UTF8,
                JsonRequestBehavior = JsonRequestBehavior.DenyGet
            };
        }
        try
        {
            MyDbContext db = new MyDbContext();

            db.Entry(employeToSave).State = EntityState.Modified;
            db.SaveChanges();

            DTO.EmployeConfig user = (DTO.EmployeConfig)Session["EmployeLoggin"];

            if (employeToSave.Id == user.Id)
            {
                user.Company = employeToSave.Company;
                user.Language = employeToSave.Language;
                user.Money = employeToSave.Money;
                user.CostCenter = employeToSave.CostCenter;

                Session["EmployeLoggin"] = user;
            }
        }
        catch (Exception ex) 
        {
            return new JsonResult
            {
                Data = new { ErrorMessage = ex.Message, Success = false },
                ContentEncoding = System.Text.Encoding.UTF8,
                JsonRequestBehavior = JsonRequestBehavior.DenyGet
            };
        }

        return new JsonResult() { Data = new { Success = true }, };
    }

Later in the ajax call I just asked for this property to know if I had an exception:

$.ajax({
    url: 'UpdateEmployeeConfig',
    type: 'PUT',
    data: JSON.stringify(EmployeConfig),
    contentType: "application/json;charset=utf-8",
    success: function (data) {
        if (data.Success) {
            //This is for the example. Please do something prettier for the user, :)
            alert('All was really ok');                                           
        }
        else {
            alert('Oups.. we had errors: ' + data.ErrorMessage);
        }
    },
    error: function (request, status, error) {
       alert('oh, errors here. The call to the server is not working.')
    }
});

Hope this helps. Happy code! :P

Daniel Silva
  • 817
  • 8
  • 16
4

In agreement with aleho's response here's a complete example. It works like a charm and is super simple.

Controller code

[HttpGet]
public async Task<ActionResult> ChildItems()
{
    var client = TranslationDataHttpClient.GetClient();
    HttpResponseMessage response = await client.GetAsync("childItems);

    if (response.IsSuccessStatusCode)
        {
            string content = response.Content.ReadAsStringAsync().Result;
            List<WorkflowItem> parameters = JsonConvert.DeserializeObject<List<WorkflowItem>>(content);
            return Json(content, JsonRequestBehavior.AllowGet);
        }
        else
        {
            return new HttpStatusCodeResult(response.StatusCode, response.ReasonPhrase);
        }
    }
}

Javascript code in the view

var url = '@Html.Raw(@Url.Action("ChildItems", "WorkflowItemModal")';

$.ajax({
    type: "GET",
    dataType: "json",
    url: url,
    contentType: "application/json; charset=utf-8",
    success: function (data) {
        // Do something with the returned data
    },
    error: function (xhr, status, error) {
        // Handle the error.
    }
});

Hope this helps someone else!

Rymnel
  • 4,515
  • 3
  • 27
  • 28
0

For handling errors from ajax calls on the client side, you assign a function to the error option of the ajax call.

To set a default globally, you can use the function described here: http://api.jquery.com/jQuery.ajaxSetup.

Brian Ball
  • 12,268
  • 3
  • 40
  • 51
  • An answer I gave over 4 years ago suddenly gets a down vote? Anyone care to give a reason why? – Brian Ball Jul 27 '15 at 11:48
  • 1
    Contact SOF and ask their DBA to query who gave the down vote. Next, message that individual so they can explain. Not just anyone can give a reason why. – JoshYates1980 Jan 29 '16 at 15:44