I've been playing around with Blazor and the IAsyncEnumerable feature in C# 8.0. Is it possible to use IAsyncEnumerable and await within Razor Pages to progressively display markup with data?
Example service:
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;
}
}
Example in razor page:
@await foreach(var game in GameService.GetGames())
{
<p>@game</p>
}
This gives error CS4033: The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task'.
Any ideas if this is possible?
The biggest difference between Razor and Blazor is that Razor is a markup language with C#, while Blazor is the framework that lets you run C# code and use the Razor view engine in the browser.
Razor is a templating engine that combines C# with HTML to build dynamic web content. Blazor is a component-based, single-page app framework for building client-side web apps using . NET that works well with all modern browsers via WebAssembly for client-side Blazor.
Razor components can be integrated into Razor Pages and MVC apps in a hosted Blazor WebAssembly solution.
ASP.NET Razor Pages (Server-side) Razor Pages (not to be confused with Razor Views, which are used in MVC) also does all its User Interface (UI) rendering on the server and returns HTML to the browser. At a high level, this looks just like MVC.
Server-side Razor allows what you describe. This video describes the code in this Github repo that shows how to use IAsyncEnumerable by modifying the ForecastService
example in server-side Blazor template.
Modifying the service itself is easy, and actually results in cleaner code :
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)]
};
}
}
The Blazor page on the other hand is more complicated. It's not just that the loop would have to finish before the HTML was displayed, you can't use await foreach
in the page itself because it's not asynchronous. You can only define asynchronous methods in the code block.
What you can do, is enumerate the IAsyncEnumerable and notify the page to refresh itself after each change.
The rendering code itself doesn't need to change :
<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
needs to call StateHasChanged()
after receiving each item :
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();
}
}
In the question's example, incoming games could be stored in a List, leaving the rendering code unchanged :
@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();
}
}
}
You can't write await foreach
on .razor
template code. But, as workaround, you can write it at @code
section:
@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();
}
}
}
Effect:
Yielding data:
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;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With