
Escaping the Cancel Button Trap: AbortController and CancellationToken in ASP.NET API
Canceling a long-running process from a UI form doesn't mean the server has stopped processing the work. Let's learn how to use AbortController and CancellationTokens to help cancel a task all the way down when building ASP NET applications.
Table of Contents
Imagine this..
A user starts a resource-heavy task from your app's UI - maybe processing extensive data or running a complex report.
Halfway through, they realize something's wrong and hit the cancel button. The UI responds immediately, showing the task as canceled.
Problem solved, right?
Wrong!!
Canceling a long-running process from a UI form doesn't mean the server has stopped processing the work. Your server could still be chugging along, consuming precious resources for something no one wants anymore.
Depending on the kind of work, this could be more than inefficient—it could also be dangerous and lead to inconsistent states and misleading results.
So, how do you let the user cancel this work and ensure it gets canceled on the server?
Thanks to AWS for sponsoring this post in my ASP NET Series.
The Example Scenario
This post will explore the scenario where a user uploads a file to Amazon S3 and has the option to cancel the operation midway.
Depending on your use case, this could be replaced with any long-running process that can be canceled based on user input from the UI.

Amazon Simple Storage Service (S3) is an object storage service that provides a scalable and secure storage infrastructure.

The Cancel Button Trap on the UI
The UI shown above calls the below POST /upload-large-file
endpoint, when the Upload button is clicked after selecting a file to upload.
The code uses the .NET Amazon S3 SDK to upload the file to S3 storage.
app.MapPost("/upload-large-file", async (
[FromForm] FileUploadRequest request,
CancellationToken cancellationToken) =>
{
try
{
var s3Client = new AmazonS3Client();
await s3Client.PutObjectAsync(new PutObjectRequest()
{
BucketName = "user-service-large-messages",
Key = $"{Guid.NewGuid()} - {request.File.FileName}",
InputStream = request.File.OpenReadStream()
}, cancellationToken);
await PerformAdditionalTasks(CancellationToken.None);
return Results.Ok();
}
catch (OperationCanceledException e)
{
return Results.StatusCode(499);
}
})
.WithName("UploadLargeFile")
.DisableAntiforgery()
.WithOpenApi();
The HTTP Endpoint takes in a CancellationToken as part of its parameters.
In ASP NET APIs, when a user makes an HTTP request, the framework automatically creates a CancellationTokenSource
and passes the token along with the HttpContext
as the ReuestAborted
property.
We explored this previously when we learned about Cancellation Tokens in .NET.

When the file uploaded is large, it takes time to upload it to S3.
If the user cancels the operation midway during the upload to S3 using the Cancel button, the underlying API request is not automatically cancelled.
const response = await fetch('https://localhost:44367/upload-large
method: 'POST',
body: formData
});
The file will continue to be uploaded to S3, and additional steps will be performed.
Since the UI no longer cares about this request, it ignores the response from the request.
Abort Controllers For Cancelling .NET API Request
In these scenarios, when we need to cancel an HTTP response from a UI form/page, we can use AbortControllers to signal this cancellation to the underlying HTTP requests.
The AbortController interface represents a controller object that allows you to abort one or more Web requests as and when desired.
We pass in the AbortSignal as a request option whenever a fetch request is made. This allows us to invoke the Abort
function on the AbortController
and also have the associated fetch request aborted.
abortController = new AbortController();
try {
const response = await fetch('https://localhost:44367/upload-large
method: 'POST',
body: formData,
signal: abortController.signal
});
...
} catch (error) {
if (error.name === 'AbortError') {
userMessage = 'Upload cancelled.';
}
...
}
...
cancelBtn.addEventListener('click', () => {
abortController.abort();
...
});
When abort()
is called, the fetch throws DOMException
named AbortError
.
In the above case, I'm explicitly handling this and showing the user the message that the operation is canceled.
Since the fetch request also listens to the abort signal, it also invokes a cancellation of the request.
This will trigger the CancellationToken wired up for the specific HTTP request, which, in turn, cancels the upload to the S3 bucket.
The file will not be uploaded in this scenario to S3.
There's still the possibility of the user canceling the upload request right after the file finished uploading to S3. In these scenarios, we will need to write additional clean-up code to remove the files from S3 and roll back the operation as a whole.
Alternatively, we can move to message-based patterns to avoid doing long-processing tasks during the HTTP request/response cycle.
Rahul Nath Newsletter
Join the newsletter to receive the latest updates in your inbox.