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
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>
6 Answers
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))
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:
info: Microsoft.Hosting.Lifetime[0]
Now listening on:
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
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
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
Request starting HTTP/2 GET
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
Request starting HTTP/2 GET
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
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 text/plain;charset=UTF-8 0
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
Request starting HTTP/2 GET
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
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.
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;
}
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
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.
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
Had the same issue.
For me it was that <script src="_framework/blazor.server.js"></script>
was written twice on _Host.cshtml