Search code examples
angular.net-corecancellation-tokencancellationtokensource

CancellationToken in controller function of .Net Core suddenly becomes true


I just came into a very strange issue regarding cancellationToken. The web API is developed using .Net Core and frontend is in Angular. 1 hour ago I try to develop more feature and before starting I run it again and suddenly the cancellationToken becomes true. This never happens before. I made no changes in backend and frontend.

The controller function:

[HttpDelete("dqc")]
[AllowAnonymous]
public async Task<IActionResult> DeleteDQCAsync([FromBody] IEnumerable<DqcDto> dqcs, CancellationToken cancellationToken)
{
    try
    {
        var response = await _managerService.DeleteDqcAsync(dqcs, cancellationToken);
        if (response)
        {
            return Ok(new { message = "DQC codes have been deleted successfully" });
        }
        else
        {
            return BadRequest(new { message = "Delete selected DQC codes failed." });
        }
    }
    catch (Exception)
    {
        throw;
    }
}

Even strange is, when I add breakpoint and debug the code, the function is invoked and cancellation is false. One second later the cancellationToken becomes true but I click nothing. Sometimes when I hit the button in the frontend, the function is even not invoked. Only when I add breakpoint in browser and execute line by line, the cancellationToken is always false.

It looks like that frontend sends the signal to cancel the request automatically but I have no idea why since this never happens even till this afternoon.

Could someone gives me a hint how I can figure out why this happens?

Update: After hours of exploration, I find that the Angular code acts strangely. For example:

deleteItems(): void {
    let isDelete: boolean = false;
    const itemsToDelete = this.checkitems.map((item) => ({
      uicp: item.uicp,
      dqcCode: item.dqcCode,
      dqcDescription: item.dqcDescription,
      task: Object.keys(item.tasks)
        .filter((task) => item.tasks[task])
        .join(','),
      remark: item.remark,
      timestamp: item.lastUpdate,
    }));

    console.log(itemsToDelete);

    // Assuming there's a method in DataService to handle deletion
    this.dataService.deleteData(itemsToDelete).subscribe((response) => {
      console.log('Items deleted:', response);
      if (response.status === 200) {
        // Remove the deleted items from tableData
        this.tableData = this.tableData.filter(
          (item) => !this.checkitems.includes(item)
        );
        this.checkitems = [];
        isDelete = true;
      }
    });
    if (isDelete) {
      location.reload();
    }
  }

  saveItems(): void {
    let isAdd: boolean = false;
    let isUpdate: boolean = false;
    const newItems = this.checkitems
      .filter((item) => item.isNew)
      .map((item) => ({
        uicp: item.uicp,
        dqcCode: item.dqcCode,
        dqcDescription: item.dqcDescription,
        task: Object.keys(item.tasks)
          .filter((task) => item.tasks[task])
          .join(','),
        remark: item.remark,
      }));
    console.log('finish new items');

    const updatedItems = this.checkitems
      .filter((item) => !item.isNew)
      .map((item) => ({
        uicp: item.uicp,
        dqcCode: item.dqcCode,
        dqcDescription: item.dqcDescription,
        task: Object.keys(item.tasks)
          .filter((task) => item.tasks[task])
          .join(','),
        remark: item.remark,
        timestamp: item.lastUpdate,
      }));
    console.log('finish update item');

    console.log('New Items:', newItems);
    console.log('Updated Items:', updatedItems);
    if (newItems.length > 0) {
      console.log('start to add');
      this.dataService.addData(newItems).subscribe(
        (response) => {
          console.log('wait for response');
          if (response.status === 200) {
            isAdd = true;
            console.log('New items added:', response.body);
          } else {
            console.error('Unexpected response:', response);
          }
          this.checkitems.forEach((item) => {
            item.isNew = false;
            item.selected = false;
          });
          this.checkitems = [];
          //this.updateSavedItems();
        },
        (error: HttpErrorResponse) => {
          console.error('Error adding new items:', error.message);
        }
      );
    }

    if (updatedItems.length > 0) {
      console.log('start to update');
      this.dataService.updateData(updatedItems).subscribe(
        (response) => {
          console.log('wait for response');
          if (response.status === 200) {
            isUpdate = true;
            console.log('Items updated:', response.body);
          } else {
            console.error('Unexpected response:', response);
          }
          this.checkitems.forEach((item) => {
            item.isNew = false;
            item.selected = false;
          });
          this.checkitems = [];
          //this.updateSavedItems();
        },
        (error: HttpErrorResponse) => {
          console.error('Error updating items:', error.message);
        }
      );
    }
    if (isAdd || isUpdate) {
      location.reload();
    }
  }

Service functions:

deleteData(items: any[]): Observable<any> {
    return this.http.delete(`${this.apiUrl}/dqc`, {'body':items});
  }

  addData(newItems: any[]): Observable<any> {
    return this.http.post<any>(`${this.apiUrl}/dqc`, newItems);
  }

   updateData(updatedItems: any[]): Observable<any> {
    return this.http.put<any>(`${this.apiUrl}/dqc`, updatedItems);
  } 

There are two main functions, one is to delete items and another one is to update or/and add items. In these two functions, somehow the program skip the middle part and directly jumps to the end to refresh the page, even skip the parts that send requests to backend. That causes the cancellationToken set to true. Sometimes this issue disappears and everything goes well. It acts like that the location.reload() is magnet and leads the program to execute this line first.

Does someone know why this happens?


Solution

  • The problem is, that location.reload() is directly executed after subscibe(). It runs in parallel - the call to your API is not awaited that way. Thats the reason for the cancellation. You have to put the location.reload() statement into the subscribe() like that:

    this.dataService.deleteData(itemsToDelete).subscribe((response) => {
          console.log('Items deleted:', response);
          if (response.status === 200) {
            // Remove the deleted items from tableData
            this.tableData = this.tableData.filter(
              (item) => !this.checkitems.includes(item)
            );
            this.checkitems = [];
            location.reload();
          }
        });
      }
    

    And by the way: Instead of using location.reload() you should just call your GET action of this API, when the delete call is finished.