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();
}
}
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:
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!
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:
async
.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:
@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();
}
É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();
}
}
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;
}
}
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");
}
}
Pour répondre à l'avis dans @ solution de daniherrera , il y a trois solutions plus élégantes proposées ici .
En bref :
INotifyPropertyChanged
au modèle et appelez StateHasChanged()
sur une propriété d'événement PropertyChangedEventHandler
du modèle. StateHasChanged()
sur le modèle. 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
.
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);
}