Canceling background tasks when a user navigates away from a Blazor component

 
 
  • Gérald Barré

In any application, wasting resources is undesirable. When an ongoing operation is no longer needed, you should cancel it. For example, if your component downloads data to display in a view and the user navigates away, you should cancel that download.

In .NET, the standard way to cancel an operation is to use a CancellationToken. You can create one using a CancellationTokenSource. Once the Blazor component is removed from the page, call the Cancel method to stop any ongoing operations.

According to the Blazor component lifecycle, the right place to call Cancel is inside the IDisposable.Dispose() method.

SampleComponent.razor (Razor)
@page "/fetchdata"
@using System.Threading
@implements IDisposable
@inject HttpClient Http

... todo content

@code {
    // 👇 Create the CancellationTokenSource
    private CancellationTokenSource cts = new CancellationTokenSource();
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        // 👇 Use the CancellationToken for the http call using cts.Token
        forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json", cts.Token);
    }

     // 👇 Cancel the token
    public void Dispose()
    {
        cts.Cancel();
        cts.Dispose();
    }

    public class WeatherForecast
    {
        public DateTime Date { get; set; }

        public int TemperatureC { get; set; }

        public string Summary { get; set; }

        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    }
}

#Using a base component

Instead of manually adding a CancellationTokenSource to every component, you can create a base component that exposes a CancellationToken and inherit it across all components in the project.

ComponentWithCancellationToken.cs (C#)
public abstract class ComponentWithCancellationToken : ComponentBase, IDisposable
{
    private CancellationTokenSource? _cancellationTokenSource;

    protected CancellationToken ComponentDetached => (_cancellationTokenSource ??= new()).Token;

    public virtual void Dispose()
    {
        if (_cancellationTokenSource != null)
        {
            _cancellationTokenSource.Cancel();
            _cancellationTokenSource.Dispose();
            _cancellationTokenSource = null;
        }
    }
}

Then, edit the _Imports.razor files with:

_Imports.razor (Razor)
@inherits ComponentWithCancellationToken
@using System.Net.Http
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using MyBlazorApp
@using MyBlazorApp.Shared

You can now use the ComponentDetached property in your components:

SampleComponent.razor (Razor)
@inject HttpClient Http

... todo content

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json", ComponentDetached);
    }
}

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?