Blazor-在API调用上显示等待或微调

Dav*_*23g 3 c# blazor

在我的blazer应用程序中,我正在对后端服务器进行api调用,这可能需要一些时间。我需要向用户显示反馈,等待光标或“旋转”图像。在Blazor中如何进行?

我尝试使用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();
    }
}
Run Code Online (Sandbox Code Playgroud)

Hen*_*man 12

除了@dani 的回答之外,我还想指出这里有两个独立的问题,将它们分开是值得的。

  1. 何时致电StateHasChanged()

Blazor 将(概念上)在初始化之后以及事件之前和之后调用 StateHasChanged()。这意味着您通常不需要调用它,只有当您的方法有几个不同的步骤并且您想要在中间更新 UI 时才需要调用它。旋转器就是这种情况。

async void当您使用“即发即弃”(fire-and-forget) ( ) 或当更改来自不同的源(例如计时器或来自程序中另一层的事件)时,您确实需要调用它。

  1. 如何确保调用后 UI 已更新StateHasChanged()

StateHasChanged() 本身不会更新 UI。它只是将渲染操作排队。您可以将其视为设置“脏标志”。

一旦渲染引擎再次在其线程上运行,Blazor 将更新 UI。与任何其他 UI 框架非常相似,所有 UI 操作都必须在主线程上完成。但是您的事件也(最初)在同一线程上运行,从而阻塞渲染器。

要解决此问题,请通过返回 来确保您的事件是异步的 async Task。Blazor 完全支持这一点。不使用。仅当您不需要异步行为时才async void使用。void

2.1 使用异步操作

当您的方法在 StateHasChanged() 之后快速等待异步 I/O 操作时,您就完成了。控制权将返回到渲染引擎,并且您的 UI 将更新。

 statusMessage = "Busy...";
 StateHasChanged();
 response = await SomeLongCodeAsync();  // show Busy
 statusMessage = "Done.";
Run Code Online (Sandbox Code Playgroud)

2.2 插入一个小的异步动作

当您的代码是 CPU 密集型时,它不会快速释放主线程。当您调用某些外部代码时,您并不总是知道它到底有多“异步”。所以我们有一个流行的技巧:

 statusMessage = "Busy...";
 StateHasChanged();
 await Task.Delay(1);      // flush changes - show Busy
 SomeLongSynchronousCode();
 statusMessage = "Done.";
Run Code Online (Sandbox Code Playgroud)

更合乎逻辑的版本是使用Task.Yield(),但在 WebAssembly 上往往会失败。

2.3 使用额外的线程Task.Run()

当您的事件处理程序需要调用一些非异步代码(例如 CPU 密集型工作)并且您位于 Blazor-Server 上时,您可以使用 Task.Run() 申请一个额外的线程池:

 statusMessage = "Busy...";
 StateHasChanged();
 await Task.Run( _ => SomeLongSynchronousCode());  // run on other thread
 statusMessage = "Done.";
Run Code Online (Sandbox Code Playgroud)

当您在 Blazor-WebAssembly 上运行它时,它没有任何效果。浏览器环境中没有可用的“额外线程”。

当您在 Blazor-Server 上运行此程序时,您应该意识到使用更多线程可能会损害您的可扩展性。如果您计划在服务器上运行尽可能多的并发客户端,那么这是一种反优化。

当你想尝试时:

void SomeLongSynchronousCode()
{ 
   Thread.Sleep(3000);
}

Task SomeLongCodeAsync()
{ 
   return Task.Delay(3000);
}
Run Code Online (Sandbox Code Playgroud)


Bri*_*ana 11

围绕 StateHasChanged() 有很多精彩的讨论,但为了回答 OP 的问题,这里有另一种普遍实现旋转器的方法,用于 HttpClient 对后端 API 的调用。

此代码来自 Blazor WebAssembly 应用程序...

程序.cs

public static async Task Main(string[] args)
{
    var builder = WebAssemblyHostBuilder.CreateDefault(args);
    builder.RootComponents.Add<App>("#app");
    
    builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
    builder.Services.AddScoped<SpinnerService>();
    builder.Services.AddScoped<SpinnerHandler>();
    builder.Services.AddScoped(s =>
    {
        SpinnerHandler spinHandler = s.GetRequiredService<SpinnerHandler>();
        spinHandler.InnerHandler = new HttpClientHandler();
        NavigationManager navManager = s.GetRequiredService<NavigationManager>();
        return new HttpClient(spinHandler)
        {
            BaseAddress = new Uri(navManager.BaseUri)
        };
    });

    await builder.Build().RunAsync();
}
Run Code Online (Sandbox Code Playgroud)

SpinnerHandler.cs
注意:请记住取消注释人为延迟。如果您在 Visual Studio 中使用现成的 Webassemble 模板,请单击“天气预报”以查看微调器的实际操作演示。

public class SpinnerHandler : DelegatingHandler
{
    private readonly SpinnerService _spinnerService;

    public SpinnerHandler(SpinnerService spinnerService)
    {
        _spinnerService = spinnerService;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        _spinnerService.Show();
        //await Task.Delay(3000); // artificial delay for testing
        var response = await base.SendAsync(request, cancellationToken);
        _spinnerService.Hide();
        return response;
    }
}
Run Code Online (Sandbox Code Playgroud)

SpinnerService.cs

public class SpinnerService
{
    public event Action OnShow;
    public event Action OnHide;

    public void Show()
    {
        OnShow?.Invoke();
    }

    public void Hide()
    {
        OnHide?.Invoke();
    }
}
Run Code Online (Sandbox Code Playgroud)

MainLayout.razor

@inherits LayoutComponentBase

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <div class="main">
        <div class="top-row px-4">
            <a href="http://blazor.net" target="_blank" class="ml-md-auto">About</a>
        </div>

        <div class="content px-4">
            @Body
            <Spinner />
        </div>
    </div>
