9.3 KiB
title | author | description | monikerRange | ms.author | ms.custom | ms.date | no-loc | uid | |||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Lazy load assemblies in ASP.NET Core Blazor WebAssembly | guardrex | Discover how to lazy load assemblies in ASP.NET Core Blazor WebAssembly apps. | >= aspnetcore-5.0 | riande | mvc | 07/16/2020 |
|
blazor/webassembly-lazy-load-assemblies |
Lazy load assemblies in ASP.NET Core Blazor WebAssembly
By Safia Abdalla and Luke Latham
Blazor WebAssembly app startup performance can be improved by deferring the loading of some application assemblies until they are required, which is called lazy loading. For example, assemblies that are only used to render a single component can be set up to load only if the user navigates to that component. After loading, the assemblies are cached client-side and are available for all future navigations.
Blazor's lazy loading feature allows you to mark app assemblies for lazy loading, which loads the assemblies during runtime when the user navigates to a particular route. The feature consists of changes to the project file and changes to the application's router.
[!NOTE] Assembly lazy loading doesn't benefit Blazor Server apps because assemblies aren't downloaded to the client in a Blazor Server app.
Project file
Mark assemblies for lazy loading in the app's project file (.csproj
) using the BlazorWebAssemblyLazyLoad
item. Use the assembly name without the .dll
extension. The Blazor framework prevents the assemblies specified by this item group from loading at app launch. The following example marks a large custom assembly (GrantImaharaRobotControls.dll
) for lazy loading. If an assembly that's marked for lazy loading has dependencies, they must also be marked for lazy loading in the project file.
<ItemGroup>
<BlazorWebAssemblyLazyLoad Include="GrantImaharaRobotControls" />
</ItemGroup>
Only assemblies that are used by the app can be lazily loaded. The linker strips unused assemblies from published output.
Router
component
Blazor's Router
component designates which assemblies Blazor searches for routable components. The Router
component is also responsible for rendering the component for the route where the user navigates. The Router
component supports an OnNavigateAsync
feature that can be used in conjunction with lazy loading.
In the app's Router
component (App.razor
):
- Add an
OnNavigateAsync
callback. TheOnNavigateAsync
handler is invoked when the user:- Visits a route for the first time by navigating to it directly from their browser.
- Navigates to a new route using a link or a xref:Microsoft.AspNetCore.Components.NavigationManager.NavigateTo%2A?displayProperty=nameWithType invocation.
- If lazy-loaded assemblies contain routable components, add a List<xref:System.Reflection.Assembly> (for example, named
lazyLoadedAssemblies
) to the component. The assemblies are passed back to the xref:Microsoft.AspNetCore.Components.Routing.Router.AdditionalAssemblies collection in case the assemblies contain routable components. The framework searches the assemblies for routes and updates the route collection if any new routes are found.
@using System.Reflection
<Router AppAssembly="@typeof(Program).Assembly"
AdditionalAssemblies="@lazyLoadedAssemblies" OnNavigateAsync="@OnNavigateAsync">
...
</Router>
@code {
private List<Assembly> lazyLoadedAssemblies = new List<Assembly>();
private async Task OnNavigateAsync(NavigationContext args)
{
}
}
If the OnNavigateAsync
callback throws an unhandled exception, the Blazor error UI is invoked.
Assembly load logic in OnNavigateAsync
OnNavigateAsync
has a NavigationContext
parameter that provides information about the current asynchronous navigation event, including the target path (Path
) and the cancellation token (CancellationToken
):
- The
Path
property is the user's destination path relative to the app's base path, such as/robot
. - The
CancellationToken
can be used to observe the cancellation of the asynchronous task.OnNavigateAsync
automatically cancels the currently running navigation task when the user navigates to a different page.
Inside OnNavigateAsync
, implement logic to determine the assemblies to load. Options 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.
LazyAssemblyLoader
is a framework-provided singleton service for loading assemblies. Inject LazyAssemblyLoader
into the Router
component:
...
@using Microsoft.AspNetCore.Components.WebAssembly.Services
@inject LazyAssemblyLoader assemblyLoader
...
The LazyAssemblyLoader
provides the LoadAssembliesAsync
method that:
- Uses JS interop to fetch assemblies via a network call.
- Loads assemblies into the runtime executing on WebAssembly in the browser.
[!NOTE] The framework's lazy loading implementation supports prerendering on the server. During prerendering, all assemblies, including those marked for lazy loading, are assumed to be loaded.
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:
- Add an
@using
directive for the xref:Microsoft.AspNetCore.Components.Routing?displayProperty=fullName namespace. - Add a
<Navigating>
tag to the component with markup to display during page transition events.
...
@using Microsoft.AspNetCore.Components.Routing
...
<Router ...>
<Navigating>
<div style="...">
<p>Loading the requested page…</p>
</div>
</Navigating>
</Router>
...
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 this cancellation token is set to avoid continuing to run the OnNavigateAsync
callback on a outdated navigation.
If a user navigates to Route A and then immediately to Route B, the app shouldn't continue running the OnNavigateAsync
callback for Route A:
@inject HttpClient Http
@inject ProductCatalog Products
<Router AppAssembly="@typeof(Program).Assembly"
OnNavigateAsync="@OnNavigateAsync">
...
</Router>
@code {
private async Task OnNavigateAsync(NavigationContext context)
{
if (context.Path == "/about")
{
var stats = new Stats = { Page = "/about" };
await Http.PostAsJsonAsync("api/visited", stats, context.CancellationToken);
}
else if (context.Path == "/store")
{
var productIds = [345, 789, 135, 689];
foreach (var productId in productIds)
{
context.CancellationToken.ThrowIfCancellationRequested();
Products.Prefetch(productId);
}
}
}
}
[!NOTE] Not throwing if the cancellation token in
NavigationContext
is canceled can result in unintended behavior, such as rendering a component from a previous navigation.
Complete example
The following complete Router
component demonstrates loading the GrantImaharaRobotControls.dll
assembly when the user navigates to /robot
. During page transitions, a styled message is displayed to the user.
@using System.Reflection
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.WebAssembly.Services
@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…</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.EndsWith("/robot"))
{
var assemblies = await assemblyLoader.LoadAssembliesAsync(
new List<string>() { "GrantImaharaRobotControls.dll" });
lazyLoadedAssemblies.AddRange(assemblies);
}
}
catch (Exception ex)
{
...
}
}
}
Troubleshoot
- If unexpected rendering occurs (for example, a component from a previous navigation is rendered), confirm that the code throws if the cancellation token is set.
- If assemblies are still loaded at application start, check that the assembly is marked as lazy loaded in the project file.