12

Is there a way to figure out in ASP.NET Web API beta whether the HTTP request was cancelled (aborted by user of for any another reason)? I'm looking for opportunity to have a kind of cancellation token out-of-the-box that will signal that the request is aborted and therefore long-running ops should be aborted as well.

Possible related question - the use case for the CancellationTokenModelBinder class. What's the reason to have a separate binder for cancellation token?

tugberk
  • 57,477
  • 67
  • 243
  • 335
pavel.baravik
  • 689
  • 1
  • 11
  • 21

3 Answers3

8

You could check Response.IsClientConnected from time to time to see if the browser is still connected to the server.

Kamyar Nazeri
  • 25,786
  • 15
  • 50
  • 87
  • This seems to be from ASP.NET MVC stack, not from ASP.NET MVC Web API. Please correct me if wrong. – pavel.baravik Mar 19 '12 at 09:02
  • 3
    This is from core ASP.Net engine. Only works with IIS integration pipeline (not VS debug mode). MSDN covers a good documentation: http://msdn.microsoft.com/en-us/library/system.web.httpresponse.isclientconnected.aspx – Kamyar Nazeri Mar 19 '12 at 09:22
  • thank you for suggestion. This seems to be the only option, I'll probably build solution basing on this. – pavel.baravik Mar 19 '12 at 15:31
8

I'd like to sum-up a bit. The only approach that seem to work is checking Response.IsClientConnected. Here some technical details regarding what is going behind the stage: here and here This approach has some flaws:

  • Works only under IIS (no self-hosting, no Dev Server);
  • According to some SO answers may be slow (do not react immediately after client disconnected): here;
  • There are considerations regarding this call cost: here

At the end I came up with the following piece of code to inject CancellationToken based on IsClientConnected into the Web API controller:

    public class ConnectionAbortTokenAttribute : System.Web.Http.Filters.ActionFilterAttribute
{
    private readonly string _paramName;
    private Timer _timer;
    private CancellationTokenSource _tokenSource;
    private CancellationToken _token;

    public ConnectionAbortTokenAttribute(string paramName)
    {
        _paramName = paramName;
    }

    public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        object value;
        if (!actionContext.ActionArguments.TryGetValue(_paramName, out value))
        {
            // no args with defined name found
            base.OnActionExecuting(actionContext);
            return;
        }

        var context = HttpContext.Current;
        if (context == null)
        {
            // consider the self-hosting case (?)
            base.OnActionExecuting(actionContext);
            return;
        }

        _tokenSource = new CancellationTokenSource();
        _token = _tokenSource.Token;
        // inject
        actionContext.ActionArguments[_paramName] = _token;
        // stop timer on client disconnect
        _token.Register(() => _timer.Dispose());

        _timer = new Timer
        (
            state =>
            {
                if (!context.Response.IsClientConnected)
                {
                    _tokenSource.Cancel();
                }
            }, null, 0, 1000    // check each second. Opts: make configurable; increase/decrease.
        );

        base.OnActionExecuting(actionContext);
    }

    /*
     * Is this guaranteed to be called?
     * 
     * 
     */
    public override void OnActionExecuted(System.Web.Http.Filters.HttpActionExecutedContext actionExecutedContext)
    {
        if(_timer != null)
            _timer.Dispose();

        if(_tokenSource != null)
            _tokenSource.Dispose();

        base.OnActionExecuted(actionExecutedContext);
    }
}
Community
  • 1
  • 1
pavel.baravik
  • 689
  • 1
  • 11
  • 21
1

If you added CancellationToken in to controller methods, it will be automatically injected by the framework, and when a client calls xhr.abort() the token will be automatically cancelled

Something similar to

public Task<string> Get(CancellationToken cancellationToken = default(CancellationToken))

For MVC you can also refer to

HttpContext.Current.Response.IsClientConnected
HttpContext.Response.ClientDisconnectedToken

For .NetCore

services.AddTransient<ICustomInterface>(provider => { 
     var accessor = provider.GetService<IHttpContextAccessor>);
     accessor.HttpContext.RequestAborted;
  }); 
Sameh
  • 1,318
  • 11
  • 11