Lazy load assemblies in ASP.NET Core Blazor WebAssembly (2024)

  • Article

Note

This isn't the latest version of this article. For the current release, see the .NET 8 version of this article.

Warning

This version of ASP.NET Core is no longer supported. For more information, see .NET and .NET Core Support Policy. For the current release, see the .NET 8 version of this article.

Important

This information relates to a pre-release product that may be substantially modified before it's commercially released. Microsoft makes no warranties, express or implied, with respect to the information provided here.

For the current release, see the .NET 8 version of this article.

Blazor WebAssembly app startup performance can be improved by waiting to load developer-created app assemblies until the assemblies are required, which is called lazy loading.

This article's initial sections cover the app configuration. For a working demonstration, see the Complete example section at the end of this article.

This article only applies to Blazor WebAssembly apps. Assembly lazy loading doesn't benefit server-side apps because server-rendered apps don't download assemblies to the client.

Lazy loading shouldn't be used for core runtime assemblies, which might be trimmed on publish and unavailable on the client when the app loads.

File extension placeholder ({FILE EXTENSION}) for assembly files

Assembly files use the Webcil packaging format for .NET assemblies with a .wasm file extension.

Throughout the article, the {FILE EXTENSION} placeholder represents "wasm".

Assembly files are based on Dynamic-Link Libraries (DLLs) with a .dll file extension.

Throughout the article, the {FILE EXTENSION} placeholder represents "dll".

Project file configuration

Mark assemblies for lazy loading in the app's project file (.csproj) using the BlazorWebAssemblyLazyLoad item. Use the assembly name with file extension. The Blazor framework prevents the assembly from loading at app launch.

<ItemGroup> <BlazorWebAssemblyLazyLoad Include="{ASSEMBLY NAME}.{FILE EXTENSION}" /></ItemGroup>

The {ASSEMBLY NAME} placeholder is the name of the assembly, and the {FILE EXTENSION} placeholder is the file extension. The file extension is required.

Include one BlazorWebAssemblyLazyLoad item for each assembly. If an assembly has dependencies, include a BlazorWebAssemblyLazyLoad entry for each dependency.

Router component configuration

The Blazor framework automatically registers a singleton service for lazy loading assemblies in client-side Blazor WebAssembly apps, LazyAssemblyLoader. The LazyAssemblyLoader.LoadAssembliesAsync method:

  • Uses JS interop to fetch assemblies via a network call.
  • Loads assemblies into the runtime executing on WebAssembly in the browser.

Note

Guidance for hosted Blazor WebAssembly solutions is covered in the Lazy load assemblies in a hosted Blazor WebAssembly solution section.

Blazor's Router component designates the assemblies that Blazor searches for routable components and is also responsible for rendering the component for the route where the user navigates. The Router component's OnNavigateAsync method is used in conjunction with lazy loading to load the correct assemblies for endpoints that a user requests.

Logic is implemented inside OnNavigateAsync to determine the assemblies to load with LazyAssemblyLoader. Options for how to structure the logic include:

  • Conditional checks inside the OnNavigateAsync method.
  • A lookup table that maps routes to assembly names, either injected into the component or implemented within the @code block.

In the following example:

  • The namespace for Microsoft.AspNetCore.Components.WebAssembly.Services is specified.
  • The LazyAssemblyLoader service is injected (AssemblyLoader).
  • The {PATH} placeholder is the path where the list of assemblies should load. The example uses a conditional check for a single path that loads a single set of assemblies.
  • The {LIST OF ASSEMBLIES} placeholder is the comma-separated list of assembly file name strings, including their file extensions (for example, "Assembly1.{FILE EXTENSION}", "Assembly2.{FILE EXTENSION}").

App.razor:

