Global Error Handler For AJAX Requests in MVC

Objective

For a large application we need to have a provision to handle AJAX request exceptions globally. Otherwise, it will be a tedious task to handle it in every AJAX request. This article is not intended for starters. I assume that people reading this have a good knowledge of MVC and have about 2 years of experience working with MVC.

Problem Statement

It is ideal to put some operations that are common to all AJAX requests through the application. Sometimes we also need to show a custom error message to let the user correct their actions. So it is always helpful to have a global error handler for all AJAX requests.

The Global AJAX Handler

jQuery provides many AJAX global handlers. These handlers are fired automatically if jQuery.ajaxSetup () property is set to true, that it is by default. To enable the global error handler we will use the .ajaxError handler.

If we want to call any controller action asynchronously, then we can either define an AJAX form action, or we can call it using a jQuery script. Both ways are useful.

Calling actions asynchronously


We can define an AJAX form action in the following way:

  1. @using (Ajax.BeginForm("FormulaAction""Home"new AjaxOptions  
  2. {  
  3.    UpdateTargetId = "FrmlEditForm",  
  4.    HttpMethod = "POST",  
  5.    OnBegin = "Base.showProgress",  
  6.    OnComplete = "Base.hideProgress"  
  7.    }, new { id = "frmAdd", autocomplete = "off" }  
  8. ))  
“FormulaAction” is the controller name and “Home” is the action name. We are setting some options here, like to show a progress indicator and that is the target view and so on. To learn more on this please check some tutorial on BeginForm ajax helper.

Another way is to call from the jQuery file. Suppose we have an editable data grid and we have provided the user to add/edit/delete data from the grid. On a “Delete” icon/button click we want to call any Action. For that we can define a script. The way I do that is I create one script per module/.csproj level and use it from the view. Let me try to provide an overview by giving a snippet of my project:

Namespace: Employee.EmployeeEntry.js

In the button onClick event I call this script:
  1. delRow: function (GridName) {  
  2.    $.ajaxSetup({  
  3.       cache: false  
  4.    });  
  5.    // Using a telerik MVC grid extension  
  6.    var grid = $(GridName).data('tGrid');  
  7.    var selectedrow = $(grid.$tbody[0]).find("tr.t-state-selected");  
  8.    var msgText;  
  9.    if (selectedrow.length == 0) {  
  10.       alert("Please select a row");  
  11.       return;  
  12.    }  
  13.    var row = selectedrow[0];  
  14.    var gridData = grid.data[$(grid.$tbody[0].rows).index(row)];  
  15.    var employee_uid = gridData.Employee_UID;  
  16.    var session_uid = gridData.SESSION_UID;  
  17.    $.ajax({  
  18.       url: '/ Employee/EmployeeEntry/DeleteEmpData',  
  19.       data: {  
  20.          "emp_uid": employee_uid,  
  21.          "session_uid": session_uid,  
  22.       },  
  23.       cache: false,  
  24.       beforeSend: function () {  
  25.       Base.showProgress();  
  26.    },  
  27.    success: function (data, textStatus, jqXHR) {  
  28.    Base.hideProgress();  
  29.    $(GridName).data('tGrid').rebind();  
  30. }, }) }),  
This will call the EmployeeEntry Controllers DeleteEmpdata action and will pass the Employee id and current session id.
  1. public ActionResult DeleteStgData(long EMP_UID, string SESSION_UID)  
  2. {  
  3.    // Delete action  
  4. }  
Setup Global error handler

To handle any ajax exception in the controller, we define a global error handler at the WebProject level. We created a script and then registered all error handlers there. Have a look at our global error handler:
  1. //register namespace  
  2. var Base = namespace("Base");  
  3. Base = {  
  4.    rootUrl: "/",  
  5.    /// Set Default Configuration for Application  
  6.    init: function (strRootURL) {  
  7.       //set root url  
  8.       this.rootUrl = strRootURL;  
  9.       //ajax call setup  
  10.       $.ajaxSetup({  
  11.          error: function (jqXHR, textStatus, errorThrown) {  
  12.          //Use Base handleError method  
  13.          try{  
  14.             Base.hideProgress();  
  15.             var msg = "";  
  16.             if (jqXHR.status === 0) {  
  17.                msg = 'Not connect. Verify Network.';  
  18.             } else if (jqXHR.status == 404) {  
  19.                msg = 'Requested page not found. [404]';  
  20.             } else if (jqXHR.status == 500) {  
  21.                msg = 'Internal Server Error [500].';  
  22.             }  
  23.             else if (jqXHR.status == 401) {  
  24.                location.href = "/";  
  25.                return;  
  26.             }  
  27.             else if (textStatus === 'parsererror') {  
  28.                msg = 'Requested JSON parse failed.';  
  29.             } else if (textStatus === 'timeout') {  
  30.                msg = 'Time out error.';  
  31.             } else if (textStatus === 'abort') {  
  32.                msg = 'Ajax request aborted.';  
  33.             } else {  
  34.             try  
  35.             {  
  36.                msg =JSON.parse(jqXHR.responseText);  
  37.             }  
  38.             catch(ers)  
  39.             {  
  40.                msg =jqXHR.responseText;  
  41.             }  
  42.          }  
  43.          Dialog.error({  
  44.             message: msg,  
  45.             title: "Error"  
  46.          });  
  47.       }  
  48.       catch (err)  
  49.       {  
  50.          Dialog.error({  
  51.             message: errorThrown,  
  52.             title: "Error"  
  53.          });  
  54.       }  
  55.       //hide loader  
  56.       Base.hideProgress();  
  57.    }  
  58. });  
Where Dialog is a custom class. You can use your own dialogue to show the error. See, you can handle any error that has occurred during the ajax request.

Propagating Custom error:

As you can see, currently 0,404,500 and 401 HTTP errors are defined. But if you want to specifically show any custom error message, in my example suppose the delete has failed for some reason, then in the controller action we can pass a custom HTTP Status and Error message to the Ajax Error handler so that the msg =JSON.parse(jqXHR.responseText); statement can be executed and the message is shown in an error dialogue box.
  1. Catch (Exception Ex)  
  2. {  
  3.    this.HttpContext.Response.StatusCode = 20;  
  4.    return new JsonResult()  
  5.    {  
  6.       JsonRequestBehavior = JsonRequestBehavior.AllowGet,  
  7.       Data = Ex.Message or “Custom message”  
  8.    };  
  9. }