0

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:

enter image description here

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.

2
  • Blazor server or blazor wasm? Commented Apr 13, 2021 at 16:06
  • Are you actually certain that Exceptions is not null after you assign it? Commented Apr 13, 2021 at 16:12

4 Answers 4

4

Why isn't Exceptions initialized?

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.

Example

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

I got this output:
enter image description here

But if we change OnInitialized so that it does not contain any awaits:

protected override async void OnInitialized()
{
    Record("-> OnInitialized");
    base.OnInitialized();
    Record("<- OnInitialized");
}

Console, no await

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
}

Why do I need 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

Original Answer

Removed because I looked into it and decided it wasn't really correct. See the revision history.

Sign up to request clarification or add additional context in comments.

14 Comments

thanks first of all for the answer! I am using WASM with .net core hosted. So there is a backend with a controller that I am contacting in OnInitialized to get the info I need. Your StateHasChanged() solves my problem but I still don't understand why it's happening. Per doc as showed in my pic above the rendering shouldn't start at all until the OnItiliazed method is finished, whether it is async or not
StateHasChanged() is necessary if you have an update to the state that occurs in an async context.
You're right, I had missed the async void : StateHasChanged is necessary in that case. But async void is very wrong by itself. And Task.Run() should be avoided too.
@ConnorLow. NP. An interesting discussion. Today's quote of the day - Async all the Way! I see all the usual suspects are in tonight (Uk time). I approved your change. Still got a -1!
@ConnorLow super clear explanation! Apologies for a couple of days in delay. Everything makes sense and indeed the code works also without the StateHasChanged call. I was badly dragged into doubt from the picture of the documentation that I read as: "OnInitialized{Async} task finished? No? Then wait before render!" . That's why it didn't make sense but your thread explanation totally does. Thanks a lot once more!
|
1

Blazor renders in multiple passes, and what you're experiencing is absolutely normal. My understanding is that this is so most of your page will load even if it's waiting for async data to get filled in (complex database search etc.)

There are two ways I've used to avoid throwing a null reference on async init:

1. Check for null. Don't worry, your component will initialize everything and render properly on the second pass.

@if (Exceptions is not null){
    foreach (var exception in Exceptions)
        {
            <tr>exception.Message</tr>
        }
}

2. Initialize your List in the declaration. This empty List will be available on the first pass:

@code{
    public List<TestEventModel> Exceptions { get; set; } = new List<TestEventModel>();
}

4 Comments

I declared a partial class with the same name of the razor file in such a way that I keep all the logic there, and the frontend on the .razor page. So in practice I am instantiating my Exceptions in the constructor of the class and this avoid giving the problem. Your check on step 1 is indeed a nice way to avoid troubles but I still do not understand why the render happens before the data is loaded. According to the lifecycle it shouldn't happen
I think you might want to examine the lifecycle page again. From the bottom of OnInitializedAsync, there's an arrow pointing down directly to OnParametersSet and then Render. To the right, there's ALSO an arrow pointing to IsTaskComplete (which in your case it isn't because you are awaiting async Tasks)-- and this also leads to a Render.
Wait.. I read that Render on the picture as First Render. In that case is exactly as you are saying! it won't render until the tasks are completed, so shouldn't throw the exception. If that Render points to any update view at random point (which IMHO I don't think it does), then it's another story. Am I missing something?
Good boy, Benny... The upvote is from me... "it won't render until the tasks are completed" Who is saying that, you or Benny ? It seems to me there is a mistake about what is asynchronous coding... When you await a method, you yield control to the calling code to do something else...
1

Where you see ...await task... in the picture a Render action can/will execute.

If you want to confirm this experimentally you can use this:

@foreach (var item in Items)  
{

}
public List<string> Items { get; set; } = new();

protected override async Task OnInitializedAsync()
{
    Console.WriteLine("OnInitializedAsync start");
    await Task.Delay(100);
    Console.WriteLine("OnInitializedAsync done");
    Items = new List<string> { "aa", "bb", "cc", };        
}

protected override Task OnAfterRenderAsync(bool firstRender)
{
    Console.WriteLine($"OnAfterRenderAsync {firstRender}");
    return base.OnAfterRenderAsync(firstRender);
}

This will print "OnInitializedAsync done" after the first rendering. Note that this is before Items is assigned.

When you remove the = new(); part you get your original NRE error again. When you then remove the Task.Delay() as well it will run w/o an error. That follows the non async path you first assumed: "according to the lifecycle it should start rendering only after the execution of OnInitialized".

It is all about the await, it has nothing to do with server prerendering.

Comments

0

As your looking for Why?

You call:

protected override async void OnInitialized()
{
        var exceptionsResponse = (await http.GetAsync("TestController")).Content.ReadAsStringAsync();
}

OnInitialized is the synchronous version of OnInitializedAsync - and should only run synchronous code. You've turned it into a fire-and-forget async void event handler.

It runs synchronously until it hits some real yielding code - http.GetAsync. At which point it yields back to the component SetParametersAsync. This runs to completion. By the time Exceptions =.. gets run SetParametersAsync has completed, the component has completed rendering and errored because Exceptions is still null.

You need to run your code in OnInitializedAsync(). This returns a task to SetParametersAsync which it can wait on, and only continues when OnInitializedAsync completes by returning a completed Task.

    protected override async Task OnInitializedAsync()
    {
        var exceptionsResponse = (await http.GetAsync("TestController")).Content.ReadAsStringAsync();
        Exceptions = JsonConvert.DeserializeObject<List<TestEventModel>>(await exceptionsResponse);
    }

You still need to check if Exceptions is null in the display code and display a Loading message because the initial component render may occur before OnInitializedAsync completes. A second render event which occurs once SetParametersAsync has completed will render the component correctly.

Something like:

<table class="table">
    <thead>
        <tr>Message</tr>
    </thead>
    <tbody>
@if (Exceptions != null)
{
        @foreach (var exception in Exceptions)
        {
            <tr><td>exception.Message</td></tr>
        }
}
else 
{
            <tr><td>Loading....</td></tr>
}
    </tbody>
</table>

When you call StateHasChanged in your code you need to ask why? There are valid reasons, but often it's to recover from a earlier coding mistake.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.