@using Microsoft.AspNetCore.Components.Routing@using Microsoft.AspNetCore.Components.WebAssembly.Services@using Microsoft.Extensions.Logging@inject LazyAssemblyLoader AssemblyLoader@inject ILogger<App> Logger<Router AppAssembly="typeof(App).Assembly" OnNavigateAsync="OnNavigateAsync"> ...</Router>@code { private async Task OnNavigateAsync(NavigationContext args) { try { if (args.Path == "{PATH}") { var assemblies = await AssemblyLoader.LoadAssembliesAsync( new[] { {LIST OF ASSEMBLIES} }); } } catch (Exception ex) { Logger.LogError("Error: {Message}", ex.Message); } }}
@using Microsoft.AspNetCore.Components.Routing@using Microsoft.AspNetCore.Components.WebAssembly.Services@using Microsoft.Extensions.Logging@inject LazyAssemblyLoader AssemblyLoader@inject ILogger<App> Logger<Router AppAssembly="typeof(Program).Assembly" OnNavigateAsync="OnNavigateAsync"> ...</Router>@code { private async Task OnNavigateAsync(NavigationContext args) { try { if (args.Path == "{PATH}") { var assemblies = await AssemblyLoader.LoadAssembliesAsync( new[] { {LIST OF ASSEMBLIES} }); } } catch (Exception ex) { Logger.LogError("Error: {Message}", ex.Message); } }}

Note

The preceding example doesn't show the contents of the Router component's Razor markup (...). For a demonstration with complete code, see the Complete example section of this article.

Note

With the release of ASP.NET Core 5.0.1 and for any additional 5.x releases, the Router component includes the PreferExactMatches parameter set to @true. For more information, see Migrate from ASP.NET Core 3.1 to 5.0.

Assemblies that include routable components

When the list of assemblies includes routable components, the assembly list for a given path is passed to the Router component's AdditionalAssemblies collection.

In the following example:

  • The List<Assembly> in lazyLoadedAssemblies passes the assembly list to AdditionalAssemblies. The framework searches the assemblies for routes and updates the route collection if new routes are found. To access the Assembly type, the namespace for System.Reflection is included at the top of the App.razor file.
  • The {PATH} placeholder is the path where the list of assemblies should load. The example uses a conditional check for a single path that loads a single set of assemblies.
  • The {LIST OF ASSEMBLIES} placeholder is the comma-separated list of assembly file name strings, including their file extensions (for example, "Assembly1.{FILE EXTENSION}", "Assembly2.{FILE EXTENSION}").

App.razor:

@using System.Reflection@using Microsoft.AspNetCore.Components.Routing@using Microsoft.AspNetCore.Components.WebAssembly.Services@using Microsoft.Extensions.Logging@inject ILogger<App> Logger@inject LazyAssemblyLoader AssemblyLoader<Router AppAssembly="typeof(App).Assembly" AdditionalAssemblies="lazyLoadedAssemblies" OnNavigateAsync="OnNavigateAsync"> ...</Router>@code { private List<Assembly> lazyLoadedAssemblies = new(); private async Task OnNavigateAsync(NavigationContext args) { try { if (args.Path == "{PATH}") { var assemblies = await AssemblyLoader.LoadAssembliesAsync( new[] { {LIST OF ASSEMBLIES} }); lazyLoadedAssemblies.AddRange(assemblies); } } catch (Exception ex) { Logger.LogError("Error: {Message}", ex.Message); } }}
@using System.Reflection@using Microsoft.AspNetCore.Components.Routing@using Microsoft.AspNetCore.Components.WebAssembly.Services@using Microsoft.Extensions.Logging@inject ILogger<App> Logger@inject LazyAssemblyLoader AssemblyLoader<Router AppAssembly="typeof(Program).Assembly" AdditionalAssemblies="lazyLoadedAssemblies" OnNavigateAsync="OnNavigateAsync"> ...</Router>@code { private List<Assembly> lazyLoadedAssemblies = new List<Assembly>(); private async Task OnNavigateAsync(NavigationContext args) { try { if (args.Path == "{PATH}") { var assemblies = await AssemblyLoader.LoadAssembliesAsync( new[] { {LIST OF ASSEMBLIES} }); lazyLoadedAssemblies.AddRange(assemblies); } } catch (Exception ex) { Logger.LogError("Error: {Message}", ex.Message); } }}

Note

The preceding example doesn't show the contents of the Router component's Razor markup (...). For a demonstration with complete code, see the Complete example section of this article.

Note

