在foreach循环Blazor中启动一个网络请求



我有一个html表,里面有设备;在线";。为了获得这个值,我们需要向具有设备Id的服务器发出HTTP获取请求。

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

为设备状态创建一个组件。在该组件中,调用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);
}
}

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

为什么?

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

那你该怎么办?

  1. 一个DI服务,用于获取和管理UI所需的数据集。从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;
}

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

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请求,所以请记住,这是有限制的。此处提供更多文档:从ASP.NET Core Blazor 调用web API

首先,确保您注册了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; }
}

如何显示结果数据取决于您。请参阅ASP.NET Core的Razor语法参考。例如:

<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>

解决方案的概要如下:

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

  2. 在Blazor页面中注入此服务,为设备ID 列表中的每个调用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代码。

相关内容

  • 没有找到相关文章

最新更新