Search code examples
blazorpre-rendering

PreRendering does not persist variable values used to trap second pass


I have reviewed the various articles on how Blazor Server Apps are prerendered then rendered in two passes. I have reviewed the LifeCycle and am very clear that this is expected behavior largely intended to provide good performance. I do not want to turn off the prerendering, so I have to account for it when loading data structures and similar things.

I need to be able to run my code that interacts with the database once. I have looked at a variety of the events that are supposed to help with this yet none seem to work.

One of the primary issues I am seeing is that any Boolean variable I use to record the first pass is not persisting the value during the second pass so I cannot use that to avoid doing the work twice.

In other words, in my experience, the second pass behaves as if the first pass never happened, so nothing set in the first pass is available during the second pass.

This is making it impossible to manage the double rendering.

If I do this, I end up with a Development Error page:

 protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        Console.WriteLine("Bink");
        //await OnInitializedAsync();
    }
    else
    {
        await Initialize();
    }
}

If I do this, the isInitialized variable is never set to true when inspected in the If statement. It is always false, so it seems like each pass is completely independent of the previous pass, which is not how I understood it to work.:

 protected override async Task OnParametersSetAsync()
{
    if(isInitialized)
    {
        return;
    }
    isInitialized = true;
    await Initialize();
    //await base.OnParametersSetAsync();
}

How can I implement a solution that will only run my Initialization code once while preserving the two pass rendering? I see a lot of solutions that are supposed to work yet none do, so how do I resolve this?


Solution

  • The first and second loads are run in two different contexts. The only thing they share are singleton services.

    You can detect the load using the IHttpContextAccessor.

    Add the service in Program:

    builder.Services.AddHttpContextAccessor(); 
    

    In any page or component you can then do this:

    @page "/"
    @inject IHttpContextAccessor HttpContextAccessor
    <PageTitle>Index</PageTitle>
    
    <h1>Hello, world!</h1>
    
    Welcome to your new app.
    
    <SurveyPrompt Title="How is Blazor working for you?" />
    
    @code {
        private bool _isServerRender;
    
        protected override void OnInitialized()
            => _isServerRender = !(HttpContextAccessor.HttpContext is not null && HttpContextAccessor.HttpContext.Response.HasStarted);
    
        protected override Task OnInitializedAsync()
        {
            if (_isServerRender)
            {
                // Do minumum to dispay page
                return base.OnInitializedAsync();
            }
    
            // Do normal stuff
            return base.OnInitializedAsync();
        }
    }
    

    Where you use this code depends on what you want to achieve. You can run it in Index, or you can run it in App and just display a loading screen.