Can you use IAsyncEnumerable in Razor pages to progressively display markup?

六月ゝ 毕业季﹏ 提交于 2020-06-10 16:54:50

问题


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?


回答1:


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>

OnInitializeAsync 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();            
        }
    }
}



回答2:


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


来源:https://stackoverflow.com/questions/57870709/can-you-use-iasyncenumerable-in-razor-pages-to-progressively-display-markup

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!