106

So with a release of asp.net core 3.0 and blazor 1.0 I started doing some actual work with blazor. When splitting Blazor component code into code behind I am using the following

public class LogoutModel : BlazorComponent
{
}

Unfortunately BlazorComponent does not exist anymore, so I move to ComponentBase. Not sure when did this change took place..

Now the rest of my code looks like this

public class LogoutModel : ComponentBase
{
    protected override async Task OnInitializedAsync()
    {
    }

    protected override async Task OnParametersSetAsync()
    {
    }
}

What i notice is that life cycle methods are executed in the following order

  1. OnInitializedAsync()
  2. OnParametersSetAsync()
  3. OnInitializedAsync()
  4. OnParametersSetAsync()

I am not really sure why is each method executed twice.

This is what my Blazor file looks like

@page  "/account/logout"
@inherits LogoutModel

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title></title>
</head>
<body>
    Logout page
</body>
</html>
2
  • 1
    Which hosting model are you using? Commented Sep 24, 2019 at 8:15
  • 1
    I am usimng blazor Server Commented Sep 24, 2019 at 8:29

10 Answers 10

156

TL;DR

This is because of the prerendering mechanism that initializes the component as a part of the host page _Host.cshtml, so that the first http request would result in a host page that comes not only as a script loader of the blazor application, but also with a statically rendered view. Therefore the user could see the initial view without having to wait for roughly the following steps:

  • A WebSocket connection is established by SignalR.

  • The first bunch of render instructions are received from the server.

  • The render instructions are applied to the view.

This would not only shorten the responding delay before the user see the initial view, but also benefit SEO. The prerendered view would be replaced by the real blazor component after the blazor application normally starts.

The prerendering feature is enabled by default within the new project template, so you have to choose one of the followings:

  • Correctly handle the case that that component is prerendered (probably by checking whether the IJSRuntime is able to be resolved from the dependency injection).

  • Disable the prerendering feature by modifying the _Host.cshtml, replacing

<component type="typeof(App)" render-mode="ServerPrerendered" />

with

<component type="typeof(App)" render-mode="Server" />

For legacy versions, replace

@(await Html.RenderComponentAsync<App>(RenderMode.ServerPrerendered))

with

@(await Html.RenderComponentAsync<App>(RenderMode.Server))

The Original Answer

I did a test with a fresh new blazorserver project, logging when lifecycle methods are called, and got this output:

info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0]
      User profile is available. Using 'C:\Users\Alsein\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
info: Microsoft.Hosting.Lifetime[0]
      Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
      Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: D:\repos\HelloWorld
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
      Request starting HTTP/2 GET https://localhost:5001/
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
      Executing endpoint '/_Host'
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[3]
      Route matched with {page = "/_Host"}. Executing page /_Host
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[103]
      Executing an implicit handler method - ModelState is Valid
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[104]
      Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult.
crit: HelloWorld.MyBase[0]
      OnInitializedAsync
crit: HelloWorld.MyBase[0]
      OnParameterSetAsync
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[4]
      Executed page /_Host in 122.3724ms
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
      Executed endpoint '/_Host'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
      Request finished in 216.7341ms 200 text/html; charset=utf-8
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
      Request starting HTTP/2 GET https://localhost:5001/css/site.css
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
      Request starting HTTP/2 GET https://localhost:5001/_framework/blazor.server.js
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
      Request starting HTTP/2 GET https://localhost:5001/css/bootstrap/bootstrap.min.css
info: Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware[2]
      Sending file. Request path: '/css/site.css'. Physical path: 'D:\repos\HelloWorld\wwwroot\css\site.css'
info: Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware[2]
      Sending file. Request path: '/_framework/blazor.server.js'. Physical path: 'N/A'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
      Request finished in 44.733000000000004ms 200 text/css
