1

I'm performing an AJAX call to my Web API, using the technology WebAPI.

The method that handles that request is the following:

[Route("RenderNotificationsPopup")]
[HttpGet]
public async Task<string> RenderNotificationsPopup(bool responsive = false)
{
    return await GetContentAsync(string.Format("RenderNotificationsPopup?responsive={0}", responsive ? 1 : 0));
}

This method calls GetContentAsync which peforms a server-side call using the class HttpClient.

private async Task<string> GetContentAsync(string urlSuffix)
{
        using (var client = new HttpClient())
        {
            return await client.GetStringAsync(string.Format(
                "{0}/Header/{1}",
                ConfigurationManager.AppSettings["GatewaysHttpRoot"],
                urlSuffix));
        }
}

Everything is working fine, except when users close the page while the AJAX query is pending a response from WebApi. In that case, I get this error:

Message: System.Threading.Tasks.TaskCanceledException: A task was canceled. at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter1.GetResult() at System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__0.MoveNext() — End of stack trace from previous location where exception was thrown — at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter1.GetResult() at System.Web.Http.Controllers.ActionFilterResult.d__2.MoveNext() — End of stack trace from previous location where exception was thrown — at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() at System.Web.Http.Controllers.ExceptionFilterResult.d__0.MoveNext()

I can reproduce this issue myself by loading the page and closing it before I get the response of the AJAX query, like if leaving the page before while WebApi is processing the query is struggling itself.

I searched on the Web and saw that there is a bug related to that (ASP.NET Web API OperationCanceledException when browser cancels the request). Despite that, it apparently concerns an old version of WebApi package and I'm using the latest version.

Did I do a mistake in my code about the async/await pattern or is it always that nasty bug ?

Many thanks !

EDIT: I tried the workaround given in the SO post I linked (using the class CancelledTaskBugWorkaroundMessageHandler) and I still get the TaskCanceledException.

EDIT 2: Seems that this issue cannot be avoided. Is it possible to handle such exceptions in WebApi applications ?

Community
  • 1
  • 1
Jämes
  • 6,945
  • 4
  • 40
  • 56
  • what is your problem with the exception? Does your appication crush? Behavior is normal I think, cause why should the task continue work when the client is gone. – Jehof Apr 13 '15 at 12:53
  • I'm wondering if getting an exception is normal, because an exception is usually synonym of error. Also, I'm wondering if I can catch it properly in order to avoid to have hundred of them per days in the server logs. – Jämes Apr 13 '15 at 13:02
  • Where do you get this exception? As the HTTP response? Or in the debugger? – usr Apr 14 '15 at 08:53
  • I added an answer that enables me to properly handle this exception finally. This one also answers to your question. :) – Jämes Apr 14 '15 at 09:15

2 Answers2

5

I use my own derived class of ExceptionFilterAttribute in order to log the exception related to the WebApi application, overriding the method OnException.

The proper way I found to handle the exception TaskCanceledException is to override OnExceptionAsync instead of OnException and rely on the property IsCancellationRequested to detect when the task is canceled.

Here is the method I use as error handling:

public override async Task OnExceptionAsync(HttpActionExecutedContext context, CancellationToken cancellationToken)
{
    if (!cancellationToken.IsCancellationRequested)
    {
        //Handle domain specific exceptions here if any.

        //Handle all other Web Api (not all, but Web API specific only ) exceptions
        Logger.Write(context.Exception, "Email");

        context.Response = context.Request.CreateErrorResponse(HttpStatusCode.InternalServerError, "An unhandled exception was thrown.");
    }
}

Where the class Logger comes from the Enterprise Library Application Blocks.

Jämes
  • 6,945
  • 4
  • 40
  • 56
  • 1
    This is the bees knees, I had issues with `IExceptionHandler` not catching `TaskCancelledException` in Web API 2, this example solves that. Thanks a ton! – Art Sep 12 '16 at 07:07
2

It is normal for the task to be canceled, if the underlying client connection is dead. You need to handle the exception.

Hylaean
  • 1,237
  • 13
  • 19
  • Okay, so what is the best practices to handle such cases ? I didn't read anything related to that behavior on the documentation of Microsoft. I tested the workaround of the SO post linked in my question in order to handle the exception but I'm still encountering it. – Jämes Apr 13 '15 at 12:22
  • This exception seems to come from MVC internal code. – usr Apr 14 '15 at 08:52