diff --git a/.openpublishing.redirection.json b/.openpublishing.redirection.json index bb2d0991f928..f0840f77f820 100644 --- a/.openpublishing.redirection.json +++ b/.openpublishing.redirection.json @@ -1397,6 +1397,11 @@ "source_path": "aspnetcore/blazor/host-and-deploy/webassembly/integrity-check-failures.md", "redirect_url": "/aspnet/core/blazor/host-and-deploy/webassembly/bundle-caching-and-integrity-check-failures", "redirect_document_id": false + }, + { + "source_path": "aspnetcore/blazor/performance.md", + "redirect_url": "/aspnet/core/blazor/performance/", + "redirect_document_id": false } ] } diff --git a/aspnetcore/blazor/components/event-handling.md b/aspnetcore/blazor/components/event-handling.md index 20aef0c16755..21ac9d89b737 100644 --- a/aspnetcore/blazor/components/event-handling.md +++ b/aspnetcore/blazor/components/event-handling.md @@ -577,7 +577,7 @@ It's often convenient to close over additional values using C# method parameters :::moniker-end -Creating a large number of event delegates in a loop may cause poor rendering performance. For more information, see . +Creating a large number of event delegates in a loop may cause poor rendering performance. For more information, see . Avoid using a loop variable directly in a lambda expression, such as `i` in the preceding `for` loop example. Otherwise, the same variable is used by all lambda expressions, which results in use of the same value in all lambdas. Capture the variable's value in a local variable. In the preceding example: diff --git a/aspnetcore/blazor/components/index.md b/aspnetcore/blazor/components/index.md index 79319ee4c515..8f595a0b7d71 100644 --- a/aspnetcore/blazor/components/index.md +++ b/aspnetcore/blazor/components/index.md @@ -1392,7 +1392,7 @@ You can factor out child components purely as a way of reusing rendering logic. } ``` -For more information, see [Reuse rendering logic](xref:blazor/performance#define-reusable-renderfragments-in-code). +For more information, see [Reuse rendering logic](xref:blazor/performance/rendering#define-reusable-renderfragments-in-code). ## Loop variables with component parameters and child content diff --git a/aspnetcore/blazor/components/lifecycle.md b/aspnetcore/blazor/components/lifecycle.md index 0f5ca364830a..d4bbd33233a4 100644 --- a/aspnetcore/blazor/components/lifecycle.md +++ b/aspnetcore/blazor/components/lifecycle.md @@ -326,7 +326,7 @@ If a disposable component doesn't use a . -For an example of implementing `SetParametersAsync` manually to improve performance in some scenarios, see . +For an example of implementing `SetParametersAsync` manually to improve performance in some scenarios, see . ## After component render (`OnAfterRender{Async}`) diff --git a/aspnetcore/blazor/components/rendering.md b/aspnetcore/blazor/components/rendering.md index 4cc6ae8e2e0e..b4f61e590b51 100644 --- a/aspnetcore/blazor/components/rendering.md +++ b/aspnetcore/blazor/components/rendering.md @@ -39,7 +39,7 @@ Components inherited from s In most cases, conventions result in the correct subset of component rerenders after an event occurs. Developers aren't usually required to provide manual logic to tell the framework which components to rerender and when to rerender them. The overall effect of the framework's conventions is that the component receiving an event rerenders itself, which recursively triggers rerendering of descendant components whose parameter values may have changed. -For more information on the performance implications of the framework's conventions and how to optimize an app's component hierarchy for rendering, see . +For more information on the performance implications of the framework's conventions and how to optimize an app's component hierarchy for rendering, see . ::: moniker range=">= aspnetcore-8.0" @@ -147,7 +147,7 @@ Even if is ::: moniker-end -For more information on performance best practices pertaining to , see . +For more information on performance best practices pertaining to , see . ## `StateHasChanged` diff --git a/aspnetcore/blazor/components/templated-components.md b/aspnetcore/blazor/components/templated-components.md index 54fb84b682cd..d9e2392dc873 100644 --- a/aspnetcore/blazor/components/templated-components.md +++ b/aspnetcore/blazor/components/templated-components.md @@ -329,6 +329,6 @@ Without using the `@key` directive attribute in the `TableTemplate` component, t ## Additional resources -* +* * * [Blazor samples GitHub repository (`dotnet/blazor-samples`)](https://github.com/dotnet/blazor-samples) ([how to download](xref:blazor/fundamentals/index#sample-apps)) diff --git a/aspnetcore/blazor/globalization-localization.md b/aspnetcore/blazor/globalization-localization.md index e864b4898f8a..e99d5ce6f599 100644 --- a/aspnetcore/blazor/globalization-localization.md +++ b/aspnetcore/blazor/globalization-localization.md @@ -143,7 +143,7 @@ Adopting [invariant globalization](#invariant-globalization) only results in usi ``` > [!NOTE] -> [``](xref:blazor/performance#disable-unused-features) overrides an earlier `` setting. We recommend removing the `` setting. +> [``](xref:blazor/performance/app-download-size#disable-unused-features) overrides an earlier `` setting. We recommend removing the `` setting. :::moniker-end diff --git a/aspnetcore/blazor/host-and-deploy/configure-linker.md b/aspnetcore/blazor/host-and-deploy/configure-linker.md index f0834255a584..73119e7eddd5 100644 --- a/aspnetcore/blazor/host-and-deploy/configure-linker.md +++ b/aspnetcore/blazor/host-and-deploy/configure-linker.md @@ -119,4 +119,4 @@ For more information, see [I18N: Pnetlib Internationalization Framework Library ## Additional resources -* + diff --git a/aspnetcore/blazor/host-and-deploy/configure-trimmer.md b/aspnetcore/blazor/host-and-deploy/configure-trimmer.md index 443c72323e85..2e6370621705 100644 --- a/aspnetcore/blazor/host-and-deploy/configure-trimmer.md +++ b/aspnetcore/blazor/host-and-deploy/configure-trimmer.md @@ -178,4 +178,4 @@ Because custom types are never trimmed by Blazor when an app is published, the c ## Additional resources * [Trim self-contained deployments and executables](/dotnet/core/deploying/trimming/trim-self-contained) -* +* diff --git a/aspnetcore/blazor/javascript-interoperability/index.md b/aspnetcore/blazor/javascript-interoperability/index.md index 3b0721618cc6..fe5b2b86ef1c 100644 --- a/aspnetcore/blazor/javascript-interoperability/index.md +++ b/aspnetcore/blazor/javascript-interoperability/index.md @@ -18,6 +18,7 @@ Further JS interop guidance is provided in the following articles: * * +* :::moniker range=">= aspnetcore-7.0" diff --git a/aspnetcore/blazor/performance/app-download-size.md b/aspnetcore/blazor/performance/app-download-size.md new file mode 100644 index 000000000000..b8018b18bd01 --- /dev/null +++ b/aspnetcore/blazor/performance/app-download-size.md @@ -0,0 +1,118 @@ +--- +title: ASP.NET Core Blazor app download size performance best practices +author: guardrex +description: Tips for reducing app download size in ASP.NET Core Blazor apps and avoiding common performance problems. +monikerRange: '>= aspnetcore-3.1' +ms.author: riande +ms.custom: mvc +ms.date: 05/02/2025 +uid: blazor/performance/app-download-size +--- +# ASP.NET Core Blazor app download size performance best practices + +[!INCLUDE[](~/includes/not-latest-version.md)] + +:::moniker range=">= aspnetcore-6.0" + +## Runtime relinking + +For information on how runtime relinking minimizes an app's download size, see . + +:::moniker-end + +## Use `System.Text.Json` + +Blazor's JS interop implementation relies on , which is a high-performance JSON serialization library with low memory allocation. Using shouldn't result in additional app payload size over adding one or more alternate JSON libraries. + +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). + +## Intermediate Language (IL) trimming + +*This section only applies to client-side Blazor scenarios.* + +:::moniker range=">= aspnetcore-5.0" + +Trimming unused assemblies from a Blazor WebAssembly app reduces the app's size by removing unused code in the app's binaries. For more information, see . + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +[Linking a Blazor WebAssembly app](xref:blazor/host-and-deploy/configure-linker) reduces the app's size by trimming unused code in the app's binaries. The Intermediate Language (IL) 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`: + +```dotnetcli +dotnet publish -c Release +``` + +:::moniker-end + +## Lazy load assemblies + +*This section only applies to client-side Blazor scenarios.* + +Load assemblies at runtime when the assemblies are required by a route. For more information, see . + +## Compression + +*This section only applies to Blazor WebAssembly apps.* + +When a Blazor WebAssembly app is published, the output is statically compressed during publish to reduce the app's size and remove the overhead for runtime compression. Blazor relies on the server to perform content negotiation and serve statically-compressed files. + +After an app is deployed, verify that the app serves compressed files. Inspect the **Network** tab in a browser's [developer tools](https://developer.mozilla.org/docs/Glossary/Developer_Tools) and verify that the files are served with `Content-Encoding: br` (Brotli compression) or `Content-Encoding: gz` (Gzip compression). If the host isn't serving compressed files, follow the instructions in . + +## Disable unused features + +*This section only applies to client-side Blazor scenarios.* + +Blazor WebAssembly's runtime includes the following .NET features that can be disabled for a smaller payload size. + +Blazor WebAssembly carries globalization resources required to display values, such as dates and currency, in the user's culture. If the app doesn't require localization, you may configure the app to [support the invariant culture](xref:blazor/globalization-localization#invariant-globalization), which is based on the `en-US` culture. Apply the `` MSBuild property with a value of `true` in the app's project file (`.csproj`): + +```xml + + true + +``` + +:::moniker range=">= aspnetcore-8.0" + +Adopting [invariant globalization](xref:blazor/globalization-localization#invariant-globalization) only results in using non-localized timezone names. To trim timezone code and data from the app, apply the `` MSBuild property with a value of `true` in the app's project file (`.csproj`): + +```xml + + true + +``` + +> [!NOTE] +> [``](xref:blazor/performance/app-download-size#disable-unused-features) overrides an earlier `` setting. We recommend removing the `` setting. + +:::moniker-end + +:::moniker range="< aspnetcore-8.0" + +A data file is included to make timezone information correct. If the app doesn't require this feature, consider disabling it by setting the `` MSBuild property to `false` in the app's project file: + +```xml + + false + +``` + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +Collation information is included to make APIs such as 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`: + +```xml + + false + +``` + +:::moniker-end + +## Additional resources + +[Configuring and hosting .NET WebAssembly applications](https://github.com/dotnet/runtime/blob/main/src/mono/wasm/features.md) diff --git a/aspnetcore/blazor/performance/index.md b/aspnetcore/blazor/performance/index.md new file mode 100644 index 000000000000..1ecf596b0dc8 --- /dev/null +++ b/aspnetcore/blazor/performance/index.md @@ -0,0 +1,26 @@ +--- +title: ASP.NET Core Blazor performance best practices +author: guardrex +description: Tips for increasing the performance of ASP.NET Core Blazor apps and avoiding common performance problems. +monikerRange: '>= aspnetcore-3.1' +ms.author: riande +ms.custom: mvc +ms.date: 04/16/2025 +uid: blazor/performance/index +--- +# ASP.NET Core Blazor performance best practices + +[!INCLUDE[](~/includes/not-latest-version.md)] + +Blazor is optimized for high performance in most realistic application UI scenarios. However, the best performance depends on developers adopting the correct patterns and features. + +> [!NOTE] +> The code examples in this node of articles adopt [nullable reference types (NRTs) and .NET compiler null-state static analysis](xref:migration/50-to-60#nullable-reference-types-nrts-and-net-compiler-null-state-static-analysis), which are supported in ASP.NET Core in .NET 6 or later. + +:::moniker range=">= aspnetcore-6.0" + +## Ahead-of-time (AOT) compilation + +Ahead-of-time (AOT) compilation compiles a Blazor app's .NET code directly into native WebAssembly for direct execution by the browser. AOT-compiled apps result in larger apps that take longer to download, but AOT-compiled apps usually provide better runtime performance, especially for apps that execute CPU-intensive tasks. For more information, see . + +:::moniker-end diff --git a/aspnetcore/blazor/performance/javascript-interoperability.md b/aspnetcore/blazor/performance/javascript-interoperability.md new file mode 100644 index 000000000000..93f439633f78 --- /dev/null +++ b/aspnetcore/blazor/performance/javascript-interoperability.md @@ -0,0 +1,117 @@ +--- +title: ASP.NET Core Blazor JavaScript interoperability (JS interop) performance best practices +author: guardrex +description: Tips for improving JS interop performance in ASP.NET Core Blazor apps and avoiding common performance problems. +monikerRange: '>= aspnetcore-3.1' +ms.author: riande +ms.custom: mvc +ms.date: 04/16/2025 +uid: blazor/performance/js-interop +--- +# ASP.NET Core Blazor JavaScript interoperability (JS interop) performance best practices + +[!INCLUDE[](~/includes/not-latest-version.md)] + +Calls between .NET and JavaScript require additional overhead because: + +* Calls are asynchronous. +* Parameters and return values are JSON-serialized to provide an easy-to-understand conversion mechanism between .NET and JavaScript types. + +Additionally for server-side Blazor apps, these calls are passed across the network. + +## Avoid excessively fine-grained calls + +Since each call involves some overhead, it can be valuable to reduce the number of calls. Consider the following code, which stores a collection of items in the browser's [`localStorage`](https://developer.mozilla.org/docs/Web/API/Window/localStorage): + +```csharp +private async Task StoreAllInLocalStorage(IEnumerable items) +{ + foreach (var item in items) + { + await JS.InvokeVoidAsync("localStorage.setItem", item.Id, + JsonSerializer.Serialize(item)); + } +} +``` + +The preceding example makes a separate JS interop call for each item. Instead, the following approach reduces the JS interop to a single call: + +```csharp +private async Task StoreAllInLocalStorage(IEnumerable items) +{ + await JS.InvokeVoidAsync("storeAllInLocalStorage", items); +} +``` + +The corresponding JavaScript function stores the whole collection of items on the client: + +```javascript +function storeAllInLocalStorage(items) { + items.forEach(item => { + localStorage.setItem(item.id, JSON.stringify(item)); + }); +} +``` + +For Blazor WebAssembly apps, rolling individual JS interop calls into a single call usually only improves performance significantly if the component makes a large number of JS interop calls. + +## Consider the use of synchronous calls + +:::moniker range=">= aspnetcore-5.0" + +### Call JavaScript from .NET + +[!INCLUDE[](~/blazor/includes/js-interop/synchronous-js-interop-call-js.md)] + +### Call .NET from JavaScript + +[!INCLUDE[](~/blazor/includes/js-interop/synchronous-js-interop-call-dotnet.md)] + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +[!INCLUDE[](~/blazor/includes/js-interop/synchronous-js-interop-call-js.md)] + +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-7.0" + +## Consider the use of unmarshalled calls + +*This section only applies to Blazor WebAssembly apps.* + +When running on Blazor WebAssembly, it's possible to make unmarshalled calls from .NET to JavaScript. These are synchronous calls that don't perform JSON serialization of arguments or return values. All aspects of memory management and translations between .NET and JavaScript representations are left up to the developer. + +> [!WARNING] +> While using 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. + +```javascript +function jsInteropCall() { + return BINDING.js_to_mono_obj("Hello world"); +} +``` + +```razor +@inject IJSRuntime JS + +@code { + protected override void OnInitialized() + { + var unmarshalledJs = (IJSUnmarshalledRuntime)JS; + var value = unmarshalledJs.InvokeUnmarshalled("jsInteropCall"); + } +} +``` + +:::moniker-end + +:::moniker range=">= aspnetcore-7.0" + +## Use JavaScript `[JSImport]`/`[JSExport]` interop + +JavaScript `[JSImport]`/`[JSExport]` interop for Blazor WebAssembly apps offers improved performance and stability over the JS interop API in framework releases prior to ASP.NET Core in .NET 7. + +For more information, see . + +:::moniker-end diff --git a/aspnetcore/blazor/performance.md b/aspnetcore/blazor/performance/rendering.md similarity index 72% rename from aspnetcore/blazor/performance.md rename to aspnetcore/blazor/performance/rendering.md index 908ee0f3b7df..7c5b18cdb028 100644 --- a/aspnetcore/blazor/performance.md +++ b/aspnetcore/blazor/performance/rendering.md @@ -1,27 +1,20 @@ --- -title: ASP.NET Core Blazor performance best practices +title: ASP.NET Core Blazor rendering performance best practices author: guardrex -description: Tips for increasing performance in ASP.NET Core Blazor apps and avoiding common performance problems. +description: Tips for improving the rendering performance of ASP.NET Core Blazor apps and avoiding common performance problems. monikerRange: '>= aspnetcore-3.1' ms.author: riande ms.custom: mvc -ms.date: 11/12/2024 -uid: blazor/performance +ms.date: 04/16/2025 +uid: blazor/performance/rendering --- -# ASP.NET Core Blazor performance best practices +# ASP.NET Core Blazor rendering performance best practices [!INCLUDE[](~/includes/not-latest-version.md)] -Blazor is optimized for high performance in most realistic application UI scenarios. However, the best performance depends on developers adopting the correct patterns and features. - -> [!NOTE] -> The code examples in this article adopt [nullable reference types (NRTs) and .NET compiler null-state static analysis](xref:migration/50-to-60#nullable-reference-types-nrts-and-net-compiler-null-state-static-analysis), which are supported in ASP.NET Core in .NET 6 or later. - -## Optimize rendering speed - Optimize rendering speed to minimize rendering workload and improve UI responsiveness, which can yield a *ten-fold or higher improvement* in UI rendering speed. -### Avoid unnecessary rendering of component subtrees +## Avoid unnecessary rendering of component subtrees You might be able to remove the majority of a parent component's rendering cost by skipping the rerendering of child component subtrees when an event occurs. You should only be concerned about skipping the rerendering subtrees that are particularly expensive to render and are causing UI lag. @@ -77,7 +70,7 @@ For more information, see the following resources: :::moniker range=">= aspnetcore-5.0" -### Virtualization +## Virtualization When rendering large amounts of UI within a loop, for example, a list or grid with thousands of entries, the sheer quantity of rendering operations can lead to a lag in UI rendering. Given that the user can only see a small number of elements at once without scrolling, it's often wasteful to spend time rendering elements that aren't currently visible. @@ -87,7 +80,7 @@ For more information, see . :::moniker-end -### Create lightweight, optimized components +## Create lightweight, optimized components Most Razor components don't require aggressive optimization efforts because most components don't repeat in the UI and don't rerender at high frequency. For example, routable components with an `@page` directive and components used to render high-level pieces of the UI, such as dialogs or forms, most likely appear only one at a time and only rerender in response to a user gesture. These components don't usually create high rendering workload, so you can freely use any combination of framework features without much concern about rendering performance. @@ -99,7 +92,7 @@ However, there are common scenarios where components are repeated at scale and o If modelling each element, cell, or data point as a separate component instance, there are often so many of them that their rendering performance becomes critical. This section provides advice on making such components lightweight so that the UI remains fast and responsive. -#### Avoid thousands of component instances +## Avoid thousands of component instances Each component is a separate island that can render independently of its parents and children. By choosing how to split the UI into a hierarchy of components, you are taking control over the granularity of UI rendering. This can result in either good or poor performance. @@ -111,9 +104,7 @@ It's possible to make components more lightweight so that you can have more of t For more information on memory management, see . -##### Inline child components into their parents - -Consider the following portion of a parent component that renders child components in a loop: +**Inline child components into their parents:** Consider the following portion of a parent component that renders child components in a loop: ```razor
@@ -152,9 +143,7 @@ The preceding example performs well if thousands of messages aren't shown at onc
``` -##### Define reusable `RenderFragments` in code - -You might be factoring out child components purely as a way of reusing rendering logic. If that's the case, you can create reusable rendering logic without implementing additional components. In any component's `@code` block, define a . Render the fragment from any location as many times as needed: +**Define reusable `RenderFragments` in code:** You might be factoring out child components purely as a way of reusing rendering logic. If that's the case, you can create reusable rendering logic without implementing additional components. In any component's `@code` block, define a . Render the fragment from any location as many times as needed: ```razor @RenderWelcomeInfo @@ -206,7 +195,7 @@ protected RenderFragment DisplayTitle => ; ``` -#### Don't receive too many parameters +## Don't receive too many parameters If a component repeats extremely often, for example, hundreds or thousands of times, the overhead of passing and receiving each parameter builds up. @@ -230,7 +219,7 @@ To reduce parameter load, bundle multiple parameters in a custom class. For exam However, keep in mind that bundling primitive parameters into a class isn't always an advantage. While it can reduce parameter count, it also impacts how change detection and rendering behave. Passing non-primitive parameters always triggers a re-render, because Blazor can't know whether arbitrary objects have internally mutable state, whereas passing primitive parameters only triggers a re-render if their values have actually changed. -Also, consider that it might be an improvement not to have a table cell component, as shown in the preceding example, and instead [inline its logic into the parent component](#inline-child-components-into-their-parents). +Also, consider that it might be an improvement not to have a table cell component, as shown in the preceding example, and instead [inline its logic into the parent component](#avoid-thousands-of-component-instances). > [!NOTE] > When multiple approaches are available for improving performance, benchmarking the approaches is usually required to determine which approach yields the best results. @@ -241,7 +230,7 @@ For more information on generic type parameters (`@typeparam`), see the followin * * -#### Ensure cascading parameters are fixed +## Ensure cascading parameters are fixed The [`CascadingValue` component](xref:blazor/components/cascading-values-and-parameters#cascadingvalue-component) has an optional `IsFixed` parameter: @@ -260,7 +249,7 @@ Where a component passes `this` as a cascaded value, `IsFixed` can also be set t For more information, see . -#### Avoid attribute splatting with `CaptureUnmatchedValues` +## Avoid attribute splatting with `CaptureUnmatchedValues` Components can elect to receive "unmatched" parameter values using the flag: @@ -282,7 +271,7 @@ Use . -#### Implement `SetParametersAsync` manually +## Implement `SetParametersAsync` manually A significant source of per-component rendering overhead is writing incoming parameter values to `[Parameter]` properties. The renderer uses [reflection](/dotnet/csharp/advanced-topics/reflection-and-attributes/) to write the parameter values, which can lead to poor performance at scale. @@ -340,7 +329,7 @@ In the preceding code, returning the base class and supplying custom logic is complicated and laborious, so we don't generally recommend adopting this approach. In extreme cases, it can improve rendering performance by 20-25%, but you should only consider this approach in the extreme scenarios listed earlier in this section. -### Don't trigger events too rapidly +## Don't trigger events too rapidly Some browser events fire extremely frequently. For example, `onmousemove` and `onscroll` can fire tens or hundreds of times per second. In most cases, you don't need to perform UI updates this frequently. If events are triggered too rapidly, you may harm UI responsiveness or consume excessive CPU time. @@ -397,7 +386,7 @@ The corresponding JavaScript code registers the DOM event listener for mouse mov ``` -### Avoid rerendering after handling events without state changes +## Avoid rerendering after handling events without state changes Components inherit from , which automatically invokes after the component's event handlers are invoked. In some cases, it might be unnecessary or undesirable to trigger a rerender after an event handler is invoked. For example, an event handler might not modify component state. In these scenarios, the app can leverage the interface to control the behavior of Blazor's event handling. @@ -542,7 +531,7 @@ In the following example: In addition to implementing the interface, leveraging the other best practices described in this article can also help reduce unwanted renders after events are handled. For example, overriding in child components of the target component can be used to control rerendering. -### Avoid recreating delegates for many repeated elements or components +## Avoid recreating delegates for many repeated elements or components Blazor's recreation of [lambda expression delegates](xref:blazor/components/event-handling#lambda-expressions) for elements or components in a loop can lead to poor performance. @@ -638,214 +627,3 @@ If a large number of buttons are rendered using the preceding approach, renderin } } ``` - -## Optimize JavaScript interop speed - -Calls between .NET and JavaScript require additional overhead because: - -* Calls are asynchronous. -* Parameters and return values are JSON-serialized to provide an easy-to-understand conversion mechanism between .NET and JavaScript types. - -Additionally for server-side Blazor apps, these calls are passed across the network. - -### Avoid excessively fine-grained calls - -Since each call involves some overhead, it can be valuable to reduce the number of calls. Consider the following code, which stores a collection of items in the browser's [`localStorage`](https://developer.mozilla.org/docs/Web/API/Window/localStorage): - -```csharp -private async Task StoreAllInLocalStorage(IEnumerable items) -{ - foreach (var item in items) - { - await JS.InvokeVoidAsync("localStorage.setItem", item.Id, - JsonSerializer.Serialize(item)); - } -} -``` - -The preceding example makes a separate JS interop call for each item. Instead, the following approach reduces the JS interop to a single call: - -```csharp -private async Task StoreAllInLocalStorage(IEnumerable items) -{ - await JS.InvokeVoidAsync("storeAllInLocalStorage", items); -} -``` - -The corresponding JavaScript function stores the whole collection of items on the client: - -```javascript -function storeAllInLocalStorage(items) { - items.forEach(item => { - localStorage.setItem(item.id, JSON.stringify(item)); - }); -} -``` - -For Blazor WebAssembly apps, rolling individual JS interop calls into a single call usually only improves performance significantly if the component makes a large number of JS interop calls. - -### Consider the use of synchronous calls - -:::moniker range=">= aspnetcore-5.0" - -#### Call JavaScript from .NET - -[!INCLUDE[](~/blazor/includes/js-interop/synchronous-js-interop-call-js.md)] - -#### Call .NET from JavaScript - -[!INCLUDE[](~/blazor/includes/js-interop/synchronous-js-interop-call-dotnet.md)] - -:::moniker-end - -:::moniker range="< aspnetcore-5.0" - -[!INCLUDE[](~/blazor/includes/js-interop/synchronous-js-interop-call-js.md)] - -:::moniker-end - -:::moniker range=">= aspnetcore-5.0 < aspnetcore-7.0" - -### Consider the use of unmarshalled calls - -*This section only applies to Blazor WebAssembly apps.* - -When running on Blazor WebAssembly, it's possible to make unmarshalled calls from .NET to JavaScript. These are synchronous calls that don't perform JSON serialization of arguments or return values. All aspects of memory management and translations between .NET and JavaScript representations are left up to the developer. - -> [!WARNING] -> While using 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. - -```javascript -function jsInteropCall() { - return BINDING.js_to_mono_obj("Hello world"); -} -``` - -```razor -@inject IJSRuntime JS - -@code { - protected override void OnInitialized() - { - var unmarshalledJs = (IJSUnmarshalledRuntime)JS; - var value = unmarshalledJs.InvokeUnmarshalled("jsInteropCall"); - } -} -``` - -:::moniker-end - -:::moniker range=">= aspnetcore-7.0" - -### Use JavaScript `[JSImport]`/`[JSExport]` interop - -JavaScript `[JSImport]`/`[JSExport]` interop for Blazor WebAssembly apps offers improved performance and stability over the JS interop API in framework releases prior to ASP.NET Core in .NET 7. - -For more information, see . - -:::moniker-end - -:::moniker range=">= aspnetcore-6.0" - -## Ahead-of-time (AOT) compilation - -Ahead-of-time (AOT) compilation compiles a Blazor app's .NET code directly into native WebAssembly for direct execution by the browser. AOT-compiled apps result in larger apps that take longer to download, but AOT-compiled apps usually provide better runtime performance, especially for apps that execute CPU-intensive tasks. For more information, see . - -:::moniker-end - -## Minimize app download size - -:::moniker range=">= aspnetcore-6.0" - -### Runtime relinking - -For information on how runtime relinking minimizes an app's download size, see . - -:::moniker-end - -### Use `System.Text.Json` - -Blazor's JS interop implementation relies on , which is a high-performance JSON serialization library with low memory allocation. Using shouldn't result in additional app payload size over adding one or more alternate JSON libraries. - -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). - -### Intermediate Language (IL) trimming - -*This section only applies to client-side Blazor scenarios.* - -:::moniker range=">= aspnetcore-5.0" - -Trimming unused assemblies from a Blazor WebAssembly app reduces the app's size by removing unused code in the app's binaries. For more information, see . - -:::moniker-end - -:::moniker range="< aspnetcore-5.0" - -[Linking a Blazor WebAssembly app](xref:blazor/host-and-deploy/configure-linker) reduces the app's size by trimming unused code in the app's binaries. The Intermediate Language (IL) 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`: - -```dotnetcli -dotnet publish -c Release -``` - -:::moniker-end - -### Lazy load assemblies - -*This section only applies to client-side Blazor scenarios.* - -Load assemblies at runtime when the assemblies are required by a route. For more information, see . - -### Compression - -*This section only applies to Blazor WebAssembly apps.* - -When a Blazor WebAssembly app is published, the output is statically compressed during publish to reduce the app's size and remove the overhead for runtime compression. Blazor relies on the server to perform content negotiation and serve statically-compressed files. - -After an app is deployed, verify that the app serves compressed files. Inspect the **Network** tab in a browser's [developer tools](https://developer.mozilla.org/docs/Glossary/Developer_Tools) and verify that the files are served with `Content-Encoding: br` (Brotli compression) or `Content-Encoding: gz` (Gzip compression). If the host isn't serving compressed files, follow the instructions in . - -### Disable unused features - -*This section only applies to client-side Blazor scenarios.* - -Blazor WebAssembly's runtime includes the following .NET features that can be disabled for a smaller payload size: - -* Blazor WebAssembly carries globalization resources required to display values, such as dates and currency, in the user's culture. If the app doesn't require localization, you may [configure the app to support the invariant culture](xref:blazor/globalization-localization#invariant-globalization), which is based on the `en-US` culture. - -:::moniker range=">= aspnetcore-8.0" - -* Adopting [invariant globalization](xref:blazor/globalization-localization#invariant-globalization) only results in using non-localized timezone names. To trim timezone code and data from the app, apply the `` MSBuild property with a value of `true` in the app's project file: - - ```xml - - true - - ``` - - > [!NOTE] - > [``](xref:blazor/performance#disable-unused-features) overrides an earlier `` setting. We recommend removing the `` setting. - -:::moniker-end - -:::moniker range="< aspnetcore-8.0" - -* A data file is included to make timezone information correct. If the app doesn't require this feature, consider disabling it by setting the `` MSBuild property to `false` in the app's project file: - - ```xml - - false - - ``` - -:::moniker-end - -:::moniker range="< aspnetcore-5.0" - -* Collation information is included to make APIs such as 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`: - - ```xml - - false - - ``` - -:::moniker-end diff --git a/aspnetcore/blazor/performance/webassembly-browser-developer-tools-diagnostics.md b/aspnetcore/blazor/performance/webassembly-browser-developer-tools-diagnostics.md new file mode 100644 index 000000000000..8f839e396705 --- /dev/null +++ b/aspnetcore/blazor/performance/webassembly-browser-developer-tools-diagnostics.md @@ -0,0 +1,105 @@ +--- +title: ASP.NET Core Blazor WebAssembly browser developer tools diagnostics +author: guardrex +description: Learn about browser developer tools diagnostics in ASP.NET Core Blazor WebAssembly apps. +monikerRange: '>= aspnetcore-10.0' +ms.author: riande +ms.custom: mvc +ms.date: 05/02/2025 +uid: blazor/performance/webassembly-browser-developer-tools +--- +# ASP.NET Core Blazor WebAssembly browser developer tools diagnostics + + + +This article describes browser developer tools diagnostic tools in Blazor WebAssembly apps. + +## Prerequisite + +Install the [.NET WebAssembly build tools](xref:blazor/tooling/webassembly#net-webassembly-build-tools): + +```dotnetcli +dotnet workload install wasm-tools +``` + +## Browser developer tools + +App code can be manually profiled using the diagnostic profiler in a browser's developer tools console. + +Built-in diagnostic features are available to track: + +* [Ahead-of-time (AOT) compilation](xref:blazor/tooling/webassembly#ahead-of-time-aot-compilation) +* Call specification (":::no-loc text="callspec":::", sequence and timing of function calls) and instrumentation + +The MSBuild properties in the following table enable profiler integration. + +Property | Default | Set value to… | Description +--- | :---: | :---: | --- +`` | No value | `browser` | Mono profilers to use. Potential values are "`browser`" and "`log`". To use both, separate the values with a semicolon. The `browser` profiler enables integration with the browser's developer tools profiler. +`` | `true` | `false` | Controls stripping the native executable. +`` | `true` | `true` | Controls building with native debug symbols. + +Enabling profilers has negative size and performance impact, so don't publish an app for production with profilers enabled. In the following example, a condition is set on a property group section that only enables profiling when the app is built with `/p:BlazorSampleProfilingEnabled=true` (.NET CLI) or `true` in a Visual Studio publish profile, where "`BlazorSampleProfilingEnabled`" is a custom symbol name that you choose and doesn't conflict with other symbol names. + +In the app's project file (`.csproj`): + +```xml + + browser; + false + true + +``` + +Alternatively, enable features when the app is built with the .NET CLI. The following options passed to the `dotnet build` command mirror the preceding MS Build property configuration: + +```dotnetcli +/p:WasmProfilers=browser /p:WasmNativeStrip=false /p:WasmNativeDebugSymbols=true +``` + +Setting WebAssembly profilers with `browser;` doesn't require AOT. + +The browser developer tools profiler can be used with AOT (``/`` set to `true`) and without WebAssembly profilers (`browser;` removed from the preceding property group). + +To see AOT method names in the developer tools console, install [DWARF chrome extension](https://chromewebstore.google.com/detail/cc++-devtools-support-dwa/pdcpmagijalfljmkmjngeonclgbbannb). + +## Set the sample interval + +Configure the sample interval in the app's project file. In the following example, the `{INTERVAL}` placeholder represents the time in milliseconds. The default setting if `sampleIntervalMs` isn't specified is 10 ms. + +```xml + + browser:interval={INTERVAL}; + +``` + +## Call specification (:::no-loc text="callspec":::) + +If you want to filter profiled methods, you can use call specification (:::no-loc text="callspec":::). For more information, see [Trace MonoVM profiler events during startup](https://github.com/dotnet/runtime/blob/main/docs/design/mono/diagnostics-tracing.md#trace-monovm-profiler-events-during-startup). + +Add `callspec` to the `browser` WebAssembly profiler in the `` element. In the following example, the `{APP NAMESPACE}` placeholder is the app's namespace: + +```xml +browser:callspec=N:{APP NAMESPACE}; +``` + +## .NET Core Diagnostics Client Library example + +In the project file (`.csproj`), use the `` property set to `browser` to enable integration with the Mono profiler. Currently, only "`browser`" is supported. The `browser` profiler enables integration with the browser's developer tools profiler. + +```xml + + browser + +``` + +The [`Timing-Allow-Origin` HTTP header](https://developer.mozilla.org/docs/Web/HTTP/Reference/Headers/Timing-Allow-Origin) allows for more precise time measurements. + +## Additional resources + +* [What diagnostic tools are available in .NET Core?](/dotnet/core/diagnostics/) +* [.NET diagnostic tools](/dotnet/core/diagnostics/tools-overview) diff --git a/aspnetcore/blazor/performance/webassembly-event-pipe-diagnostics.md b/aspnetcore/blazor/performance/webassembly-event-pipe-diagnostics.md new file mode 100644 index 000000000000..5e31220e1ee2 --- /dev/null +++ b/aspnetcore/blazor/performance/webassembly-event-pipe-diagnostics.md @@ -0,0 +1,117 @@ +--- +title: ASP.NET Core Blazor WebAssembly Event Pipe diagnostics +author: guardrex +description: Learn about Event Pipe diagnostics and how to get a Garbage Collector heap dump in ASP.NET Core Blazor WebAssembly apps. +monikerRange: '>= aspnetcore-10.0' +ms.author: riande +ms.custom: mvc +ms.date: 05/02/2025 +uid: blazor/performance/webassembly-event-pipe +--- +# ASP.NET Core Blazor WebAssembly Event Pipe diagnostics + + + +This article describes Event Pipe diagnostic tools, counters, and how to get a Garbage Collector heap dump in Blazor WebAssembly apps. + +## Prerequisite + +Install the [.NET WebAssembly build tools](xref:blazor/tooling/webassembly#net-webassembly-build-tools): + +```dotnetcli +dotnet workload install wasm-tools +``` + +## .NET Core Diagnostics Client Library example + +Parse and validate NetTrace (`.nettrace`) messages using the .NET Core Diagnostics Client Library: + +* [`dotnet/diagnostics` GitHub repository](https://github.com/dotnet/diagnostics) +* [`Microsoft.Diagnostics.NETCore.Client` NuGet package](https://www.nuget.org/packages/Microsoft.Diagnostics.NETCore.Client) + +For more information, see the [.NET Core diagnostics documentation](/dotnet/core/diagnostics/) and the [`IpcMessage` API (reference source)](https://github.com/dotnet/diagnostics/blob/main/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcMessage.cs). + +[!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)] + +The MSBuild properties in the following table enable profiler integration. + +Property | Default | Set value to… | Description +--- | :---: | :---: | --- +`` | `false` | `true` | Controls diagnostic server tracing. +`` | `false` | `true` | Controls `System.Diagnostics.Metrics` support. For more information, see the [`System.Diagnostics.Metrics` namespace](/dotnet/api/system.diagnostics.metrics). +`` | `false`| `true` | Controls `EventPipe` support. For more information, see [Diagnostics and instrumentation: Observability and telemetry](/dotnet/core/deploying/native-aot/diagnostics#observability-and-telemetry). + +Enabling profilers has negative size and performance impact, so don't publish an app for production with profilers enabled. In the following example, a condition is set on a property group section that only enables profiling when the app is built with `/p:BlazorSampleProfilingEnabled=true` (.NET CLI) or `true` in a Visual Studio publish profile, where "`BlazorSampleProfilingEnabled`" is a custom symbol name that you choose and doesn't conflict with other symbol names. + +In the app's project file (`.csproj`): + +```xml + + true + true + true + +``` + +Alternatively, enable features when the app is built with the .NET CLI. The following options passed to the `dotnet build` command mirror the preceding MS Build property configuration: + +```dotnetcli +/p:WasmPerfTracing=true /p:WasmPerfInstrumentation=true /p:MetricsSupport=true /p:EventSourceSupport=true +``` + +The [`Timing-Allow-Origin` HTTP header](https://developer.mozilla.org/docs/Web/HTTP/Reference/Headers/Timing-Allow-Origin) allows for more precise time measurements. + +## EventPipe profiler + +[EventPipe](/dotnet/core/diagnostics/eventpipe) is a runtime component used to collect tracing data, similar to [ETW](/windows/win32/etw/event-tracing-portal) and [perf_events](https://wikipedia.org/wiki/Perf_%28Linux%29). + +Use the `` property to enable CPU sampling instrumentation for diagnostic server. This setting isn't necessary for memory dump or counters. **Makes the app execute slower. Only set this to `true` for performance profiling.** + +Enabling profilers has negative size and performance impact, so don't publish an app for production with profilers enabled. In the following example, a condition is set on a property group section that only enables profiling when the app is built with `/p:BlazorSampleProfilingEnabled=true` (.NET CLI) or `true` in a Visual Studio publish profile, where "`BlazorSampleProfilingEnabled`" is a custom symbol name that you choose and doesn't conflict with other symbol names. + +```xml + + true + +``` + +Collect CPU counters for 60 seconds with `collectCpuSamples(durationSeconds)`: + +```javascript +globalThis.getDotnetRuntime(0).collectCpuSamples({durationSeconds: 60}); +``` + +To view the trace, see [Use EventPipe to trace your .NET application](/dotnet/core/diagnostics/eventpipe#use-eventpipe-to-trace-your-net-application). + +## GC (Garbage Collector) dumps + +The [`dotnet-gcdump` (`collect`/convert` options)](/dotnet/core/diagnostics/dotnet-gcdump) global tool collects GC (Garbage Collector) dumps of live .NET processes using [EventPipe](/dotnet/core/diagnostics/eventpipe). + +Collect a GC (Garbage Collector) dump of the live .NET process with `collectGcDump`: + +```javascript +globalThis.getDotnetRuntime(0).collectGcDump(); +``` + +To view the captured GC dump, see [View the GC dump captured from dotnet-gcdump](/dotnet/core/diagnostics/dotnet-gcdump#view-the-gc-dump-captured-from-dotnet-gcdump). + +## Counters trace + +[`dotnet-counters collect`](/dotnet/core/diagnostics/dotnet-counters) is a performance monitoring tool for ad-hoc health monitoring and first-level performance investigation. + +Collect diagnostic counters for 60 seconds with `collectPerfCounters(durationSeconds)`: + +```javascript +globalThis.getDotnetRuntime(0).collectPerfCounters({durationSeconds: 60}); +``` + +To view the trace, see [Use EventPipe to trace your .NET application](/dotnet/core/diagnostics/eventpipe#use-eventpipe-to-trace-your-net-application). + +## Additional resources + +* [What diagnostic tools are available in .NET Core?](/dotnet/core/diagnostics/) +* [.NET diagnostic tools](/dotnet/core/diagnostics/tools-overview) diff --git a/aspnetcore/blazor/webassembly-lazy-load-assemblies.md b/aspnetcore/blazor/webassembly-lazy-load-assemblies.md index 2317df26779b..e1f857aac198 100644 --- a/aspnetcore/blazor/webassembly-lazy-load-assemblies.md +++ b/aspnetcore/blazor/webassembly-lazy-load-assemblies.md @@ -822,4 +822,4 @@ When the `Robot` component from the RCL is requested at `/robot`, the `GrantImah ## Additional resources * [Handle asynchronous navigation events with `OnNavigateAsync`](xref:blazor/fundamentals/routing#handle-asynchronous-navigation-events-with-onnavigateasync) -* +* diff --git a/aspnetcore/diagnostics/bl0001.md b/aspnetcore/diagnostics/bl0001.md index f9be1941fa5a..f10992c5bbd2 100644 --- a/aspnetcore/diagnostics/bl0001.md +++ b/aspnetcore/diagnostics/bl0001.md @@ -50,7 +50,7 @@ Component parameters are required to have publicly accessible setters to allow t } ``` -* If making the property non-public is not possible, consider [implementing `SetParametersAsync` manually](xref:blazor/performance#implement-setparametersasync-manually). +* If making the property non-public is not possible, consider [implementing `SetParametersAsync` manually](xref:blazor/performance/rendering#implement-setparametersasync-manually). ## When to suppress warnings diff --git a/aspnetcore/diagnostics/bl0004.md b/aspnetcore/diagnostics/bl0004.md index 0796d47fd011..6629468966eb 100644 --- a/aspnetcore/diagnostics/bl0004.md +++ b/aspnetcore/diagnostics/bl0004.md @@ -41,7 +41,7 @@ Component parameters are required to be public and must have a public setter. } ``` -* If making the property non-public is not possible, consider [implementing `SetParametersAsync` manually](xref:blazor/performance#implement-setparametersasync-manually). +* If making the property non-public is not possible, consider [implementing `SetParametersAsync` manually](xref:blazor/performance/rendering#implement-setparametersasync-manually). ## When to suppress warnings diff --git a/aspnetcore/performance/diagnostic-tools.md b/aspnetcore/performance/diagnostic-tools.md index 77bf092c6abc..8f317d47ec98 100644 --- a/aspnetcore/performance/diagnostic-tools.md +++ b/aspnetcore/performance/diagnostic-tools.md @@ -13,6 +13,8 @@ By [Mike Rousos](https://github.com/mjrousos) This article lists tools for diagnosing performance issues in ASP.NET Core. +For information on Blazor WebAssembly performance diagnostic tools and counters, see and . + ## Visual Studio Diagnostic Tools The [profiling and diagnostic tools](/visualstudio/profiling) built into Visual Studio are a good place to start investigating performance issues. These tools are powerful and convenient to use from the Visual Studio development environment. The tooling allows analysis of CPU usage, memory usage, and performance events in ASP.NET Core apps. Being built-in makes profiling easy at development time. diff --git a/aspnetcore/performance/memory.md b/aspnetcore/performance/memory.md index 4b9290ed604f..f95d2a836faf 100644 --- a/aspnetcore/performance/memory.md +++ b/aspnetcore/performance/memory.md @@ -7,7 +7,6 @@ ms.custom: mvc ms.date: 4/05/2019 uid: performance/memory --- - # Memory management and garbage collection (GC) in ASP.NET Core By [Sébastien Ros](https://github.com/sebastienros) and [Rick Anderson](https://twitter.com/RickAndMSFT) @@ -427,6 +426,7 @@ The main difference is allocated bytes, and as a consequence much fewer generati ## Additional resources +* * [Garbage Collection](/dotnet/standard/garbage-collection/) * [Understanding different GC modes with Concurrency Visualizer](https://blogs.msdn.microsoft.com/seteplia/2017/01/05/understanding-different-gc-modes-with-concurrency-visualizer/) * [Large Object Heap Uncovered](https://devblogs.microsoft.com/dotnet/large-object-heap-uncovered-from-an-old-msdn-article/) diff --git a/aspnetcore/performance/overview.md b/aspnetcore/performance/overview.md index 4578a97bd206..14428678afb9 100644 --- a/aspnetcore/performance/overview.md +++ b/aspnetcore/performance/overview.md @@ -13,7 +13,7 @@ The following articles provide information about how to optimize the performance :::moniker range=">= aspnetcore-8.0" -* +* * * * @@ -30,7 +30,7 @@ The following articles provide information about how to optimize the performance :::moniker range="< aspnetcore-8.0" -* +* * * * diff --git a/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md b/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md index ccf25f2e19d5..709bb77b3b3b 100644 --- a/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md +++ b/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md @@ -346,4 +346,11 @@ For more information, see the following sections of the *Call JavaScript functio * [Create an instance of a JS object using a constructor function](xref:blazor/js-interop/call-javascript-from-dotnet#create-an-instance-of-a-js-object-using-a-constructor-function) * [Read or modify the value of a JS object property](xref:blazor/js-interop/call-javascript-from-dotnet#read-or-modify-the-value-of-a-js-object-property) +### Blazor WebAssembly performance profiling and diagnostic counters + +New performance profiling and diagnostic counters are available for Blazor WebAssembly apps. For more information, see the following articles: + +* +* + --> diff --git a/aspnetcore/toc.yml b/aspnetcore/toc.yml index 89bfd084bb96..d97cf7d16b4d 100644 --- a/aspnetcore/toc.yml +++ b/aspnetcore/toc.yml @@ -651,7 +651,19 @@ items: - name: WebAssembly native dependencies uid: blazor/webassembly-native-dependencies - name: Performance - uid: blazor/performance + items: + - name: Overview + uid: blazor/performance/index + - name: Rendering speed + uid: blazor/performance/rendering + - name: App download size + uid: blazor/performance/app-download-size + - name: JavaScript interop + uid: blazor/performance/js-interop + - name: WebAssembly browser developer tools diagnostics + uid: blazor/performance/webassembly-browser-developer-tools + - name: WebAssembly Event Pipe diagnostics + uid: blazor/performance/webassembly-event-pipe - name: Test components uid: blazor/test - name: Progressive Web Applications