web-dev-qa-db-fra.com

Blazor - Afficher l'attente ou le spinner lors d'un appel API

Dans mon application blazer, je passe un appel API à un serveur principal qui pourrait prendre un certain temps. J'ai besoin d'afficher des commentaires à l'utilisateur, un curseur d'attente ou une image "spinner". Comment cela se fait-il dans Blazor?

J'ai essayé d'utiliser CSS et d'activer et de désactiver le CSS, mais la page n'est pas actualisée tant que l'appel n'est pas terminé. Toutes les suggestions seraient grandement appréciées.

@functions {
    UserModel userModel = new UserModel();
    Response response = new Response();
    string errorCss = "errorOff";
    string cursorCSS = "cursorSpinOff";

    protected void Submit()
    {
        //Show Sending...
        cursorCSS = "";
        this.StateHasChanged();
        response = Service.Post(userModel);
        if (response.Errors.Any())
        {
            errorCss = "errorOn";
        }
        //turn sending off
        cursorCSS = "cursorSpinOff";
        this.StateHasChanged();
    }
}
9
David23g

Modifié en janvier 2020. @ Ed Charbenea a publié la bonne façon de le faire sur projet BlazorPro.Spinkit : enfermer de longs processus dans une tâche pour ne pas bloquer le thread. Avant son explication, j'ai utilisé await Task.Delay(1); pour vider les changements d'interface utilisateur, maintenant, la bonne façon est d'exposer ici:


Réponse courte

Assurez-vous que votre LongOperation() est un Task, si ce n'est pas le cas, placez-la dans un Task et attendez-la:

response = await Task.Run(()=> Service.Post(userModel)); //<--here!

Réponse détaillée

Blazor fonctionne avec un dom virtuel, le framework suit les modifications et n'envoie que les modifications lorsque le thread principal n'est pas bloqué. Voici comment j'autorise Blazor à vider les modifications apportées à l'interface utilisateur:

  1. Utilisez les fonctions async.
  2. Apportez des modifications sur dom virtuel.
  3. Ne bloquez pas le thread principal pour votre long processus, utilisez une tâche asynchrone.
  4. Continuez votre tâche.

Pour le code OP:

protected async Task Submit()
{
    //Show Sending...
    cursorCSS = "";
    this.StateHasChanged();
    response = await Task.Run(()=> Service.Post(userModel)); //<--here!
    if (response.Errors.Any())
    {
        errorCss = "errorOn";
    }
    //turn sending off
    cursorCSS = "cursorSpinOff";
    this.StateHasChanged();
}

Exemple

async Task AsyncLongOperation()    // this is an async task
{
    spinning=true;
    await Task.Run(()=> LongOperation());  //<--here!
    currentCount++;
    spinning=false;
}

Comme vous pouvez le voir, StateHasChanged n'est pas nécessaire.

Effet:

enter image description here

@page "/counter"

<h1>Counter</h1>

<p>Current count: 
   @(spinning?"Incrementing .... (the spinning effect)":currentCount.ToString())
</p>

<button class="btn btn-primary" 
        @onclick="@IncrementCount">Click me</button>

<button class="btn  @(spinning?"btn-dark":"btn-primary") " 
        @onclick="@AsyncLongOperation">Click me Async</button>

@code {                     
    int currentCount = 0;
    bool spinning = false;
    void IncrementCount()
    {
        currentCount++;
    }

    async Task AsyncLongOperation()
    {
        spinning=true;
        await Task.Run(()=> LongOperation());  //<--here!
        currentCount++;
        spinning=false;
        await Task.CompletedTask;
    }

    void LongOperation() => Task.Delay(2000).Wait();
}

Prerendering côté spinner et côté serveur

Étant donné que les applications Blazor Server utilisent le pré-rendu, le spinner n'apparaîtra pas, pour afficher le spinner, l'opération longue doit être effectuée dans OnAfterRender .

Utilisez OnAfterRenderAsync sur OnInitializeAsync pour éviter un rendu côté serveur retardé

    // Don't do this
    //protected override async Task OnInitializedAsync()
    //{
    //    await LongOperation();
    //}

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await LongOperation();
            await Task.Run(()=> LongOperation());  //<--here!
            StateHasChanged();
        }
    }

