Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are Blazor lifecycle methods getting executed twice?

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     { } 

Unfortunatelly 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 OnInitializedAsync() OnParametersSetAsync() OnInitializedAsync() 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> 
like image 969
mko Avatar asked Sep 24 '19 07:09

mko


People also ask

What is OnInitialized in Blazor?

OnInitialized and OnInitializedAsync are invoked when the component is initialized after having received its initial parameters in SetParametersAsync. Blazor apps that prerender their content on the server call OnInitializedAsync twice: Once when the component is initially rendered statically as part of the page.

How do you dispose of Blazor components?

If a component implements IDisposable, the Dispose method will be called when the component is removed from the UI. You can use Dispose method to release unmanaged resources, unhook events, dispose DotNetObjectReference instances to avoid memory leak. Refer to the following code sample.

What is Blazor Prerendering?

What is Prerendering for Blazor WebAssembly? Blazor WebAssembly allows you to build . NET applications that run entirely in the browser, without a "back-end" application. The application is served entirely as static files in the same way that JavaScript SPAs built with Angular, React, or Vue are.

How does Blazor server work?

Server-side Blazor is executed on the server within an ASP.NET Core application. All UI updates, event handling, and JavaScript calls are handled from server by using a SignalR connection, even a button click will go to server.


1 Answers

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.

like image 139
Alsein Avatar answered Oct 18 '22 14:10

Alsein