在 foreach 循环 Blazor 中触发网络请求
Posted
技术标签:
【中文标题】在 foreach 循环 Blazor 中触发网络请求【英文标题】:Fire off a network request in the foreach loop 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
【参考方案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 调用是一个非常糟糕的主意。
为什么?
-
您无法真正控制渲染进程及其运行时间。
您让渲染器在每个渲染周期多次执行缓慢而繁琐的任务。 Renderer 进程不是为数据访问而设计的。
那你该怎么办?
获取和管理 UI 所需数据集的 DI 服务。从 UI 中获取所有数据访问权限。如果您希望它“实时”,请使用计时器循环每 x 秒刷新一次数据。如果数据集已更改,则每次刷新都会引发DataSetChanged
事件。
组件注入服务并连接到DataSetChanged
事件。如果有新内容要显示,事件处理程序会调用 StateHasChanged
来刷新 UI。
这是一个非常基本的演示。
服务和数据类。将服务注册为 Scoped。
using System.Timers;
namespace ***.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】:解决方案的概要如下:
-
创建一个服务来检查特定 url 是在线还是离线(发出 http 请求 => 检查状态代码 => 返回特定于应用程序的响应)
public class StatusChecker
public Task<bool> CheckAsync(string deviceId)
// check status by pinging server and return the status
将此服务声明为作用域或单例(取决于您的逻辑的线程安全、隐私等)
在您的 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 代码。
【讨论】:
以上是关于在 foreach 循环 Blazor 中触发网络请求的主要内容,如果未能解决你的问题,请参考以下文章
forEach 循环中的 promise.all —— 一切立即触发
将数据从枚举传递到在ForEach循环中触发的sheet中。