13
votes

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

Dans mon application blazer, je passe un appel API à un serveur principal qui peut 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();
    }
}


0 commentaires

5 Réponses :


3
votes

Vous trouverez 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 l'appel http, effectué dans la méthode OnInitAsync, vers le 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 prévisions 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 spinners, etc.

    • Une fois les prévisions attribué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");
     }
 }


2 commentaires

J'ai oublié d'indiquer que l'utilisateur doit entrer un identifiant de recherche, puis cliquer sur un bouton d'envoi. C'est sur le bouton de soumission que j'ai besoin du spinner. Cela ne permettra pas à OnInitAsync de fonctionner.


@ David23g, consultez cet article. blog.vivensas.com/...



31
votes

Option 1: Utilisation de Task.Delay (1)
  • Utilisez une méthode asynchrone.
  • Utilisez await Task.Delay(1) ou await Task.Yield(); pour vider les modifications
    // Don't do this
    //protected override async Task OnInitializedAsync()
    //{
    //    await LongOperation();
    //}

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {            
            await Task.Run(()=> LongOperation());//<--or Task.Delay(0) without Task.Run
            StateHasChanged();
        }
    }

L'option 1 est une solution simple qui fonctionne bien mais qui ressemble à une astuce.

Option 2: Utilisation de Task.Run () (pas pour WebAssembly)

En janvier 2020. @Ed Charbeneau a publié le projet BlazorPro.Spinkit englobant de longs processus dans une tâche pour ne pas bloquer le thread:

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

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

Effet

un spinner chargeant des données

Prérendu côté serveur et spinner

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

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

private async Task AsyncLongFunc()    // this is an async task
{
    spinning=true;
    await Task.Delay(1);      // flushing changes. The trick!!
    LongFunc();               // non-async code
    currentCount++;
    spinning=false;
    await Task.Delay(1);      // changes are flushed again    
}

Plus d'échantillons

Apprenez-en plus sur la façon d'écrire un joli spinner que vous pouvez apprendre du projet open source BlazorPro.Spinkit , il contient des exemples intelligents.

Plus d'informations

Voir la réponse de Henk Holterman avec l'explication interne de Blazor.


12 commentaires

Quelle version de Blazor / du navigateur utilisez-vous pour cela. Sur FireFox (v60.5.0esr) et Chrome (75.0.3770.142) avec Blazor (installé il y a environ une semaine, devrait être en aperçu v6), cette vue se traduit par un navigateur qui se bloque.


@tobriand, juste testé sur l'aperçu côté serveur blazor7 et toujours en cours d'exécution.


Je me demande si c'est plutôt côté serveur que client. Il se peut que le runtime utilisé pour le côté serveur soit plus robuste pour async-with-tasks que async-with-IO, par exemple s'il s'agit d'un "vrai" .Net Core. J'exécute ce côté client (et j'essaie de déterminer exactement ce que Blazor doit être donné afin de produire proprement une opération de longue durée). Je vais essayer avec l'aperçu 7 et voir ...


@tobriand, peut-être que le problème est côté client avec une attente de délai de tâche. Appelez simplement vos opérations de longue durée au lieu de fausses opérations de démonstration.


Merci merci merci. tellement content d'avoir trouvé ça.


Je recherche depuis des heures et Task.Run est ce qui me manquait en renvoyant des données à partir d'un Entity Framework DBContext, dans une fonction asynchrone. Merci!


Merci beaucoup @HenkHolterman pour la modification du message. Soyez libre de l'améliorer si nécessaire.


Votre propre dernière modification en faisant deux options est bien meilleure. Je vais faire quelques petites retouches.


Salut @HenkHolterman, merci pour vos améliorations de réponse. J'apprécie cela.


Bien sûr @HenkHolterman, toujours en train d'apprendre de vous! Un jour je comprendrai les internes asynchrones.


Très bonne réponse. Surtout la partie sur OnAfterRender dans les projets Blazor côté serveur. Merci beaucoup.


@ n-decelop, crédits à Ed Charbeneau



1
votes

Ne faites pas la même erreur que moi en testant wait spinner en utilisant 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);
}


3 commentaires

Ça n'a pas l'air de marcher. J'obtiens l'avertissement "Parce que cet appel n'est pas attendu, l'exécution de la méthode actuelle se poursuit avant que l'appel ne soit terminé. Pensez à appliquer l'opérateur 'await' au résultat de l'appel."


Salut, je suppose que vous n'avez pas attendu l'appel -> attendez SomethingToAwait.SomeAsyncMethod ()


@ rugbrød, l'avertissement suggère que vous devez attendre "Task.Delay (3000)", sinon le retard ne se produira pas. J'ai modifié l'exemple pour corriger cet avertissement.



2
votes

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

En bref :

  • Implémentez INotifyPropertyChanged au modèle et StateHasChanged() sur une PropertyChangedEventHandler événement PropertyChangedEventHandler partir du modèle.
  • Utilisez des délégués pour appeler StateHasChanged() sur le modèle.
  • Ajoutez un EventCallBack<T> au composant ou à la page de la vue et affectez-le à la fonction qui doit 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, la plus flexible et la plus élevée, mais choisissez à votre convenance.

Dans l'ensemble, je conseillerai d'utiliser l'une de ces solutions présentées plus que l' await Task.Delay(1); un 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 .


0 commentaires

0
votes

Blazor Serverside - J'avais besoin d'appeler StateHasChanged () pour forcer le frontend à se mettre à jour afin que le spinner s'affiche avant que le code ne passe à l'appel ajax.

/* Show spinner */
carForm.ShowSpinner = true;

/* Force update of front end */
StateHasChanged();

/* Start long running API/Db call */
await _carRepository.Update(item);


0 commentaires