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?

3

6 Answers 6

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
    }
});
Sign up to request clarification or add additional context in comments.

7 Comments

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?
@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.
Wouldn't a Status code of 500 be kind of wrong? To quote this chap broadcast.oreilly.com/2011/06/… : "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.
This answer still valid for the newer versions of ASPNET?
@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?
|
75

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 :)

4 Comments

I can see the error in Firebug butIt is not redirecting to Error page.?
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
I think the "Request.IsAjaxRequest()" is not that reliable sometimes.
For debug configuration it always works but not working always in Release configuration & returns html instead anybody have workaround for such case?
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.

2 Comments

@Will Huang:the name of the exception instance
I have to cast the first argument to int. Also, when I do this, the result is passed to the ajax success handler, and not the error handler. Is this the expected behavior?
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

Comments

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!

Comments

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.

2 Comments

An answer I gave over 4 years ago suddenly gets a down vote? Anyone care to give a reason why?
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.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.