</div>
Run Code Online (Sandbox Code Playgroud)

Spinner.razor
注意:要添加一些变化,您可以在 OnIntialized() 方法中生成一个随机数,并在 div 内使用 switch 语句来选择随机微调器类型。在此方法中,对于每个 HttpClient 请求,最终用户将观察到随机旋转器类型。为了简洁起见,该示例已被修剪为仅一种类型的旋转器。

@inject SpinnerService SpinnerService

@if (isVisible)
{
    <div class="spinner-container">
        <Spinner_Wave />
    </div>
}

@code
{
    protected bool isVisible { get; set; }

    protected override void OnInitialized()
    {
        SpinnerService.OnShow += ShowSpinner;
        SpinnerService.OnHide += HideSpinner;
    }

    public void ShowSpinner()
    {
        isVisible = true;
        StateHasChanged();
    }

    public void HideSpinner()
    {
        isVisible = false;
        StateHasChanged();
    }

    public void Dispose()
    {
        SpinnerService.OnShow -= ShowSpinner;
        SpinnerService.OnHide -= HideSpinner;
    }
}
Run Code Online (Sandbox Code Playgroud)

Spinner-Wave.razor
来源:https ://tobiasahlin.com/spinkit/
注意:此旋转套件有一个 Nuget 包。Nuget 包的缺点是您无法直接访问 CSS 来进行调整。在这里,我调整了微调器的大小,并设置背景颜色以匹配网站的主要颜色,如果您在整个网站中使用 CSS 主题(或者可能是多个 CSS 主题),这会很有帮助

@* Credit: https://tobiasahlin.com/spinkit/ *@

<div class="spin-wave">
    <div class="spin-rect spin-rect1"></div>
    <div class="spin-rect spin-rect2"></div>
    <div class="spin-rect spin-rect3"></div>
    <div class="spin-rect spin-rect4"></div>
    <div class="spin-rect spin-rect5"></div>
</div>
<div class="h3 text-center">
    <strong>Loading...</strong>
</div>

<style>
    .spin-wave {
        margin: 10px auto;
        width: 200px;
        height: 160px;
        text-align: center;
        font-size: 10px;
    }

        .spin-wave .spin-rect {
            background-color: var(--primary);
            height: 100%;
            width: 20px;
            display: inline-block;
            -webkit-animation: spin-waveStretchDelay 1.2s infinite ease-in-out;
            animation: spin-waveStretchDelay 1.2s infinite ease-in-out;
        }

        .spin-wave .spin-rect1 {
            -webkit-animation-delay: -1.2s;
            animation-delay: -1.2s;
        }

        .spin-wave .spin-rect2 {
            -webkit-animation-delay: -1.1s;
            animation-delay: -1.1s;
        }

        .spin-wave .spin-rect3 {
            -webkit-animation-delay: -1s;
            animation-delay: -1s;
        }

        .spin-wave .spin-rect4 {
            -webkit-animation-delay: -0.9s;
            animation-delay: -0.9s;
        }

        .spin-wave .spin-rect5 {
            -webkit-animation-delay: -0.8s;
            animation-delay: -0.8s;
        }

    @@-webkit-keyframes spin-waveStretchDelay {
        0%, 40%, 100% {
            -webkit-transform: scaleY(0.4);
            transform: scaleY(0.4);
        }

        20% {
            -webkit-transform: scaleY(1);
            transform: scaleY(1);
        }
    }

    @@keyframes spin-waveStretchDelay {
        0%, 40%, 100% {
            -webkit-transform: scaleY(0.4);
            transform: scaleY(0.4);
        }

        20% {
            -webkit-transform: scaleY(1);
            transform: scaleY(1);
        }
    }
</style>
Run Code Online (Sandbox Code Playgroud)

很美丽

结果动画

  • 谢谢(你的)信息!我最终向 SpinnerHandler 类添加了一个 static int _callCounter 成员变量。它在调用 SendAsync 之前递增,并在调用 SendAsync 之后递减。然后,如果 _callCounter 为 0,我只调用 _spinnerService.Hide() 。这并不完美,因为如果连续进行多个调用并且第一个调用在第二个调用开始之前完成,它会有点闪烁,但它可以工作非常适合并发通话,可以很好地满足我的需求。 (2认同)

dan*_*era 7

Blazor使用虚拟dom,该框架跟踪更改并仅在不阻止主线程时发送更改。这就是我允许Blazor将更改刷新到UI的方式:

  1. 使用async功能。
  2. 在虚拟dom上进行更改。
  3. 取消阻止主线程(我使用await Task.Delay(1)
  4. 继续您的任务。

async Task AsyncLongFunc()    // this is an async task
{
    spinning=true;
    await Task.Delay(1);      // changes are flushed
    LongFunc();               // usually with a wait ( is a web request)
    currentCount++;
    spinning=false;
    await Task.CompletedTask; // just to avoid non await warning.
}
Run Code Online (Sandbox Code Playgroud)

如您所见,StateHasChanged不需要。

注意:如果有更简单的方法或更优雅的解决方案,请使用IDK。

影响:

在此处输入图片说明

页代码(已编辑,符合netcore3 Preview6)

@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="@AsyncLongFunc">Click me Async</button>

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

    async Task AsyncLongFunc()
    {
        spinning=true;
        await Task.Delay(1);
        LongFunc();
        currentCount++;
        spinning=false;
        await Task.CompletedTask;
    }

    void LongFunc() => Task.Delay(2000).Wait();
}
Run Code Online (Sandbox Code Playgroud)

  • 谢谢你,谢谢你,谢谢你。很高兴我找到了这个。 (2认同)