making my first steps in Blazor!
I have my page Test.razor with a simple grid:
<table class="table">
<thead>
<tr>Message</tr>
</thead>
<tbody>
@foreach (var exception in Exceptions)
{
<tr>exception.Message</tr>
}
</tbody>
</table>
and my logic:
public partial class Test
{
public List<TestEventModel> Exceptions { get; set; }
protected override async void OnInitialized()
{
var exceptionsResponse = (await http.GetAsync("TestController")).Content.ReadAsStringAsync();
Exceptions = JsonConvert.DeserializeObject<List<TestEventModel>>(await exceptionsResponse);
}
}
Problem: Unhandled exception rendering component: Object reference not set to an instance of an object.
Exception occurring on the line :
@foreach (var exception in Exceptions)
But according to the lifecycle it should start rendering only after the execution of OnInitialized:
If indeed I initialize the list in a constructor for example, problem is not there, but of course the list will be empty and not showing the result of my http call.
async OnInitialized
(and OnInitializedAsync
; you should use this instead of OnInitialized
if you are doing async work like HTTP requests!) begins before render, but await
unblocks the execution chain allowing the page to render before an asset (i.e. Exceptions
from the question) has loaded.
I created a test page that logs the chronological order of each lifecycle event. In particular, note the OnInitialized
method:
protected override async void OnInitialized()
{
Record("-> OnInitialized");
// Note: I am not advocating you use Task.Run...
// this is to simulate an asynchronous call to an external source!
Data = await Task.Run(() => new List<string> { "Hello there" });
StateHasChanged(); // not always necessary... see link below
base.OnInitialized();
Record("<- OnInitialized");
}
But if we change OnInitialized
so that it does not contain any await
s:
protected override async void OnInitialized()
{
Record("-> OnInitialized");
base.OnInitialized();
Record("<- OnInitialized");
}
As you can see, await
will unblock the process that called OnInitialized
allowing for the next steps in the life-cycle method to be called. In your case, you await
your Exceptions
to set it, but this allows the component to continue down the lifecycle, rendering before the awaited task completes, assigning Exceptions
.
Blazor's own default app demonstrates knowledge of this and how to address it:
@if (forecasts == null) @* <-- forecasts is null, initially *@
{
<p><em>Loading...</em></p>
}
else
{
@* render forecasts omitted *@
}
@code {
private WeatherForecast[] forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await
Http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json");
}
// WeatherForecast implementation omitted
}
StateHasChanged()
?See When to call StateHasChanged for an explanation.
...it might make sense to call StateHasChanged in the cases described in the following sections of this article:
- An asynchronous handler involves multiple asynchronous phases
- Receiving a call from something external to the Blazor rendering and event handling system
- To render component outside the subtree that is rerendered by a particular event
Removed because I looked into it and decided it wasn't really correct. See the revision history.