Skip to content

Commit 7520f42

Browse files
authored
Blazor WASM perf best practices topic (#18288)
1 parent 1c8fc3d commit 7520f42

4 files changed

Lines changed: 164 additions & 0 deletions

File tree

aspnetcore/blazor/lifecycle.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,8 @@ protected override bool ShouldRender()
154154

155155
Even if `ShouldRender` is overridden, the component is always initially rendered.
156156

157+
For more information, see <xref:performance/blazor/webassembly-best-practices#avoid-unnecessary-component-renders>.
158+
157159
## State changes
158160

159161
<xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> notifies the component that its state has changed. When applicable, calling `StateHasChanged` causes the component to be rerendered.

aspnetcore/host-and-deploy/blazor/configure-linker.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,7 @@ To control which I18N assemblies are retained, set the `<BlazorWebAssemblyI18NAs
119119
Use a comma to separate multiple values (for example, `mideast,west`).
120120

121121
For more information, see [I18N: Pnetlib Internationalization Framework Library (mono/mono GitHub repository)](https://github.com/mono/mono/tree/master/mcs/class/I18N).
122+
123+
## Additional resources
124+
125+
* <xref:performance/blazor/webassembly-best-practices#intermediate-language-il-linking>
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
---
2+
title: ASP.NET Core Blazor WebAssembly performance best practices
3+
author: pranavkm
4+
description: Tips for increasing performance in ASP.NET Core Blazor WebAssembly apps and avoiding common performance problems.
5+
monikerRange: '>= aspnetcore-2.1'
6+
ms.author: riande
7+
ms.custom: mvc
8+
ms.date: 05/13/2020
9+
no-loc: [Blazor, "Identity", "Let's Encrypt", Razor, SignalR]
10+
uid: performance/blazor/webassembly-best-practices
11+
---
12+
# ASP.NET Core Blazor WebAssembly performance best practices
13+
14+
By [Pranav Krishnamoorthy](https://github.com/pranavkm)
15+
16+
This article provides guidelines for ASP.NET Core Blazor WebAssembly performance best practices.
17+
18+
## Avoid unnecessary component renders
19+
20+
Blazor's diffing algorithm avoids rerendering a component when the algorithm perceives that the component hasn't changed. Override [ComponentBase.ShouldRender](xref:Microsoft.AspNetCore.Components.ComponentBase.ShouldRender%2A) for fine-grained control over component rendering.
21+
22+
If authoring a UI-only component that never changes after the initial render, configure `ShouldRender` to return `false`:
23+
24+
```razor
25+
@code {
26+
protected override bool ShouldRender() => false;
27+
}
28+
```
29+
30+
Most apps don't require fine-grained control, but <xref:Microsoft.AspNetCore.Components.ComponentBase.ShouldRender%2A> can also be used to selectively render a component responding to a UI event.
31+
32+
In the following example:
33+
34+
* <xref:Microsoft.AspNetCore.Components.ComponentBase.ShouldRender%2A> is overridden and set to the value of the `shouldRender` field, which is initially `false` when the component loads.
35+
* When the button is selected, `shouldRender` is set to `true`, which forces the component to rerender with the updated `currentCount`.
36+
* Immediately after rerendering, <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRender%2A> sets the value of `shouldRender` back to `false` to prevent further rerendering until the next time the button is selected.
37+
38+
```razor
39+
<p>Current count: @currentCount</p>
40+
41+
<button @onclick="IncrementCount">Click me</button>
42+
43+
@code {
44+
private int currentCount = 0;
45+
private bool shouldRender;
46+
47+
protected override bool ShouldRender() => shouldRender;
48+
49+
protected override void OnAfterRender(bool first)
50+
{
51+
shouldRender = false;
52+
}
53+
54+
private void IncrementCount()
55+
{
56+
currentCount++;
57+
shouldRender = true;
58+
}
59+
}
60+
```
61+
62+
For more information, see <xref:blazor/lifecycle#after-component-render>.
63+
64+
## Virtualize re-usable fragments
65+
66+
Components offer a convenient approach to produce re-usable fragments of code and markup. In general, we recommend authoring individual components that best align with the app's requirements. One caveat is that each additional child component contributes to the total time it takes to render a parent component. For most apps, the additional overhead is negligible. Apps that produce a large number of components should consider using strategies to reduce processing overhead, such as limiting the number of rendered components.
67+
68+
For example, a grid or list that renders hundreds of rows containing components is processor intensive to render. Consider virtualizing a grid or list layout so that only a subset of the components is rendered at any given time. For an example of component subset rendering, see the following components in the [Virtualization sample app (aspnet/samples GitHub repository)](https://github.com/aspnet/samples/tree/master/samples/aspnetcore/blazor/Virtualization):
69+
70+
* `Virtualize` component ([Shared/Virtualize.razor](https://github.com/aspnet/samples/blob/master/samples/aspnetcore/blazor/Virtualization/Shared/Virtualize.cs)): A component written in C# that implements <xref:Microsoft.AspNetCore.Components.ComponentBase> to render a set of weather data rows based on user scrolling.
71+
* `FetchData` component ([Pages/FetchData.razor](https://github.com/aspnet/samples/blob/master/samples/aspnetcore/blazor/Virtualization/Pages/FetchData.razor)): Uses the `Virtualize` component to display 25 rows of weather data at a time.
72+
73+
## Avoid JavaScript interop to marshal data
74+
75+
In Blazor WebAssembly, a JavaScript (JS) interop call must traverse the WebAssembly-JS boundary. Serializing and deserializing content across the two contexts creates processing overhead for the app. Frequent JS interop calls often adversely affects performance. To reduce the marshalling of data across the boundary, determine if the app can consolidate many small payloads into a single large payload to avoid the high volume of context switching between WebAssembly and JS.
76+
77+
## Use System.Text.Json
78+
79+
Blazor's JS interop implementation relies on <xref:System.Text.Json>, which is a high-performance JSON serialization library with low memory allocation. Using <xref:System.Text.Json> doesn't result in additional app payload size over adding one or more alternate JSON libraries.
80+
81+
For migration guidance, see [How to migrate from Newtonsoft.Json to System.Text.Json](/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to).
82+
83+
## Use synchronous and unmarshalled JS interop APIs where appropriate
84+
85+
Blazor WebAssembly offers two additional versions of <xref:Microsoft.JSInterop.IJSRuntime> over the single version available to Blazor Server apps:
86+
87+
* <xref:Microsoft.JSInterop.IJSInProcessRuntime> allows invoking JS interop calls synchronously, which has less overhead than the asynchronous versions:
88+
89+
```razor
90+
@inject IJSRuntime JS
91+
92+
@code {
93+
protected override void OnInitialized()
94+
{
95+
var jsInProcess = (IJSInProcessRuntime)JS;
96+
97+
var value = jsInProcess.Invoke<string>("jsInteropCall");
98+
}
99+
}
100+
```
101+
102+
* <xref:Microsoft.JSInterop.WebAssembly.WebAssemblyJSRuntime> permits unmarshalled JS interop calls:
103+
104+
```javascript
105+
function jsInteropCall() {
106+
return BINDING.js_to_mono_obj("Hello world");
107+
}
108+
```
109+
110+
```razor
111+
@inject IJSRuntime JS
112+
113+
@code {
114+
protected override void OnInitialized()
115+
{
116+
var jsInProcess = (WebAssemblyJSRuntime)JS;
117+
118+
var value = jsInProcess.InvokeUnmarshalled<string>("jsInteropCall");
119+
}
120+
}
121+
```
122+
123+
> [!WARNING]
124+
> While using <xref:Microsoft.JSInterop.WebAssembly.WebAssemblyJSRuntime> has the least overhead of the JS interop approaches, the JavaScript APIs required to interact with these APIs are currently undocumented and subject to breaking changes in future releases.
125+
126+
## Reduce app size
127+
128+
### Intermediate Language (IL) linking
129+
130+
[Linking a Blazor WebAssembly app](xref:host-and-deploy/blazor/configure-linker) reduces the app's size by trimming unused code in the app's binaries. By default, the linker is only enabled when building in `Release` configuration. To benefit from this, publish the app for deployment using the [dotnet publish](/dotnet/core/tools/dotnet-publish) command with the [-c|--configuration](/dotnet/core/tools/dotnet-publish#options) option set to `Release`:
131+
132+
```dotnetcli
133+
dotnet publish -c Release
134+
```
135+
136+
### Disable unused features
137+
138+
Blazor WebAssembly's runtime includes the following .NET features that can be disabled if the app doesn't require them for a smaller payload size:
139+
140+
* A data file is included to make timezone information correct. If the app doesn't require this feature, consider disabling it by setting the `BlazorEnableTimeZoneSupport` MSBuild property in the app's project file to `false`:
141+
142+
```xml
143+
<PropertyGroup>
144+
<BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport>
145+
</PropertyGroup>
146+
```
147+
148+
* Collation information is included to make APIs such as <xref:System.StringComparison.InvariantCultureIgnoreCase?displayProperty=nameWithType> work correctly. If you're certain that the app doesn't require the collation data, consider disabling it by setting the `BlazorWebAssemblyPreserveCollationData` MSBuild property in the app's project file to `false`:
149+
150+
```xml
151+
<PropertyGroup>
152+
<BlazorWebAssemblyPreserveCollationData>false</BlazorWebAssemblyPreserveCollationData>
153+
</PropertyGroup>
154+
```

aspnetcore/toc.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,8 @@
367367
uid: blazor/debug
368368
- name: Call a web API from WebAssembly
369369
uid: blazor/call-web-api
370+
- name: WebAssembly performance
371+
uid: performance/blazor/webassembly-best-practices
370372
- name: Progressive Web Applications
371373
uid: blazor/progressive-web-app
372374
- name: Host and deploy
@@ -1180,6 +1182,8 @@
11801182
uid: performance/diagnostic-tools
11811183
- name: Load and stress testing
11821184
uid: test/loadtests
1185+
- name: Blazor WebAssembly
1186+
uid: performance/blazor/webassembly-best-practices
11831187
- name: Globalization and localization
11841188
items:
11851189
- name: Overview

0 commit comments

Comments
 (0)