info: Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware[2]
      Sending file. Request path: '/css/bootstrap/bootstrap.min.css'. Physical path: 'D:\repos\HelloWorld\wwwroot\css\bootstrap\bootstrap.min.css'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
      Request finished in 55.3613ms 200 text/css
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
      Request finished in 55.569900000000004ms 200 application/javascript
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
      Request starting HTTP/2 GET https://localhost:5001/css/open-iconic/font/css/open-iconic-bootstrap.min.css
info: Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware[2]
      Sending file. Request path: '/css/open-iconic/font/css/open-iconic-bootstrap.min.css'. Physical path: 'D:\repos\HelloWorld\wwwroot\css\open-iconic\font\css\open-iconic-bootstrap.min.css'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
      Request finished in 4.5189ms 200 text/css
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
      Request starting HTTP/2 POST https://localhost:5001/_blazor/negotiate text/plain;charset=UTF-8 0
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
      Request starting HTTP/2 GET https://localhost:5001/css/open-iconic/font/fonts/open-iconic.woff
info: Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware[2]
      Sending file. Request path: '/css/open-iconic/font/fonts/open-iconic.woff'. Physical path: 'D:\repos\HelloWorld\wwwroot\css\open-iconic\font\fonts\open-iconic.woff'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
      Request finished in 4.3562ms 200 application/font-woff
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
      Executing endpoint '/_blazor/negotiate'
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
      Executed endpoint '/_blazor/negotiate'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
      Request finished in 24.7409ms 200 application/json
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
      Request starting HTTP/1.1 GET https://localhost:5001/_blazor?id=7oyJvbydrUy9tqlsH_DHzQ
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
      Executing endpoint '/_blazor'
crit: HelloWorld.MyBase[0]
      OnInitializedAsync
crit: HelloWorld.MyBase[0]
      OnParameterSetAsync

From the result we can see that, the component is loaded twice.

  • The first time it was loaded as a simple Mvc component directly when the page is requested and handled by /_Host which must be specified with the following code in _Host.cshtml, which calls the lifecycle methods for the first time:
@(await Html.RenderComponentAsync<App>(RenderMode.ServerPrerendered))
  • Then resources are loaded including blazor.server.js.

  • Then the blazor app starts rendering.

  • Then the component is loaded as a blazor component, where the lifecycle methods are called for the second time.

Try replacing RenderMode.ServerPrerendered with RenderMode.Server, then it behaves as expected, which is that the lifecycle methods are only called for once (when the blazor app starts).

Conclusion: The default RenderMode is ServerPrerendered which must mean that Mvc could render the components as static contents in order to display the page contents before the blazor app is downloaded and starts, then when the blazor app starts, it takes over the page content. This must be a workaround for user experience that the browser user could wait less time for seeing the contents.

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

9 Comments

Thanks for taking the time to look into this, that helped me a lot.
Thanks, here's the MS Documentation
If this were Nuxt.js, this would be similar to hybrid mode, where some part of the component or page can be controllably pre-rendered at the server, to speed things up, or use some service that is only available server-side (or should only be). There must be a way to check where the component is rendered, in the lifecycle method, I think I saw it somewhere but I don't remember it now. You could then simply do an if check and proceed accordingly.
@KhalidAb The answer was written before the first GA version of blazor was released. The answer is updated.
@Alsein Then what is the benefit of these events if we can't use them precisely? As they will execute twice. I want to perform an action when first-time OnAfterRender is executed, but I can't do it. It's executing twice.
|
15

I solved my problem like this

Put this line at top of your page to prevent pre-rendering for dynamic parts

@rendermode @(new InteractiveServerRenderMode(prerender:false))

For other case if you are using a Blazor component in a Razor Or MVC project use like this

<component type="typeof(UsersComponent)" render-mode="Server" />

Comments

12

You can use this method and you will have only once

protected override Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            //do something
            StateHasChanged();
        }
        return Task.CompletedTask;
    }

3 Comments

But that firstRender param only exists in this particular event
After hours of looking how to avoid executing twice my page, this worked for me. Thanks!
This works great in .NET 8.
4

I had exactly the same issue with a holding page for a site where I had a little css animation which looked great in development, but when I put it live it ran twice. Changing the RenderMode to Server certainly fixes the issue, but it appears notably slower.