With the release of ASP.NET Core 5.0.1 and for any additional 5.x releases, the Router component includes the PreferExactMatches parameter set to @true. For more information, see Migrate from ASP.NET Core 3.1 to 5.0.

For more information, see ASP.NET Core Blazor routing and navigation.

User interaction with <Navigating> content

While loading assemblies, which can take several seconds, the Router component can indicate to the user that a page transition is occurring with the router's Navigating property.

For more information, see ASP.NET Core Blazor routing and navigation.

Handle cancellations in OnNavigateAsync

The NavigationContext object passed to the OnNavigateAsync callback contains a CancellationToken that's set when a new navigation event occurs. The OnNavigateAsync callback must throw when the cancellation token is set to avoid continuing to run the OnNavigateAsync callback on an outdated navigation.

For more information, see ASP.NET Core Blazor routing and navigation.

OnNavigateAsync events and renamed assembly files

The resource loader relies on the assembly names that are defined in the blazor.boot.json file. If assemblies are renamed, the assembly names used in an OnNavigateAsync callback and the assembly names in the blazor.boot.json file are out of sync.

To rectify this:

  • Check to see if the app is running in the Production environment when determining which assembly names to use.
  • Store the renamed assembly names in a separate file and read from that file to determine what assembly name to use with the LazyAssemblyLoader service and OnNavigateAsync callback.

Lazy load assemblies in a hosted Blazor WebAssembly solution

The framework's lazy loading implementation supports lazy loading with prerendering in a hosted Blazor WebAssembly solution. During prerendering, all assemblies, including those marked for lazy loading, are assumed to be loaded. Manually register the LazyAssemblyLoader service in the Server project.

At the top of the Program.cs file of the Server project, add the namespace for Microsoft.AspNetCore.Components.WebAssembly.Services:

using Microsoft.AspNetCore.Components.WebAssembly.Services;

In Program.cs of the Server project, register the service:

builder.Services.AddScoped<LazyAssemblyLoader>();

At the top of the Startup.cs file of the Server project, add the namespace for Microsoft.AspNetCore.Components.WebAssembly.Services:

using Microsoft.AspNetCore.Components.WebAssembly.Services;

In Startup.ConfigureServices (Startup.cs) of the Server project, register the service:

services.AddScoped<LazyAssemblyLoader>();

Complete example

The demonstration in this section:

  • Creates a robot controls assembly (GrantImaharaRobotControls.{FILE EXTENSION}) as a Razor class library (RCL) that includes a Robot component (Robot.razor with a route template of /robot).
  • Lazily loads the RCL's assembly to render its Robot component when the /robot URL is requested by the user.

Create a standalone Blazor WebAssembly app to demonstrate lazy loading of a Razor class library's assembly. Name the project LazyLoadTest.

Add an ASP.NET Core class library project to the solution:

  • Visual Studio: Right-click the solution file in Solution Explorer and select Add > New project. From the dialog of new project types, select Razor Class Library. Name the project GrantImaharaRobotControls. Do not select the Support pages and view checkbox.
  • Visual Studio Code/.NET CLI: Execute dotnet new razorclasslib -o GrantImaharaRobotControls from a command prompt. The -o|--output option creates a folder and names the project GrantImaharaRobotControls.

The example component presented later in this section uses a Blazor form. In the RCL project, add the Microsoft.AspNetCore.Components.Forms package to the project.

Note

For guidance on adding packages to .NET apps, see the articles under Install and manage packages at Package consumption workflow (NuGet documentation). Confirm correct package versions at NuGet.org.

Create a HandGesture class in the RCL with a ThumbUp method that hypothetically makes a robot perform a thumbs-up gesture. The method accepts an argument for the axis, Left or Right, as an enum. The method returns true on success.

HandGesture.cs:

