0

In ASP.NET MVC 5 I make a client (JavaScript) ajax request, and in case of receiving an error message from the API, I want to send this message to the client

I am using error handling in the config file:

    <httpErrors errorMode="Custom" existingResponse="Replace">
      <remove statusCode="400"/>
      <remove statusCode="403"/>
      <remove statusCode="404"/>
      <remove statusCode="500"/>
      <error statusCode="400" path="/SystemPages/OtherError" responseMode="ExecuteURL"/>
      <error statusCode="403" path="/SystemPages/Login" responseMode="Redirect"/>
      <error statusCode="404" path="/SystemPages/NotFoundError" responseMode="ExecuteURL"/>
      <error statusCode="500" path="/SystemPages/InternalServerError" responseMode="ExecuteURL"/>
    </httpErrors>

And I have an error handling in the filter:

 public class ExceptionAttribute : HandleErrorAttribute
    {
        public override void OnException(ExceptionContext filterContext)
           ...

In JavaScript code, I am displaying an error if the response code does not match 2XX:

$.ajax({
    type: "POST",
    url: '/api/xxx',
    data: JSON.stringify({ ids: invoiceIds }),
    contentType: "application/json",
    success: function (data) {
        successToast("Success result ....bla bla bla.", "Success");
        window.location.reload(false);
    },
    error: function (XMLHttpRequest, textStatus, errorThrown) {
        let errorMessage = errorThrown;
        if (XMLHttpRequest.responseText != null && XMLHttpRequest.responseText != '') {
            errorMessage = XMLHttpRequest.responseText;
        }
        errorToast(errorMessage, "Error");
    }
});

The problem is this: if I receive an error from the API (for example, with a status code = 400), I can process it in the filter, and I want to send a server response to the client with the same error code and error text in the response body. But in this case (due to the error code = 400) the module httpError is triggered and inserts its view into the response. And I am losing the original error description text message.

Maybe you can
a) somehow stop the operation of the httpErrors module in a particular case, or
b) somehow pass the message I need to the controller code that is called by the httpErrors module?

Alex Muha
  • 11
  • 3
  • See these two links: https://www.c-sharpcorner.com/UploadFile/97fc7a/exception-handling-at-filter-level-using-iexceptionfilter-in/ and https://stackoverflow.com/questions/4707755/asp-net-mvc-ajax-error-handling – Meysam Asadi Jan 30 '21 at 14:23

3 Answers3

0

I also did not have any problems because I never received a page error with Ajax, but as far as I know it is not possible to manage browser errors in this case. I will give an example below and put a link for you at the end.

public class JsonData
{
    public string HtmlMsg { get; set; }
    public string HtmlBody { get; set; }
    public bool Success { get; set; }
}

controller

[HttpPost]
public ActionResult AddToBasket(string basket)
{
    //operations ......
    if(Success)
      return Json(new JsonData()
      {
        HtmlMsg = "success operation",
        HtmlBody = "",
        Success = true,
      });
    else
      return Json(new JsonData()
      {
          HtmlMsg = "failed operation",
          HtmlBody = "",
         Success = false,
      });
}

ajax code

$.ajax({
    url: "/Basket/AddToBasket",
    data: { basket: basket },
    type: "Post",
    dataType: "Json",
    success: function (result) {
       if(result.Success)
           //success work....
       esle
           //failed work.........
    },
    error: function () {
        closeshowLoad();
    }
});

Please see this link: Prevent Unhandled jQuery AJAX Error

Take message 400, for example. You can save the error text in a TempData or ViewBag when navigating through the corresponding action on the OtherError page. And show it on the OtherError page.