Funny because I would never have picked this up until I did this, for the final site I'll be switching back to ServerPrerendered

2 Comments

I switched to "Server' but still calls twice. protected override async Task OnInitializedAsync() { await GetData() } ???
@Jon - see my answer below - it fixes your issue
3

As an update for .NET 9, We now have access to a RendererInfo property.

RendererInfo.Name
RendererInfo.IsInteractive

You can check RenderInfo.IsInteractive to see if its true and handle parts of your code that only need to run after the pre-render etc.

1 Comment

This was the solution I tried and it worked very well for me.
2

If you are here because of the .net 8 changes then the following article might help you by using persistent state for you on page data:

https://jonhilton.net/persist-state-between-renders-net8/

Summary

Prerendering makes for a faster initial load for your users.

But it can also lead to your component fetching data twice (once during prerendering, and again when rendered using one of the interactive render modes in .NET 8).

You can switch prerendering off if you don’t need it.

Alternatively, you can persist state during the first render, and use it during the second, to save making multiple calls to fetch the exact same data.

Persisting the state between renders adds more ‘plumbing’ code to your component, but ensures you only fetch data once, and avoids the user seeing that ‘flash’ in the UI as your component updates with the new data (during the second render).

Example code for using PersistentComponentState

@implements IDisposable
@inject PersistentComponentState ApplicationState

@code{

    private PersistingComponentStateSubscription _subscription;
    private string _myData;

    protected override void OnInitialized()
    {
        // register the `Persist()` function to be called before refresh
        _subscription = ApplicationState.RegisterOnPersisting(Persist);
    }

    protected override async Task OnParametersSetAsync()
    {
        // check if data is present in the persistent storage
        if (!ApplicationState.TryTakeFromJson<string>("myData", out _myData))
        {
            // if data is not store then fetch it like normally
            _myData = FooBar();
        }
    }

    private Task Persist()
    {
        // storing the data
        ApplicationState.PersistAsJson("myData", _myData);
        return Task.CompletedTask;
    }

    public void Dispose()
    {
        // !!! dispose the subscription or otherwise the persistent storage  !!!
        // !!! will be a huge contributor to memory leaks in you application !!!
        _subscription.Dispose();
    }
}

Comments

1

Being tracked here: https://github.com/dotnet/aspnetcore/issues/21348

Meanwhile:

(Blazor WASM) - change from

protected override async Task OnParametersSetAsync()
    await ReloadServerData();
}

to parameter setter:

[Parameter]
public string Foo
{
    get => _foo;
    set
    {
        // if you put a breakpoint here, you will realize, that this setter
        // gets called multiple times, but only once actually changes value
        if (_foo == value)
            return;

        _foo = value;

        ReloadServerData();
    }
}

being totally aware, that when ReloadServerData() fails, the property also fails to set value.

3 Comments

What if my parameter is of complex type? ex. of type Class
It's up to you and I would say it depends on specific case to decide if the object changes - check e.g. stackoverflow.com/questions/10454519/…
Or - if you need to detect change of specific property of some complex object, use custom event and then subscribe to it.
1

It is expected that OnInitializedAsync() is invoked twice. It is good that it is called twice, and there is no need to disable pre-rendering as mentioned in other answers. Pre-rendering was created on purpose.

If you have issues with double calling your data fetching logic, instead of disabling pre-rendering it is better to cache your data during the first invocation (pre-render phase) and read data from the cache on the second invocation (render phase).

For details see: https://learn.microsoft.com/en-us/aspnet/core/blazor/components/lifecycle?view=aspnetcore-8.0#stateful-reconnection-after-prerendering

Comments

0

you can call some methods or run some JS codes using the IJSRuntime one time only to do that you can check the property IsFirtRender to execute some methods only once but it wont work for all the method sometimes it wont if the method is used before the render

Comments

0

Had the same issue. For me it was that <script src="_framework/blazor.server.js"></script> was written twice on _Host.cshtml

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.