【问题标题】:Fire off a network request in the foreach loop Blazor在 foreach 循环 Blazor 中触发网络请求
【发布时间】:2022-01-11 09:43:57
【问题描述】:

我有一个包含设备的 html 表。我的其中一个列名为“在线”。为了获取这个值,我们需要使用设备 ID 向服务器发出 HTTP 获取请求。

如何在 Blazor 中执行此操作,然后根据 HTTP 响应使用响应将正确的行更新为“是”或“否”?

【问题讨论】:

  • 你也要定期更新吗?
  • 暂时没有,除非用户刷新页面,但这可能会很好地作为第 2 步添加
  • 您查看过此文档吗? Call a web API from ASP.NET Core Blazor
  • 问题不在于 http 调用,它使用结果来更新表格行。
  • Razor 中有所有标准结构关键字,如 Razor 中的@for@foreach@if(以及更多)。见Razor syntax reference for ASP.NET Core

标签: blazor blazor-webassembly http-get


【解决方案1】:

为设备状态创建一个组件。在该组件中调用 OnInitializedAsync 中的 Async 方法。

<table>
  @foreach(var device in devices)
  {
    <tr @key=device >
        <td> ... </td>
        <td> ... </td>
        <td> ... </td>
        <td><DeviceStatus Device=@device /></td>
    </tr>
  }
</table>

DeviceStatus.razor

@IsOnline

@code {
    private bool online = false;
    private string IsOnline => online ? "Yes" : "No";

    [Parameter]
    public Device Device { get; set; }

    protected override async Task OnInitializedAsync()
    {
        online = await SomeService.GetOnlineStatus(Device);
    }
}

【讨论】:

  • 1++。我会那样做...
【解决方案2】:

在渲染器循环中进行 API 调用是一个非常糟糕的主意。

为什么?

  1. 您无法真正控制渲染进程及其运行时间。
  2. 您让渲染器在每个渲染周期多次执行缓慢而繁琐的任务。 Renderer 进程不是为数据访问而设计的。

那你该怎么办?

  1. 获取和管理 UI 所需数据集的 DI 服务。从 UI 中获取所有数据访问权限。如果您希望它“实时”,请使用计时器循环每 x 秒刷新一次数据。如果数据集已更改,则每次刷新都会引发DataSetChanged 事件。

  2. 组件注入服务并连接到DataSetChanged 事件。如果有新内容要显示,事件处理程序会调用 StateHasChanged 来刷新 UI。

这是一个非常基本的演示。

服务和数据类。将服务注册为 Scoped。

using System.Timers;

namespace StackOverflow.Server;

public class RTService: IDisposable
{
    private System.Timers.Timer _timer = new System.Timers.Timer(3000);

    public readonly List<RTData> Data = new List<RTData>();

    public event EventHandler? DataChanged;

    public RTService()
    {
        {
            Data.Add(new RTData { Name = "Device 1", Live = true });
            Data.Add(new RTData { Name = "Device 2", Live = false });
            Data.Add(new RTData { Name = "Device 3", Live = true });
            Data.Add(new RTData { Name = "Device 4", Live = false });
        }
        _timer.Elapsed += OnTimerElapsed;
        _timer.Start();
        _timer.AutoReset = true;
    }

    private async void OnTimerElapsed(object? sender, ElapsedEventArgs e)
    {
        // simulated get the data from the network
        await Task.Delay(1000);
        Data.ForEach(item => item.NewStatus = !item.Live);
        if (Data.Any(item => item.Live != item.NewStatus))
        {
            Data.ForEach(item => item.Live = item.NewStatus);
            DataChanged?.Invoke(this, EventArgs.Empty);
        }
    }

    public void Dispose()
    {
        _timer.Elapsed -= OnTimerElapsed;
    }
}

public class RTData
{
    public string? Name { get; set; }

    public bool Live { get; set; }

    public bool NewStatus { get; set; }

}

演示页面:

@page "/"
<h3>Real-Time Device Status</h3>
<div class="container">
    @foreach (var device in this.rTService.Data)
    {
        <div class="row">
            <div class="col-8">
                @device.Name
            </div>
            <div class="col-4">
                <span class="badge @this.ButtonCss(device.Live)">@this.ButtonText(device.Live)</span>
            </div>
        </div>
    }
</div>

@code {
    [Inject] private RTService rTService { get; set; }

    private string ButtonCss(bool live) => live ? "bg-success" : "bg-danger";
    private string ButtonText(bool live) => live ? "On" : "Off";

    protected override Task OnInitializedAsync()
    {
        this.rTService.DataChanged += this.OnDataChanged;
        return base.OnInitializedAsync();
    }

    private void OnDataChanged(object? sender, EventArgs e)
        => this.InvokeAsync(StateHasChanged);

    public void Dispose()
        => this.rTService.DataChanged -= this.OnDataChanged;
}

【讨论】:

  • 1++。我同意你的观点并理解你的意思。不确定OP。结合 Brian Parker 的代码提出的服务(实现状态和观察者模式)可能是一个可行的解决方案。
  • @HenkHolterman - 感谢代码错字编辑。
【解决方案3】:

