web-dev-qa-db-ja.com

Blazor-API呼び出しで待機またはスピナーを表示する

私のブレザーアプリでは、時間がかかる可能性のあるバックエンドサーバーに対してapi呼び出しを行っています。ユーザーへのフィードバック、待機カーソル、または「スピナー」画像を表示する必要があります。これはブレイザーでどのように行われますか?

CSSを使用してCSSのオンとオフを切り替えましたが、呼び出しが完了するまでページは更新されません。任意の提案をいただければ幸いです。

@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

2020年1月に編集@ Ed Charbenea で正しい方法を公開 BlazorPro.Spinkitプロジェクト :スレッドをブロックしないように長いプロセスをタスクに含める。彼の説明の前に、私はawait Task.Delay(1);を使用してUIの変更をフラッシュしましたが、今、正しい方法はここに公開されています:


簡潔な答え

LongOperation()Taskであることを確認します。そうでない場合は、Taskで囲み、それを待ちます。

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

詳細な回答

Blazorは仮想domで動作し、フレームワークは変更を追跡し、メインスレッドがブロックされていない場合にのみ変更を送信します。これは私がBlazorに変更をUIにフラッシュさせる方法です:

  1. async関数を使用します。
  2. 仮想domに変更を加えます。
  3. 長いプロセスでメインスレッドをブロックしないでください。非同期タスクを使用してください。
  4. タスクを続行します。

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();
}

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

ご覧のとおり、StateHasChangedは必要ありません。

効果:

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();
}

スピナーとサーバー側の事前レンダリング

Blazor Serverアプリは事前レンダリングを使用するため、スピナーは表示されません。スピナーを表示するには、長い操作をOnAfterRenderで実行する必要があります。

OnInitializeAsyncではなくOnAfterRenderAsyncを使用して、サーバー側レンダリングの遅延を回避します。

    // 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();
        }
    }

その他のサンプル

オープンソースプロジェクトから学ぶことができる、Niceスピナーの作成方法の詳細 BlazorPro.Spinkit には、次のような巧妙なサンプルが含まれています。

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

以下は、BlazorテンプレートからのFetchData.razorファイルの内容です。

  • このファイルには2つの部分が含まれていることに注意してください。C#と混合されたHTML(Razor)、および@codeブロック内のC#コードでは、予測と呼ばれるWeatherForecastオブジェクトの配列を定義します。この配列は、OnInitAsyncメソッドで行われたhttp呼び出しからサーバーに返されるWeatherForecastオブジェクトを保持します。

    • Ifステートメント(@if (forecasts == null))がWeatherForecastオブジェクトが既に取得されているかどうかを確認していることに注意してください。変数の予測がnullである限り、html <p><em>Loading...</em></p> 表示されています。ここには、画像、スピナーなどを含め、好きなだけHTMLを追加できます。

    • 天気予報がWeatherForecastオブジェクトに割り当てられると、Htmlテーブルが取得されたデータとともに表示されます

    お役に立てれば...

 @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

@ daniherrera's solution の通知に答えるために、3つのよりエレガントなソリューションが提案されています here

要するに :

  • モデルに実装INotifyPropertyChangedして呼び出しStateHasChanged()onPropertyChangedEventHandlerモデルのイベントプロパティ。
  • デリゲートを使用してモデルを呼び出すStateHasChanged()
  • a_EventCallBack<T>_パラメータをビューのコンポーネントまたはページに追加し、それを次の関数に割り当てます。コンポーネントとその親のレンダリングを変更する必要があります。StateHasChanged()はありませんこれで必要 `)

最後のオプションは、最も単純で柔軟性があり、高レベルですが、必要に応じて選択してください。

全体として、アプリのセキュリティが懸念される場合は、await Task.Delay(1);ソリューションよりも提示されたソリューションの1つを使用することをお勧めします。

編集:さらに読んだ後、 このリンク はC#でイベントを処理する方法について、主にEventCallBack

1
PepperTiger

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