public ActionResult ErrorPage()
{
   ViewBag.ErrorText = ViewBag.SenderActionErrorText;
   return View();
}
Meysam Asadi
  • 6,438
  • 3
  • 7
  • 17
  • Thanks, I got the idea, I'll try to use this scheme – Alex Muha Jan 30 '21 at 12:50
  • The idea is clear, but I don't like it, since a "crutch" is used at the level of the client code, it is easy to forget about it when supporting a project. I would like to leave the normal implementation in the client code with handling the "correct" error codes – Alex Muha Jan 30 '21 at 13:13
  • >You can save the error text in a TempData or ViewBag when navigating through the corresponding action on the OtherError page I tried, neither TempData nor ViewBag are passed to these controllers, which are triggered by HttpErrors :-( Via httpContext, it was also not possible to pass either through the `HttpConext.Session` or through the `HttpContext.Items` – Alex Muha Feb 01 '21 at 17:28
  • Where you can not work with the viewbag or TempData you can use HttpContext for example HttpContext.Current.Session ["CapchaStringlogin"] = randomString – Meysam Asadi Feb 01 '21 at 18:37
  • Of course, it is better to use httpContext in this case. Because it records the information with the same request. – Meysam Asadi Feb 01 '21 at 18:47
  • Yes, I tried to use the HttpСontext (I filled in the value in the filter and looked at the value in the error handling controller), but for some reason, the context comes empty in the controller (I checked `Session` and `Items`) – Alex Muha Feb 04 '21 at 10:37
  • web.config add this code – Meysam Asadi Feb 04 '21 at 10:56
  • I tried adding a line to the config ``. But data through the session is still not transmitted. Maybe I'm not transmitting correctly? I write in the filter like this: `HttpContext.Current.Session["ErrorMessage"] = message`, and in the error handling control I read it like this: `message = HttpContext.Session["ErrorMessage"].ToString()` – Alex Muha Feb 09 '21 at 07:12
  • handling control may be called before HttpContext.Current.Session ["ErrorMessage"] = message. – Meysam Asadi Feb 09 '21 at 07:19
  • But I look at the sequence of bypassing on the debugger, I see what follows what. – Alex Muha Feb 09 '21 at 12:20
  • The handling control may not have access to session values – Meysam Asadi Feb 09 '21 at 17:51
0

You can also use this way:

HandlerAttribute

    public class MyErrorHandlerAttribute : FilterAttribute, IExceptionFilter
    {
        public void OnException(ExceptionContext filterContext)
        {
            filterContext.ExceptionHandled = true;
            filterContext.Result = new JsonResult
            {
                Data = new {
                    success = false, 
                    error = filterContext.Exception.Message.ToString(),
                    ExceptionStackTrack = filterContext.Exception.StackTrace,
                    ControllerName = filterContext.RouteData.Values["controller"].ToString(),
                    ActionName = filterContext.RouteData.Values["action"].ToString(),
                    ExceptionLogTime = DateTime.Now
                },
                JsonRequestBehavior = JsonRequestBehavior.AllowGet
            };
        }
    }

your action for test

[HttpPost]
public ActionResult Foo(string basket)
{
   if (string.IsNullOrEmpty(basket))
   {
       throw new Exception("your text");
   }
   return Json(new { success = true });
}

register Handler Attribute

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

ajax request

$.ajax({
      url: "/Pages/Foo",
      data: { basket: null },
       type: "Post",
       dataType: "Json",
       success: function (result) {
                if (!result.success) {
                          alert(result.error);
                          alert(result.ExceptionStackTrack);
                          alert(result.ExceptionLogTime);
                          alert(result.ActionName);
                 } else {
                         //successfull         
                 }
       },
       error: function (XMLHttpRequest, textStatus, errorThrown) {
                 alert(XMLHttpRequest.status);
       }
});
     
Meysam Asadi
  • 6,438
  • 3
  • 7
  • 17
  • This solution is understandable, but I don't like it. I would like to receive a message on the client with the correct error code (what I received from the server API) – Alex Muha Feb 01 '21 at 07:37
  • Well, it also fixes the server api error. With this config you manage the server error – Meysam Asadi Feb 01 '21 at 08:37
0

There are several ways to manage errors.

  1. Manage errors that occur in Ajax actions or requests with FilterAttribute.
  2. Manage server-side errors in web config, for example, if a page is not found, redirect to ErrorPage404 and other errors.............
  3. From the action where the error occurs, either put the information in a ViewBag or TempData redirect it to the error management action or do it with the action input parameters.

My suggestion for you

  1. Use a filterattribute for all http actions and requests and pass all errors to a single action. I have given an example in this post.

  2. For server side errors, use web config and transfer all errors to only one action, and there, using the error code, display the appropriate text to the user. I have given an example in this post.

  3. For Ajax and jQuery requests, use the two previous posts that I gave an example.

It all depends on the programmer's taste in how to manage errors.

public class MyErrorHandlerAttribute : FilterAttribute, IExceptionFilter
{
    public void OnException(ExceptionContext filterContext)
    {
        filterContext.ExceptionHandled = true;
        filterContext.Result = new RedirectResult("/Pages/Error?ControllerName=" +
        filterContext.RouteData.Values["controller"].ToString() +
                "&ActionName=" + filterContext.RouteData.Values["action"].ToString() +
                "&Error=" + filterContext.Exception.Message.ToString());

    }      
}

register Handler Attribute

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

and ActionResult

//error 404 & 500 & .............. 
public ActionResult ErrorPage(string errorCode)
{ 
   switch(errorCode)
        {
            case "404":
                ViewBag.Text = "404 Error";
                break;
            case "500":
                ViewBag.Text = "500 Error";
                break;
            case "unknown":
                ViewBag.Text = "unknown Error";
                break;
            default:
                ViewBag.Text = "404 Not Found";
                break;
        }
        return View();
}
//error in  all actions 
public ActionResult Error(string ControllerName, string ActionName, string Error)
{
    ViewBag.Text = "controller: " + ControllerName + "<br /><br />action: " + ActionName + "<br /><br />error message: " + Error;
    return View();
}

and web.config for server-side Error

 <customErrors mode="On" defaultRedirect="/Pages/ErrorPage?errorCode=unknown">
  <error statusCode="404" redirect="/Pages/ErrorPage?errorCode=404" />
  <error statusCode="500" redirect="/Pages/ErrorPage?errorCode=500" />
</customErrors>

and routeConfig.cs

//this code add end route
//Add this code to handle non-existing urls
routes.MapRoute(
       name: "404-PageNotFound",
       // This will handle any non-existing urls
       url: "{*url}",
       // "Shared" is the name of your error controller, and "Error" is the action/page
       // that handles all your custom errors
       defaults: new { controller = "Pages", action = "ErrorPage", id = UrlParameter.Optional }
);

example server error: enter image description here

example action error filterattribute: enter image description here

Meysam Asadi
  • 6,438
  • 3
  • 7
  • 17
  • Thank you very much for your persistence in helping me. But I would like to receive correct error codes on the client. But all your options don't let you do it – Alex Muha Feb 02 '21 at 12:13