Skip to main content

Razor Class Libraries (RCL) and lazy loading

A Razor Class Library (RCL) can provide routable pages ([Route]) to the client application. Lagoon uses its own router: pages from an RCL are not found until its assembly is registered with the router. Two modes are available:

ModeAssembly downloadRegistration
At startupwith the rest of the applicationGetRouterAdditionalAssemblies()
Lazy loadedon first navigation to one of its pagesapp.LazyAssemblies (OnConfigureBehavior) or OnNavigateAsync

Load an RCL at startup​

The Client project references the RCL; the GetRouterAdditionalAssemblies method in Main.cs adds its assembly to the router.

Client/Main.cs
protected override IEnumerable<Assembly> GetRouterAdditionalAssemblies()
{
yield return typeof(MyCompany.Orders.Pages.OrderList).Assembly;
}

Load an RCL with lazy loading​

Lazy loading reduces the amount downloaded at startup: the RCL's assembly is only fetched on first navigation to one of its routes and then kept in memory.

1. Mark the assembly as lazy-loaded​

The Client project references the RCL and declares it with the BlazorWebAssemblyLazyLoad element:

Client/Client.csproj
<ItemGroup>
<ProjectReference Include="..\Orders\MyCompany.Orders.csproj" />
</ItemGroup>

<ItemGroup>
<BlazorWebAssemblyLazyLoad Include="MyCompany.Orders.wasm" />
</ItemGroup>
.wasm extension

Since .NET 8, assemblies are published in Webcil format: the name to declare and pass to Lagoon ends with .wasm (not .dll). Only a project that disables Webcil (<WasmEnableWebcil>false</WasmEnableWebcil>) uses the .dll extension.

2. Associate a route prefix with the assembly​

In OnConfigureBehavior, app.LazyAssemblies.Add associates a route prefix with the assemblies to load before displaying a page with that prefix:

Client/Main.cs
protected override void OnConfigureBehavior(ApplicationBehavior app)
{
// All "orders" and "orders/..." routes are in MyCompany.Orders
app.LazyAssemblies.Add("orders", "MyCompany.Orders.wasm");
}

Route prefix matching rules:

  • Comparison is performed on whole segments: orders matches orders and orders/list, but not orders2;
  • It is case-insensitive;
  • The prefix contains no route parameters ({id}): only the first literal segments matter;
  • A prefix can request multiple assemblies: app.LazyAssemblies.Add("orders", "MyCompany.Orders.wasm", "MyCompany.Common.wasm").

This mode is sufficient for most cases. Lagoon handles downloading, deduplication (an assembly is downloaded only once, even if multiple navigations request it simultaneously), and route table refresh.

3. Advanced cases: OnNavigateAsync​

For logic that prefixes do not cover, override OnNavigateAsync in Main.cs. Lagoon calls it before each navigation, before searching for the target page, after already applying the LazyAssemblies rules. It is also called before opening the default tab of a tabbed application and by WindowManager.OpenModalAsync, with a token that is never canceled.

Quick processing

Each navigation waits for OnNavigateAsync to complete: keep the processing quick, without server calls.

Client/Main.cs
protected override Task OnNavigateAsync(LgNavigationContext context)
{
if (context.StartsWithRoute("reports") && !context.Path.EndsWith("/print"))
{
context.LoadAssemblies("MyCompany.Reports.wasm");
}
return Task.CompletedTask;
}

The LgNavigationContext context exposes:

MemberRole
PathTarget path relative to the application base address, without / at the beginning or end, without query string or fragment (e.g. orders/list).
CancellationTokenCanceled when another navigation replaces this one.
StartsWithRoute(string routePrefix)Indicates whether the path starts with the prefix (same rules as LazyAssemblies).
LoadAssemblies(params string[] assemblyFileNames)Requests download of assemblies declared with BlazorWebAssemblyLazyLoad.
AddAssemblies(params Assembly[] assemblies)Adds to the router assemblies already loaded by the application itself.
Navigation replaced

If the user navigates elsewhere during a download, the first navigation is abandoned: only the last one is displayed. The ongoing download continues and its result is used for the next navigation.

Preload an assembly​

LoadLazyAssembliesAsync downloads assemblies and adds their pages to the router without navigation. For example, it can preload a module in the background after the home page is displayed:

await App.LoadLazyAssembliesAsync(new[] { "MyCompany.Orders.wasm" });

Open a lazy-loaded page in a modal​

WindowManager.OpenModal is synchronous and does not download anything: the page must already be routable. Use WindowManager.OpenModalAsync, which first loads the assemblies for the route (rules from LazyAssemblies and OnNavigateAsync):

await App.WindowManager.OpenModalAsync("orders/detail/12", "Order 12");

Restored tabs​

In tab mode, a tab restored at startup (TabSavingMethod) or declared in the startup tabs can point to a lazy-loaded page. Its assembly is not downloaded at startup: it is downloaded on first tab activation.

Initial navigation​

When the application starts directly on a lazy-loaded page (direct link, page reload), the startup screen remains displayed during the assembly download.

Errors​

If an assembly cannot be downloaded (network, name missing from BlazorWebAssemblyLazyLoad elements), an error message prompts to reload the page and the "not found" page replaces the requested page. During initial navigation, the message is displayed as soon as the application is shown.

Reload required

The WebAssembly runtime remembers download failures: subsequent navigations also fail until the page is reloaded.

The assembly's routes are added to the application's:

  • A route identical to an existing route is ignored: the already-declared page remains displayed;
  • Ambiguous routes (for example orders/{id} in the RCL and orders/{name} in the application) cause the load to fail with an error message; other lazy-loaded assemblies remain usable.

An assembly that the application has already loaded itself is added to the router with AddAssemblies, not with LoadAssemblies.

Limitations​

  • No DI services in lazy-loaded RCL. The service container is built at startup: services used by lazy-loaded pages must be registered in an assembly loaded at startup (the Client project or a non-lazy shared library).
  • Simple RCL only. The lazy-loaded RCL must not be a Lagoon module (Lagoon.*): Lagoon modules that expose pages are loaded at startup. The Dico.xml placed at the root of an RCL referenced as a project (ProjectReference) is merged at build into the application's dictionary, whether the RCL is lazy-loaded or not; its icons and SCSS styles do not go through the Lagoon resource pipeline.
  • No type reference from the main assembly. A reference to an RCL type in the Client code (for example calling OrderList.Link() to build the menu) fails until the assembly is loaded: the runtime does not download a lazy-loaded assembly on its own. Build links from the route as text: LgPageLink.Create("orders/list", "#menuOrders", IconNames.All.Window).
  • This is not a security measure. A lazy-loaded assembly remains downloadable by any client: pages retain their [Authorize] attributes and permissions remain server-side controlled.

See also​