Gestire la cancellazione di una richiesta in streaming da Blazor

di Marco De Sanctis, in ASP.NET Core,

In uno script precedente (https://www.aspitalia.com/script/1459/Sfruttare-Streaming-Chiamata-HTTP-Blazor.aspx) ci siamo occupati del concetto di streaming tra Blazor e ASP.NET Core, e abbiamo visto come può essere una soluzione molto interessante quando si tratta di inviare corpose moli di dati dal server al client.

Tuttavia, in questi contesti, vogliamo anche supportare la cancellazione della richiesta. Immaginiamo per esempio, di trovarci sulla pagina che invia la richiesta HTTP in streaming. Nel momento in cui navighiamo via dalla pagina corrente, se proviamo a guardare il network inspector del browser, noteremo un comportamento inaspettato: la richiesta HTTP, non essendo ancora terminata, continuerà a funzionare in background, usando risorse sia sul server che sul client per un risultato che non verrà mai visualizzato all'utente.

Si tratta di un comportamento poco corretto, e il modo per evitarlo è quello di sfruttare un CancellationToken. Il primo passo è ovviamente quello di aggiungere questa funzionalità al nostro extension method GetAsyncEnumerable:

public static class HttpExtensions
{
  public async static IAsyncEnumerable<T> GetAsyncEnumerable<T>(this HttpClient http, string url,
    CancellationToken token = default) // <-- cancellation token passato dall'esterno
  {
    using var request = new HttpRequestMessage(HttpMethod.Get, url);

    request.SetBrowserResponseStreamingEnabled(true);

    // passiamo il cancellation token alla richiesta...
    using var response = await http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token));
    response.EnsureSuccessStatusCode();

    // ... alla lettura dello stream ...
    using var responseStream = await response.Content.ReadAsStreamAsync(token);

    // ... e alla sua deserializzazione ...
    IAsyncEnumerable<T> items = JsonSerializer.DeserializeAsyncEnumerable<T>(responseStream,
      new JsonSerializerOptions
      {
        PropertyNameCaseInsensitive = true,
        DefaultBufferSize = 128
      });

      await foreach (T item in items)
      {
        yield return item;
      }
  }
}

Come possiamo notare, tutto ciò che dobbiamo fare è aggiungere un parametro di tipo CancellationToken e passarlo ai vari statement asincroni che dobbiamo eseguire.

A questo punto, dobbiamo fare in modo che venga invocato quando navighiamo via dalla pagina corrente. La soluzione è implementare l'interfaccia IDisposable come mostrato in basso:

@page "/fetchdata"
@implements IDisposable

... altro codice qui ...

@code {
    private CancellationTokenSource _cts = new();
    
    // .. altro codice qui ..

    protected override async Task OnInitializedAsync()
    {
        var results = Http.GetAsyncEnumerable<WeatherForecast>("https://localhost...", _cts.Token);
        
        // ... altro codice qui ...
    }

    public void Dispose()
    {
        _cts.Cancel();
    }
}

La pagina contiene un field di tipo CancellationTokenSource, che usiamo per inviare il token alla chiamata GetAsyncEnumerable. Nel metodo Dispose, ne invochiamo il Cancel, così da segnalare che non è più necessario proseguire nella richiesta. Questo farà sì che la chiamata HTTP venga correttamente terminata, sia sul client, sia sul server, visto che ASP.NET Core gestisce automaticamente la cancellazione della richiesta terminandone l'esecuzione.

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi