Search code examples
c#asp.net.net-coreasync-awaitblazor

CancellationToken in Blazor Pages?


After living under a rock for 2 years employment wise, I am now confronted with Blazor at my new Workplace and have a lot of catch up to do after doing mostly ASP.NET Framework MVC prior to the 2 Years.

Trying myself on Blazor server side, I tried to apply my past knowledge which included cancellationtokens for async operations and i couldn't find much information about them in combination with Blazor.

Are they still a Best Practice or did they became Obsolete at some point? I did found this previously asked question which recommends creating a tokensource on the OnInitializedAsync() method and cancelling it on Dispose() which i honestly find a bit crude. (I would need to implement this for each page and you know... DRY)

I also found this Article about advanced Scenarios on Microsoft Docs that explains how to implement a Circuit Handler, which honestly is a bit beyond me right now and most likely way out of scope for my little home-project.

In comparison, in asp.net Framework MVC i would build a Controller like this:

namespace SampleWebsite.Controllers
{
    public class SampleController : ApiController
    {
        private readonly MyEntities _entities = new MyEntities();

        public async Task<IHttpActionResult> MyAsyncApi(CancellationToken cancellationToken)
        {
            var result = _entities.MyModel.FirstOrDefault(e => e.Id == 1, cancellationToken: cancellationToken);
            return OK(result);
        }
    }
}

The CancellationToken will be injected by asp.net Framework / Core and is directly linked to the current context connection-pipe. Hence, if the user closes the connection, the token becomes invalid.

I would have assumed that for asp.net core and blazor where dependency-injections is a big part of it, this would be the case here too, but i could not find any documentation about this here.

So, should cancellationtokens still be used at this point or does Microsoft do some magic in the background for asynchronous tasks? And if yes, what would be the best implementation?

EDIT: Here would be my Setup to clarify:

The Blazor-Component:

@page "/Index"
@inject IIndexService Service

@* Some fancy UI stuff *@

@code {
    private IEnumerable<FancyUiValue> _uiValues;

    protected override async Task OnInitializedAsync()
    {
        _uiValues = await Service.FetchCostlyValues();
    }
}

And the Injected Service-Class that does the heavy lifting:

public interface IIndexService
{
    Task<IEnumerable<FancyUiValue>> FetchCostlyValues();
}

public class IndexService : IIndexService
{
    public async Task<IEnumerable<FancyUiValue>> FetchCostlyValues()
    {
        var uiValues = await heavyTask.ToListAsync(); // <-- Best way to get a cancellationtoken here?
        return uiValues;
    }
}

My question is, what would be the best way to get a token in the specificed part of the code or would it be irrelevant because the Server would kill all running tasks when the connection (as example) ends?


Solution

  • After 2 years of experience with Blazor, i figured that the only reliable way to pass an CancellationToken to a Task within an Object of a longer Lifetime (e.g. Singleton or Scoped Service) is the combination of IDisposeable and CancellationTokenSource

    @page "/"
    @implements IDisposable
    
    *@ Razor Stuff *@
    
    @code
    {
        private CancellationTokenSource _cts = new();
    
        protected override async Task OnInitializedAsync()
        {
            await BusinessLogicSingleton.DoExpensiveTask(_cts.Token);
        }
    
        #region IDisposable
    
        public void Dispose()
        {
            _cts.Cancel();
            _cts.Dispose();
        }
    
        #endregion
    }
    

    On repeated use or just to comply to the DRY-Rule, you can also inherit from the ComponentBase Class and then use that Class for your Components that require to pass a CancellationToken:

    public class CancellableComponent : ComponentBase, IDisposable
        {
            internal CancellationTokenSource _cts = new();
    
            public void Dispose()
            {
                _cts.Cancel();
                _cts.Dispose();
            }
        }
    
    @page "/"
    @inherits CancellableComponent
    
    @* Rest of the Component *@
    

    I also found that while you could Inject the IHttpContextAccessor and use the HttpContext.RequestAborted token which is the same that will be generated and injected in your ASP.Net MVC Method Calls, as of the current .Net6 Version it will never fire even after the Connection to the Client is severed and the providing HttpContext is disposed.

    This may be a case for the Developer-Team on Github as i do see UseCases for it where the User is allowed to exit the Component while the Task keeps on going until the User leaves the Website completely.
    (For such cases, my recommended Workaround would be to write your own CircuitHandler that will give you Events for when a Circuit is removed.)