简体   繁体   中英

Async method in controller is called multiple times when an exception is caught

I am trying to troubleshoot a problem with exceptions not getting through to my ajax call properly.

My asynchronous controller method is called multiple times, where I only send one ajax request. The response is supposed to be sent, but looking at the network tab in firefox's debug panel, I see no response; the ajax error handler function also has a readyState = 0. This happens only when an exception is thrown inside the async controller method.

There are three/four parts to this problem:

  • My javascript
  • My async controller method
  • The async private controller method called by the latter
  • The JsonError and JsonSuccess methods formatting the response

Ajax call:

function test() {

    var data = {
        campagneId: 15,
        tiersNum: 2721
    };

    console.debug("Starting Ajax !");

    $.ajax({
        url: "/CampagnesMailing/SendMail",
        data: data,
        method: "POST",
        success: function (response) {
            console.debug("Sent mail successfully!");
        },
        error: function (xhr, ajaxOpt, thrownError) {
            console.error("Error!");
            console.error(xhr); //xhr.readyState is 0
            console.error(ajaxOpt);
            console.error(thrownError); //this is empty
        },
        complete: function () {
            console.debug("Finished ajax call!");
        }
    });
}
$("#goButton").on("click", test);

Async controller method called by ajax:

[HttpPost]
public async Task<ActionResult> SendMail(int campagneId, int tiersNum)
{
    try
    {
        MailMessage mail = await GetMailFor(campagneId, tiersNum); //I tried adding .ConfigureAwait(false) with no change
        return JsonSuccess();
    }
    catch (Exception e)
    {
        return JsonError(e);
    }
}

Async method GetMailFor:

private async Task<MailMessage> GetMailFor(int campagneId, int tiersNum)
{
    try
    {
        MailMessage mail = new MailMessage();

        mail.To.Add(new MailAddress("")); // This throws an ArgumentException

        return mail;
    }
    catch (Exception e)
    {
        throw;
    }
}

JsonError / JsonSuccess:

protected JsonResult JsonSuccess()
{
    Response.StatusCode = (int) System.Net.HttpStatusCode.OK;
    Response.StatusDescription = "SUCCESS";
    return Json(new { success = true }, JsonRequestBehavior.AllowGet);
}
protected JsonResult JsonError(Exception e)
{
    Response.StatusCode = (int) System.Net.HttpStatusCode.InternalServerError;
    Response.StatusDescription = e.Message;
    //HttpContext.ApplicationInstance.CompleteRequest();
    return Json(new {e.Message}, JsonRequestBehavior.AllowGet);
}

When I put a breakpoint on the first line of SendMail (the one with MailMessage mail = await GetMailFor(campagneId, tiersNum); ) and a breakpoint in the catch, I see the method is called 7 times and goes into the catch everytime, mostly with different ManagedThreadId values in System.Threading.Thread.CurrentThread .

Oddly enough, if I replace the mail.To.Add() call by a throw new ArgumentException("Boom"); , the process goes well and only one call is caught in my breakpoints.

I had a look at this issue but adding HttpContext.ApplicationInstance.CompleteRequest(); before return Json(new {e.Message}, JsonRequestBehavior.AllowGet); did not change anything.

I only see 1 POST request in Network tab from firefox. This never gets any response. Please note these snippets are enough to cause the problem but I use more code in the real application (No need to warn me about async method containing no await).

What is happening? Why is SendMail called 7 times? Why only when new MailAddress() throws and not when I throw manually? How can I debug such a mindblowing behavior?

EDIT: Removing try/catch from inside the GetMailFor method yields no change.
EDIT2: Removing any mention of async , Task<T> or await also yields no change, so this has nothing to do with an async problem. I'm kind of lost now because I don't know how to debug that...
EDIT3: I never enter the Application_Error function, but I enter Application_BeginRequest everytime before entering SendMail and Application_EndRequest everytime after returning JsonError

Trying crazy things at random, I stumbled upon the real problem: JsonError was adding the exception message as Response.StatusDescription while the message contained \\r\\n . This somehow broke the request handling.

The fix is simply to change JsonError to:

protected JsonResult JsonError(Exception e)
{
    Response.StatusCode = (int) System.Net.HttpStatusCode.InternalServerError;
    Response.StatusDescription = e.Message.Replace("\r\n", " | ");
    return Json(new {e.Message}, JsonRequestBehavior.AllowGet);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM