您可以在 Razor 页面中使用 IAsyncEnumerable 来逐步显示标记吗?
Can you use IAsyncEnumerable in Razor pages to progressively display markup?
我一直在研究 Blazor 和 C# 8.0 中的 IAsyncEnumerable 功能。是否可以在 Razor Pages 中使用 IAsyncEnumerable 和 await 来逐步显示带有数据的标记?
示例服务:
private static readonly string[] games = new[] { "Call of Duty", "Legend of Zelda", "Super Mario 64" };
public async IAsyncEnumerable<string> GetGames()
{
foreach (var game in games)
{
await Task.Delay(1000);
yield return game;
}
}
razor 页面中的示例:
@await foreach(var game in GameService.GetGames())
{
<p>@game</p>
}
这会产生错误 CS4033:'await' 运算符只能在异步方法中使用。考虑使用 'async' 修饰符标记此方法并将其 return 类型更改为 'Task'。
有什么想法可以吗?
服务器端 Razor 允许您描述的内容。 This video describes the code in this Github repo 展示了如何通过修改服务器端 Blazor 模板中的 ForecastService
示例来使用 IAsyncEnumerable。
修改服务本身很容易,实际上代码更简洁:
public async IAsyncEnumerable<WeatherForecast> GetForecastAsync(DateTime startDate)
{
var rng = new Random();
for(int i=0;i<5;i++)
{
await Task.Delay(200);
yield return new WeatherForecast
{
Date = startDate.AddDays(i),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
};
}
}
另一方面,Blazor 页面更复杂。不仅循环必须在显示 HTML 之前完成,您 不能 在页面本身中使用 await foreach
因为它不是异步的。您只能在代码块中定义异步方法。
您可以做的是枚举 IAsyncEnumerable 并通知页面在每次更改后自行刷新。
渲染代码本身不需要改变:
<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>
OnInitializedAsync
需要在收到每个项目后调用 StateHasChanged()
:
List<WeatherForecast> forecasts;
protected override async Task OnInitializedAsync()
{
forecasts =new List<WeatherForecast>();
await foreach(var forecast in ForecastService.GetForecastAsync(DateTime.Now))
{
forecasts.Add(forecast);
this.StateHasChanged();
}
}
在问题的例子中,传入的游戏可以存储在一个列表中,保持渲染代码不变:
@foreach(var game in games)
{
<p>@game</p>
}
@code {
List<string> games;
protected override async Task OnInitializedAsync()
{
games =new List<games>();
await foreach(var game in GameService.GetGamesAsync())
{
games.Add(game);
this.StateHasChanged();
}
}
}
您不能在 .razor
模板代码上编写 await foreach
。但是,作为解决方法,您可以将其写在 @code
部分:
@if (@gamesUI == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Game</th>
</tr>
</thead>
<tbody>
@foreach (var game in gamesUI) // <--- workaround
{
<tr>
<td>@game</td>
</tr>
}
</tbody>
</table>
}
@code {
List<string> gamesUI; // <--- workaround
protected override async Task OnInitializedAsync()
{
gamesUI = new List<string>();
await foreach(var game in GService.GetgamesAsync() )
{
gamesUI.Add(game);
this.StateHasChanged();
}
}
}
效果:
屈服数据:
private static readonly string[] games = new[] { "Call of Duty", "Legend of Zelda", "Super Mario 64", "Bag man" };
public async IAsyncEnumerable<string> GetgamesAsync()
{
var rng = new Random();
foreach (var game in games)
{
await Task.Delay(1000);
yield return game;
}
}
我一直在研究 Blazor 和 C# 8.0 中的 IAsyncEnumerable 功能。是否可以在 Razor Pages 中使用 IAsyncEnumerable 和 await 来逐步显示带有数据的标记?
示例服务:
private static readonly string[] games = new[] { "Call of Duty", "Legend of Zelda", "Super Mario 64" };
public async IAsyncEnumerable<string> GetGames()
{
foreach (var game in games)
{
await Task.Delay(1000);
yield return game;
}
}
razor 页面中的示例:
@await foreach(var game in GameService.GetGames())
{
<p>@game</p>
}
这会产生错误 CS4033:'await' 运算符只能在异步方法中使用。考虑使用 'async' 修饰符标记此方法并将其 return 类型更改为 'Task'。
有什么想法可以吗?
服务器端 Razor 允许您描述的内容。 This video describes the code in this Github repo 展示了如何通过修改服务器端 Blazor 模板中的 ForecastService
示例来使用 IAsyncEnumerable。
修改服务本身很容易,实际上代码更简洁:
public async IAsyncEnumerable<WeatherForecast> GetForecastAsync(DateTime startDate)
{
var rng = new Random();
for(int i=0;i<5;i++)
{
await Task.Delay(200);
yield return new WeatherForecast
{
Date = startDate.AddDays(i),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
};
}
}
另一方面,Blazor 页面更复杂。不仅循环必须在显示 HTML 之前完成,您 不能 在页面本身中使用 await foreach
因为它不是异步的。您只能在代码块中定义异步方法。
您可以做的是枚举 IAsyncEnumerable 并通知页面在每次更改后自行刷新。
渲染代码本身不需要改变:
<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>
OnInitializedAsync
需要在收到每个项目后调用 StateHasChanged()
:
List<WeatherForecast> forecasts;
protected override async Task OnInitializedAsync()
{
forecasts =new List<WeatherForecast>();
await foreach(var forecast in ForecastService.GetForecastAsync(DateTime.Now))
{
forecasts.Add(forecast);
this.StateHasChanged();
}
}
在问题的例子中,传入的游戏可以存储在一个列表中,保持渲染代码不变:
@foreach(var game in games)
{
<p>@game</p>
}
@code {
List<string> games;
protected override async Task OnInitializedAsync()
{
games =new List<games>();
await foreach(var game in GameService.GetGamesAsync())
{
games.Add(game);
this.StateHasChanged();
}
}
}
您不能在 .razor
模板代码上编写 await foreach
。但是,作为解决方法,您可以将其写在 @code
部分:
@if (@gamesUI == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Game</th>
</tr>
</thead>
<tbody>
@foreach (var game in gamesUI) // <--- workaround
{
<tr>
<td>@game</td>
</tr>
}
</tbody>
</table>
}
@code {
List<string> gamesUI; // <--- workaround
protected override async Task OnInitializedAsync()
{
gamesUI = new List<string>();
await foreach(var game in GService.GetgamesAsync() )
{
gamesUI.Add(game);
this.StateHasChanged();
}
}
}
效果:
屈服数据:
private static readonly string[] games = new[] { "Call of Duty", "Legend of Zelda", "Super Mario 64", "Bag man" };
public async IAsyncEnumerable<string> GetgamesAsync()
{
var rng = new Random();
foreach (var game in games)
{
await Task.Delay(1000);
yield return game;
}
}