由于您的问题涉及到数据更改在显示原始数据之后,这可能会有所帮助:您可以在模型中添加一个标志以指示正在加载数据:

    List<TestUser>? Users { get; set; }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (this.Users == null)
        {
            this.Users = await this.GetUsersAsync();
            // Notify that properties changed
            this.StateHasChanged();
        }

        var onlineTasks = new List<Task>();
        foreach (var user in this.Users)
        {
            onlineTasks.Add(Task.Run(async () =>
            {
                user.IsOnline = await this.GetIsOnlineAsync(user.Id);
            }));
        }

        await Task.WhenAll(onlineTasks);

        // Notify that properties changed
        this.StateHasChanged();
    }

    // Implement your own API call
    static readonly Random random = new Random();
    private async Task<List<TestUser>> GetUsersAsync()
    {
        await Task.Delay(100);
        return new()
            {
                new() { Id = 0, Name = "Foo", },
                new() { Id = 1, Name = "Bar", },
                new() { Id = 2, Name = "XYZ", },
            };
    }

    // Implement your own API call using HttpClient
    private async Task<bool> GetIsOnlineAsync(int id)
    {
        // Simulate a random delay
        await Task.Delay((int)(random.NextDouble() * 3000));

        // Here I just use this as example
        return id % 2 == 0;
    }


    class TestUser
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public bool? IsOnline { get; set; } // Null = no data yet
    }

显示数据取决于数据状态:

<table>
    <thead>
        <tr>
            <th>Name</th>
            <th>Is Online?</th>
        </tr>
    </thead>
    <tbody>
        @if (this.Users != null)
        {
            foreach (var user in this.Users)
            {
                <tr>
                    <td>@(user.Name)</td>
                    <td>
                        @if (user.IsOnline == null)
                        {
                            <span class="text-muted">Loading...</span>
                        }
                        else if (user.IsOnline.Value)
                        {
                            <span class="text-success">Online!</span>
                        }
                        else
                        {
                            <span class="text-danger">Offline</span>
                        }
                    </td>
                </tr>
            }
        }
    </tbody>
</table>

您可以像在服务器端一样使用HttpClient 发出 HTTP 请求,但显然它会被转换为来自浏览器的 HTTP 请求,因此请记住存在限制。更多文档在这里:Call a web API from ASP.NET Core Blazor

首先,确保您注册了 HttpClient 服务。在默认模板中应该已经有一个,但如果没有:

builder.Services.AddScoped(sp => 
    new HttpClient
    {
        BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
    });

在您的 Blazor .razor 页面中:

// Inject the HttpClient service
@inject HttpClient Http;

// ...

// Request
List<TestUser>? users;

protected override async Task OnInitializedAsync()
{
    var resObj = await this.Http.GetFromJsonAsync<TestResponse>("https://reqres.in/api/users?page=2");
    this.users = resObj?.Data;
}

class TestResponse
{

    public List<TestUser> Data { get; set; }

}

class TestUser
{
    public int Id { get; set; }
    public string First_Name { get; set; }
    public string Last_Name { get; set; }
}

如何显示结果数据由您决定。见Razor syntax reference for ASP.NET Core。例如:

<table>
    <thead>
        <tr>
            <th>Name</th>
            <th>ID > 10?</th>
        </tr>
    </thead>
    <tbody>
        @if (this.users != null)
        {
            foreach (var user in this.users)
            {
                <tr>
                    <td>@(user.First_Name) @(user.Last_Name)</td>
                    <td>@(user.Id > 10 ? "Yes" : "No")</td>
                </tr>
            }
        }
    </tbody>
</table>

【讨论】:

    【解决方案4】:

    解决方案的概要如下:

    1. 创建一个服务来检查特定 url 是在线还是离线(发出 http 请求 => 检查状态代码 => 返回特定于应用程序的响应)
    public class StatusChecker
    {
       public Task<bool> CheckAsync(string deviceId)
       {
         // check status by pinging server and return the status
       }
    }
    
    1. 将此服务声明为作用域或单例(取决于您的逻辑的线程安全、隐私等)

    2. 在您的 Blazor 页面中注入此服务,为您的 deviceIds 列表中的每个调用 CheckAsync

     @inject StatusChecker Checker
         @if(IsCheckingStatus)    { 
             Checking.....   }\else    {
          <table>     <tbody>
      
        foreach(var device in DeviceIds)   {
          <tr>
            <td @device </td
            <td @statuses[device] </td> </tr>   }      </tbody>      </table>  // if you want to manually refresh again
          <button @onclick="CheckStatuses"Refresh</button>
           }
      
      
         @code {    
           Dictionary<string,string statuses = new ();    
           bool IsCheckingStatus;    
           [Parameter]    
           public List<string DeviceIds {get;set;}
      
         protected override async Task OnInitializedAsync()    {
            await CheckStatuses();    }
      
         async Task CheckStatuses()    {
            IsCheckingStatus = true;
            foreach(var devId in DeviceIds)
            {
              var stat = await Checker.CheckAsync(devId);
              statuses[devId] = stat ? "online" : "offline";
            }
            IsCheckingStatus = false;    }
         }
    

    附:有人可以格式化吗? SO 根本无法在 Edge 上格式化 Blazor 代码。

    【讨论】:

      猜你喜欢
      • 2021-02-15
      • 2020-02-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-24
      • 2021-04-18
      • 1970-01-01
      • 2023-04-10
      相关资源
      最近更新 更多