using Microsoft.Extensions.Logging;namespace GrantImaharaRobotControls;public static class HandGesture{ public static bool ThumbUp(Axis axis, ILogger logger) { logger.LogInformation("Thumb up gesture. Axis: {Axis}", axis); // Code to make robot perform gesture return true; }}public enum Axis { Left, Right }
using Microsoft.Extensions.Logging;namespace GrantImaharaRobotControls{ public static class HandGesture { public static bool ThumbUp(Axis axis, ILogger logger) { logger.LogInformation("Thumb up gesture. Axis: {Axis}", axis); // Code to make robot perform gesture return true; } } public enum Axis { Left, Right }}

Add the following component to the root of the RCL project. The component permits the user to submit a left or right hand thumb-up gesture request.

Robot.razor:

@page "/robot"@using Microsoft.AspNetCore.Components.Forms@using Microsoft.Extensions.Logging@inject ILogger<Robot> Logger<h1>Robot</h1><EditForm FormName="RobotForm" Model="robotModel" OnValidSubmit="HandleValidSubmit"> <InputRadioGroup @bind-Value="robotModel.AxisSelection"> @foreach (var entry in Enum.GetValues<Axis>()) { <InputRadio Value="entry" /> <text>&nbsp;</text>@entry<br> } </InputRadioGroup> <button type="submit">Submit</button></EditForm><p> @message</p>@code { private RobotModel robotModel = new() { AxisSelection = Axis.Left }; private string? message; private void HandleValidSubmit() { Logger.LogInformation("HandleValidSubmit called"); var result = HandGesture.ThumbUp(robotModel.AxisSelection, Logger); message = $"ThumbUp returned {result} at {DateTime.Now}."; } public class RobotModel { public Axis AxisSelection { get; set; } }}
@page "/robot"@using Microsoft.AspNetCore.Components.Forms@using Microsoft.Extensions.Logging@inject ILogger<Robot> Logger<h1>Robot</h1><EditForm Model="robotModel" OnValidSubmit="HandleValidSubmit"> <InputRadioGroup @bind-Value="robotModel.AxisSelection"> @foreach (var entry in Enum.GetValues<Axis>()) { <InputRadio Value="entry" /> <text>&nbsp;</text>@entry<br> } </InputRadioGroup> <button type="submit">Submit</button></EditForm><p> @message</p>@code { private RobotModel robotModel = new() { AxisSelection = Axis.Left }; private string? message; private void HandleValidSubmit() { Logger.LogInformation("HandleValidSubmit called"); var result = HandGesture.ThumbUp(robotModel.AxisSelection, Logger); message = $"ThumbUp returned {result} at {DateTime.Now}."; } public class RobotModel { public Axis AxisSelection { get; set; } }}
@page "/robot"@using Microsoft.AspNetCore.Components.Forms@using Microsoft.Extensions.Logging@inject ILogger<Robot> Logger<h1>Robot</h1><EditForm Model="robotModel" OnValidSubmit="HandleValidSubmit"> <InputRadioGroup @bind-Value="robotModel.AxisSelection"> @foreach (var entry in (Axis[])Enum .GetValues(typeof(Axis))) { <InputRadio Value="entry" /> <text>&nbsp;</text>@entry<br> } </InputRadioGroup> <button type="submit">Submit</button></EditForm><p> @message</p>@code { private RobotModel robotModel = new RobotModel() { AxisSelection = Axis.Left }; private string message; private void HandleValidSubmit() { Logger.LogInformation("HandleValidSubmit called"); var result = HandGesture.ThumbUp(robotModel.AxisSelection, Logger); message = $"ThumbUp returned {result} at {DateTime.Now}."; } public class RobotModel { public Axis AxisSelection { get; set; } }}

In the LazyLoadTest project, create a project reference for the GrantImaharaRobotControls RCL:

  • Visual Studio: Right-click the LazyLoadTest project and select Add > Project Reference to add a project reference for the GrantImaharaRobotControls RCL.
  • Visual Studio Code/.NET CLI: Execute dotnet add reference {PATH} in a command shell from the project's folder. The {PATH} placeholder is the path to the RCL project.

Specify the RCL's assembly for lazy loading in the LazyLoadTest app's project file (.csproj):

<ItemGroup> <BlazorWebAssemblyLazyLoad Include="GrantImaharaRobotControls.{FILE EXTENSION}" /></ItemGroup>

The following Router component demonstrates loading the GrantImaharaRobotControls.{FILE EXTENSION} assembly when the user navigates to /robot. Replace the app's default App component with the following App component.

During page transitions, a styled message is displayed to the user with the <Navigating> element. For more information, see the User interaction with <Navigating> content section.

The assembly is assigned to AdditionalAssemblies, which results in the router searching the assembly for routable components, where it finds the Robot component. The Robot component's route is added to the app's route collection. For more information, see the ASP.NET Core Blazor routing and navigation article and the Assemblies that include routable components section of this article.

App.razor:

@using System.Reflection@using Microsoft.AspNetCore.Components.Routing@using Microsoft.AspNetCore.Components.WebAssembly.Services@using Microsoft.Extensions.Logging@inject ILogger<App> Logger@inject LazyAssemblyLoader AssemblyLoader<Router AppAssembly="typeof(App).Assembly" AdditionalAssemblies="lazyLoadedAssemblies" OnNavigateAsync="OnNavigateAsync"> <Navigating> <div style="padding:20px;background-color:blue;color:white"> <p>Loading the requested page&hellip;</p> </div> </Navigating> <Found Context="routeData"> <RouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)" /> </Found> <NotFound> <LayoutView Layout="typeof(MainLayout)"> <p>Sorry, there's nothing at this address.</p> </LayoutView> </NotFound></Router>@code { private List<Assembly> lazyLoadedAssemblies = new(); private async Task OnNavigateAsync(NavigationContext args) { try { if (args.Path == "robot") { var assemblies = await AssemblyLoader.LoadAssembliesAsync( new[] { "GrantImaharaRobotControls.{FILE EXTENSION}" }); lazyLoadedAssemblies.AddRange(assemblies); } } catch (Exception ex) { Logger.LogError("Error: {Message}", ex.Message); } }}
@using System.Reflection@using Microsoft.AspNetCore.Components.Routing@using Microsoft.AspNetCore.Components.WebAssembly.Services@using Microsoft.Extensions.Logging@inject ILogger<App> Logger@inject LazyAssemblyLoader AssemblyLoader<Router AppAssembly="typeof(Program).Assembly" AdditionalAssemblies="lazyLoadedAssemblies" OnNavigateAsync="OnNavigateAsync"> <Navigating> <div style="padding:20px;background-color:blue;color:white"> <p>Loading the requested page&hellip;</p> </div> </Navigating> <Found Context="routeData"> <RouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)" /> </Found> <NotFound> <LayoutView Layout="typeof(MainLayout)"> <p>Sorry, there's nothing at this address.</p> </LayoutView> </NotFound></Router>@code { private List<Assembly> lazyLoadedAssemblies = new List<Assembly>(); private async Task OnNavigateAsync(NavigationContext args) { try { if (args.Path == "robot") { var assemblies = await AssemblyLoader.LoadAssembliesAsync( new[] { "GrantImaharaRobotControls.{FILE EXTENSION}" }); lazyLoadedAssemblies.AddRange(assemblies); } } catch (Exception ex) { Logger.LogError("Error: {Message}", ex.Message); } }}

Build and run the app.

When the Robot component from the RCL is requested at /robot, the GrantImaharaRobotControls.{FILE EXTENSION} assembly is loaded and the Robot component is rendered. You can inspect the assembly loading in the Network tab of the browser's developer tools.

Troubleshoot

  • If unexpected rendering occurs, such as rendering a component from a previous navigation, confirm that the code throws if the cancellation token is set.
  • If assemblies configured for lazy loading unexpectedly load at app start, check that the assembly is marked for lazy loading in the project file.

Note

A known issue exists for loading types from a lazily-loaded assembly. For more information, see Blazor WebAssembly lazy loading assemblies not working when using @ref attribute in the component (dotnet/aspnetcore #29342).

Additional resources

  • Handle asynchronous navigation events with OnNavigateAsync
  • ASP.NET Core Blazor performance best practices
Lazy load assemblies in ASP.NET Core Blazor WebAssembly (2024)

FAQs

What are the limitations of Blazor WebAssembly? ›

The Blazor WebAssembly hosting model has the following limitations: Razor components are restricted to the capabilities of the browser. Capable client hardware and software (for example, WebAssembly support) is required. Download size is larger, and components take longer to load.

What is the difference between eager loading and lazy loading in ASP.NET Core? ›

Explaining lazy loading and eager loading

Lazy loading is a method of retrieving related data when it's demanded, while eager loading fetches all related data as part of the initial query using joins in the database when needed.

Why is Blazor not popular? ›

The following are some major Blazor disadvantages: The use of Blazor WebAssembly is not supported by all web browsers, including Internet Explorer, due to certain restrictions. The execution time of Blazor framework depends on the application weight, hence performance is affected by app's complexity.

Is Blazor mature enough? ›

The debugging experience for Blazor applications is not as mature as that of JavaScript applications and it has fewer functionalities. The ecosystem and availability of third-party libraries for Blazor are limited. Still though, it is evolving. Blazor Server requires a constant connection to the server.

How do you do lazy loading when should you use lazy loading? ›

Lazy loading is a technique for waiting to load certain parts of a webpage — especially images — until they are needed. Instead of loading everything all at once, known as "eager" loading, the browser does not request certain resources until the user interacts in such a way that the resources are needed.

Is Blazor faster than react? ›

Blazor tends to offer better performance than React especially when it comes down to load time. The reason behind the same is that it does not require a separate JavaScript.

Why is Blazor slow? ›

With Blazor WASM, there will be an initial cold start delay as your application runtime is downloaded to the client. From there, if you are not making any server-side or API calls, then any performance issues will be directly in the client's browser, and should not depend on the host.

Is Blazor faster than JS? ›

Blazor is faster than JavaScript in many cases

Additionally, by pre-compiling with WebAssembly, server-side code runs faster than languages like JavaScript. The more extensive and advanced the solution you create, the more pronounced will be the difference in the achievable speed.

Is lazy loading good? ›

Lazy loading is a great option for improving page performance and keeping visitors on your site.

What are the disadvantages of lazy loading in Entity Framework? ›

Cons: Increased memory usage: Loading all data in a single query can result in increased memory usage, especially for large data sets. Reduced performance: Loading all data in a single query can result in slower performance, especially for large data sets.

What is an example of lazy load? ›

The technique of lazy loading can be applied to almost all the resources on a page. For example, in a single page application, if a JS file is not needed until later, it is best not to load it initially. If an image is not needed up front, load it later when it actually needs to be viewed.

What is the limit of Blazor? ›

The maximum supported file size for the InputFile component is 2 GB. Additionally, client-side Blazor reads the file's bytes into a single JavaScript array buffer when marshalling the data from JavaScript to C#, which is limited to 2 GB or to the device's available memory.

What are the disadvantages of Wasm? ›

A notable drawback of Wasm is its lack of garbage collection. Because Wasm does not possess native memory management, it relies on the underlying code in the original programming language to provide memory management features.

What is the memory limitation of WebAssembly? ›

maximum is specified and is smaller than initial . initial exceeds 65,536 (2^16). 2^16 pages is 2^16 * 64KiB = 4GiB bytes, which is the maximum range that a Wasm module can address, as Wasm currently only allows 32-bit addressing.

Is Blazor WebAssembly SEO friendly? ›

It has the potential to be as SEO-friendly as you make it - really depends on the page/component rendering model you pick. Blazor Server-side Rendering (not Server) is going to be the best SEO experience for the least work.

Top Articles
Latest Posts
Article information

Author: Barbera Armstrong

Last Updated:

Views: 5832

Rating: 4.9 / 5 (79 voted)

Reviews: 86% of readers found this page helpful

Author information

Name: Barbera Armstrong

Birthday: 1992-09-12

Address: Suite 993 99852 Daugherty Causeway, Ritchiehaven, VT 49630

Phone: +5026838435397

Job: National Engineer

Hobby: Listening to music, Board games, Photography, Ice skating, LARPing, Kite flying, Rugby

Introduction: My name is Barbera Armstrong, I am a lovely, delightful, cooperative, funny, enchanting, vivacious, tender person who loves writing and wants to share my knowledge and understanding with you.