dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.19k stars 9.93k forks source link

Unable to asynchronously load data into Blazor components #17755

Closed k05mang closed 4 years ago

k05mang commented 4 years ago

I started using Blazor recently and was trying to figure out how to asynchronously load data onto the page. Normally I would do this with ajax in javascript, put up a loading message or something and render the data from the server when it was ready. This functionality is suppose to be easily achievable with OnInitializedAsync. But regardless of my attempts the page always loads synchronously. Clicking the tab causes the browser to wait until the page is fully loaded from the server. I thought I was doing something wrong with my code, so I decided to load up the default Blazor Server App project and even that is failing to do what its supposed to. The code below is from the FetchData page of the blazor default project, I tweaked the number of elements to render from 5 to 5000 for longer delays to test this.

FetchData.razor

@page "/fetchdata"

@using BlazorApp1.Data
@using System.Diagnostics
@inject WeatherForecastService ForecastService

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from a service.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <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>
}

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
    }
}

WeatherForecastService.cs

using System;
using System.Linq;
using System.Threading.Tasks;

namespace BlazorApp1.Data
{
    public class WeatherForecastService
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
        {
            var rng = new Random();
            return Task.FromResult(Enumerable.Range(1, 5000).Select(index => new WeatherForecast
            {
                Date = startDate.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            }).ToArray());
        }
    }
}

My expectation was that the Loading... text in FetchData.razor is supposed to display until the call ForecastService.GetForecastAsync(DateTime.Now) completes. After which the page re-renders and shows the table with the data. But when clicking the FetchData tab in the app, the browser hangs and waits for the code to fully execute. I'm running this in VS 2019 on Windows 10. Am I missing something? Shouldn't Loading... show while the async function executes?

YordanYanakiev commented 4 years ago

Maybe it is connected to lack of actual multithreading in Blazor.

willdean commented 4 years ago

The problem you're seeing is not with Blazor, it's that GetForecastAsync isn't actually asynchronous, so if you increase the Enumerable.Range size, it just blocks for longer.

If, instead of increasing the size of the enumerable, you change it to this:

        public async Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
        {
            await Task.Delay(5000);
            var rng = new Random();
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = startDate.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            }).ToArray();
        }

You'll see the effect you're expecting. In reality, if GetForecastAsync was waiting on an async call to a database or a HTTP service, then this would work out OK.

Maybe the template should be better in this regard?

mrpmorris commented 4 years ago

If this is server-side blazor then I think the prerender will wait for the init code to complete before sending your initial html.

If you want to see UI before the days loads, put the call in OnAfterRender when firstRender == true

javiercn commented 4 years ago

@mrpmorris answer is correct. Prerendering will always wait for the async work to complete before producing the HTML output so that it can produce meaningful results. The alternative suggested is a valid option to avoid delaying the rendering until the content has loaded.