Plus d'échantillons

En savoir plus sur la façon d'écrire Nice spinner que vous pouvez apprendre d'un projet open source BlazorPro.Spinkit , il contient des exemples intelligents comme celui-ci:

async Task TryLoadingData(Action<WeatherForecast[]> onSuccess, 
                          Action<Exception> onFaulted)
{
    isLoading = true;
    try
    {
        if (forceException)
        {
            throw new NotSupportedException();
        }
        var data = await Task.Run(() => WeatherService.GetForecast(DateTime.Now));
        isFaulted = false;
        onSuccess(data);
    }
    catch (Exception e)
    {
        isFaulted = true;
        onFaulted(e);
    }
    finally
    {
        isLoading = false;
    }
}
14
dani herrera

Ci-dessous le contenu du fichier FetchData.razor de Blazor Templates

  • Notez que le fichier contient deux parties: HTML mélangé avec C # (Razor) et code C # dans le bloc @code, dans lequel nous définissons un tableau d'objets WeatherForecast qui est appelé prévisions. Ce tableau contiendra les objets WeatherForecast renvoyés par un appel http, effectué dans la méthode OnInitAsync, au serveur.

    • Notez que l'instruction if (@if (forecasts == null)) vérifie si les objets WeatherForecast ont déjà été récupérés. Tant que la variable variable est nulle, le html <p><em>Loading...</em></p> est affiché. Vous pouvez ajouter ici autant de Html que vous le souhaitez, y compris des images, des filateurs, etc.

    • Une fois les prévisions affectées aux objets WeatherForecast, un tableau Html s'affiche avec les données récupérées

    J'espère que cela t'aides...

 @page "/fetchdata"
 @using BlazorHosted_CSharp.Shared
 @inject HttpClient Http

 <h1>Weather forecast</h1>

 <p>This component demonstrates fetching data from the server.</p>

 @if (forecasts == null)
 {
     <p><em>Loading...</em></p>
 }
 else
 {
     <table class="table">
         <thead>
             <tr>
                 <th>Date</th>
                 <th>Temp. (C)</th>
                 <th>Temp. (F)</th>
                 <th>Summary</th>
             </tr>
         </thead>
         <tbody>
             @foreach (var forecast in forecasts)
             {
                 <tr>
                     <td>@forecast.Date.ToShortDateString()</td>
                     <td>@forecast.TemperatureC</td>
                     <td>@forecast.TemperatureF</td>
                     <td>@forecast.Summary</td>
                 </tr>
             }
         </tbody>
     </table>
 }

 @code {
     WeatherForecast[] forecasts;

     protected override async Task OnInitAsync()
     {
         forecasts = await Http.GetJsonAsync<WeatherForecast[]>("api/SampleData/WeatherForecasts");
     }
 }
3
enet

Pour répondre à l'avis dans @ solution de daniherrera , il y a trois solutions plus élégantes proposées ici .

En bref :

  • Implémentez INotifyPropertyChanged au modèle et appelez StateHasChanged() sur une propriété d'événement PropertyChangedEventHandler du modèle.
  • Utilisez les délégués pour invoquer StateHasChanged() sur le modèle.
  • Ajoutez un paramètre EventCallBack<T> au composant ou à la page de la vue et affectez-le à la fonction cela devrait changer le rendu du composant et de ses parents. (StateHasChanged() n'est pas nécessaire dans celui-ci` )

La dernière option est la plus simple, flexible et de haut niveau, mais choisissez à votre convenance.

Dans l'ensemble, je vous conseille d'utiliser l'une de ces solutions présentées plus que la await Task.Delay(1); si la sécurité de votre application est un problème.

Edit: Après plus de lecture, ce lien fournit une explication solide sur la façon de gérer les événements en C #, principalement avec EventCallBack.

1
PepperTiger

Ne faites pas la même erreur que moi en testant le spinner d'attente à l'aide de Thread.Sleep (n).

protected override async Task OnInitializedAsync()
{
    // Thread.Sleep(3000); // By suspending current thread the browser will freeze.
    await Task.Delay(3000); // This is your friend as dani herrera pointed out. 
                      // It creates a new task that completes 
                      // after a specified number of milliseconds.

    forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
}
0
albin