'How to cancel .Net Core Web API request using Angular?

I have the following two applications

  • Angular 6/7 App
  • .Net Core Web API

I am making GET request to API using Angular's HttpClient as shown below

this.subscription = this.httpClient.get('api/Controller/LongRunningProcess')
                                   .subscribe((response) => 
                                   {
                                      // Handling response
                                   });

API controller's LongRunningProcess method has the following code

    [HttpGet]
    [Route("LongRunningProcess")]
    public async Task<IActionResult> LongRunningProcess(CancellationToken cancellationToken)
    {
        try
        {
            // Dummy long operation
            await Task.Factory.StartNew(() =>
            {
                for (int i = 0; i < 10; i++)
                {
                    // Option 1 (Not working)
                    if (cancellationToken.IsCancellationRequested)
                        break;

                    // Option 2 (Not working)
                    cancellationToken.ThrowIfCancellationRequested();

                    Thread.Sleep(6000);
                }

            }, cancellationToken);
        }
        catch (OperationCanceledException e)
        {
            Console.WriteLine($"{nameof(OperationCanceledException)} thrown with message: {e.Message}");
        }

        return Ok();
    }

Now I want to cancel this long-running process so I am unsubscribing from client side as shown below

// On cancel button's click
this.subscription.unsubscribe();

Above code will cancel the request and I can see it is canceled in the Network tab of the browser as shown below

enter image description here

But it is not going to make IsCancellationRequested to true in the method LongRunningProcess of the API, so the operation will keep going.

[Note]: Both Option 1 and Option 2 in API method are not working even if I make a call using postman.

Question: Is there any way to cancel that LongRunningProcess method's operation?



Solution 1:[1]

When angular cancel request, you can get cancellation token from http context

   CancellationToken cancellationToken = HttpContext.RequestAborted;
    if (cancellationToken.IsCancellationRequested)
    {
        // The client has aborted the request
    }

Solution 2:[2]

You dont need break in this case only use like this

 [HttpGet]
 [Route("LongRunningProcess")]
 public async Task<IActionResult> LongRunningProcess(CancellationToken cancellationToken)
 {
   for (int i = 0; i < 10; i++)
   {
      cancellationToken.ThrowIfCancellationRequested();
       // Dummy long operation
       await Task.Factory.StartNew(() => Thread.Sleep(60000));
   }

    return Ok();
 }

You can read it more here

Solution 3:[3]

This is because your dummy long operation does not monitor the canncellationToken. I'm not sure it is actually your intention to start 10 one-minute tasks all in parallel without any delay, which is what your code does.

In order to have a dummy long operation, the code would be like

[HttpGet]
[Route("LongRunningProcess")]
public async Task<IActionResult> LongRunningProcess(CancellationToken cancellationToken)
{
    // Dummy long operation
    await Task.Run(() =>
        {
            for (var i = 0; i < 60; i++)
            {
                if (cancel.IsCancellationRequested)
                    break;
                Task.Delay(1000).Wait();
            }
        });

    return Ok();
}

Task.Run is just equivalent to Task.Factory.StartNew, by the way.

However, if you just need a dummy long-run operation in your web API, then you can also simply use Task.Delay, which supports cancellation token. Task.Delay throws an exception when the request is canceled, so add exception handling code when you need to do something after request cancellation.

[HttpGet]
[Route("LongRunningProcess")]
public async Task<IActionResult> LongRunningProcess(CancellationToken cancellationToken)
{
    // Dummy long operation
    await Task.Delay(60000, cancel);

    return Ok();
}

Solution 4:[4]

Any http observables still running at the time will complete and run their logic unless you unsubscribe in onDestroy(). Whether the consequences are trivial or not will depend upon what you do in the subscribe handler. If you try to update something that doesn't exist anymore you may get an error.

Tip: The Subscription contains a closed boolean property that may be useful in advanced cases. For HTTP this will be set when it completes. In Angular it might be useful in some situations to set a _isDestroyed property in ngDestroy which can be checked by your subscribe handler.

Tip 2: If handling multiple subscriptions you can create an ad-hoc new Subscription() object and add(...) any other subscriptions to it - so when you unsubscribe from the main one it will unsubscribe all the added subscriptions too.

So, best practice is to use takeUntil() and unsubscribe from http calls when the component is destroyed.

   import { takeUntil } from 'rxjs/operators';
   .....
   ngOnDestroy(): void {
     this.destroy$.next();  // trigger the unsubscribe
     this.destroy$.complete(); // finalize & clean up the subject stream
   }

Solution 5:[5]

var cancellationToken = new CanellationToken();
    cancellationToken.CancelAfter(2000);
    using (var response = await _httpClient.GetAsync("emp", 
        HttpCompletionOption.ResponseHeadersRead, cancellationTokenSource.Token))
    {
        response.EnsureSuccessStatusCode();
        var stream = await response.Content.ReadAsStreamAsync();
        var emp = await JsonSerializer.DeserializeAsync<List<empDto>>(stream, _options);
    }

Further we can also have this "CancellationToken" class, which is nothing much Http client method which terminates the request after certain time-interval.

Solution 6:[6]

    • In angular subscription.unsubscribe(); closes the channel and causes CORE to cancel the API caller's thread, that's good.
    • Don't use await Task.Run(()... This creates a result/task that should be disposed, if not, the task keeps going, your pattern doesn't permit this - that's why it continues to run.
    • Simply - 'await this.YourLongRunningFunction()', I'm pretty sure that when the owning thread throws the OperationCancelled exception your task will end.
  1. If "3" doesn't work, then pass a cancellation token to your long running task and set that when you catch your OperationCancelled exception.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 divyang4481
Solution 2 Tony Ngo
Solution 3 Yas Ikeda
Solution 4 Moinul Islam
Solution 5 Prabhanath
Solution 6 DougS