Sdílet prostřednictvím


Opožděné načtení sestavení v ASP.NET Core Blazor WebAssembly

Poznámka:

Toto není nejnovější verze tohoto článku. Aktuální verzi najdete ve verzi .NET 8 tohoto článku.

Důležité

Tyto informace se týkají předběžného vydání produktu, který může být podstatně změněn před komerčním vydáním. Microsoft neposkytuje žádné záruky, výslovné ani předpokládané, týkající se zde uváděných informací.

Aktuální verzi najdete ve verzi .NET 8 tohoto článku.

Blazor WebAssembly Výkon spouštění aplikace je možné zlepšit čekáním na načtení sestavení aplikací vytvořených vývojářem, dokud se sestavení nevyžadují, což se nazývá opožděné načítání.

Počáteční části tohoto článku se týkají konfigurace aplikace. Funkční ukázku najdete v části Kompletní příklad na konci tohoto článku.

Tento článek se týká Blazor WebAssembly jenom aplikací. Opožděné načítání sestavení nepodporuje aplikace na straně serveru, protože aplikace vykreslené serverem nestahují sestavení do klienta.

Opožděné načítání by se nemělo používat pro sestavení modulu runtime jádra, která se můžou při načítání aplikace oříznout při publikování a nedostupnosti v klientovi.

Zástupný symbol přípony souboru ({FILE EXTENSION}) pro soubory sestavení

Soubory sestavení používají formát balení webcilu pro sestavení .NET s příponou .wasm souboru.

V celém článku {FILE EXTENSION} představuje zástupný symbol "wasm".

Soubory sestavení jsou založené na knihovnách DYNAMIC-Link (DLL) s příponou .dll souboru.

V celém článku {FILE EXTENSION} představuje zástupný symbol "dll".

Konfigurace souboru projektu

Označte sestavení pro opožděné načítání v souboru projektu aplikace (.csproj) pomocí BlazorWebAssemblyLazyLoad položky. Použijte název sestavení s příponou souboru. Architektura Blazor brání sestavení při spuštění aplikace.

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

Zástupný {ASSEMBLY NAME} symbol je název sestavení a {FILE EXTENSION} zástupný symbol je přípona souboru. Přípona souboru je povinná.

Zahrňte jednu BlazorWebAssemblyLazyLoad položku pro každé sestavení. Pokud sestavení obsahuje závislosti, zahrňte BlazorWebAssemblyLazyLoad položku pro každou závislost.

Router konfigurace komponent

Architektura Blazor automaticky zaregistruje jednu službu pro opožděné načítání sestavení v aplikacích na straně Blazor WebAssembly klienta . LazyAssemblyLoader Metoda LazyAssemblyLoader.LoadAssembliesAsync:

  • Pomocí JS zprostředkovatele komunikace načítá sestavení prostřednictvím síťového volání.
  • Načte sestavení do modulu runtime, který se spouští na WebAssembly v prohlížeči.

Poznámka:

Pokyny k hostovaným řešením najdete v části Hostované Blazor WebAssemblyBlazor WebAssemblyřešení v sestaveních Opožděné načítání.

BlazorRouter Komponenta určuje sestavení, která Blazor vyhledá směrovatelné komponenty, a je také zodpovědná za vykreslení komponenty pro trasu, ve které uživatel prochází. Metoda Router komponenty OnNavigateAsync se používá ve spojení s opožděným načítáním k načtení správných sestavení pro koncové body, které uživatel požaduje.

Logika je implementována uvnitř OnNavigateAsync k určení sestavení, která se mají načíst s LazyAssemblyLoader. Mezi možnosti strukturování logiky patří:

  • Podmíněné kontroly uvnitř OnNavigateAsync metody.
  • Vyhledávací tabulka, která mapuje trasy na názvy sestavení, buď vložené do komponenty, nebo implementované v @code rámci bloku.

V následujícím příkladu:

  • Je určen obor názvů pro Microsoft.AspNetCore.Components.WebAssembly.Services .
  • Služba LazyAssemblyLoader se vloží (AssemblyLoader).
  • Zástupný {PATH} symbol je cesta, kde se má načíst seznam sestavení. Příklad používá podmíněnou kontrolu jedné cesty, která načte jednu sadu sestavení.
  • Zástupný {LIST OF ASSEMBLIES} symbol je čárkami oddělený seznam řetězců názvu souboru sestavení, včetně jejich přípon (například "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);
           }
    }
}

Poznámka:

Předchozí příklad nezobrazuje obsah značky Router komponenty Razor (...). Ukázku s úplným kódem najdete v části Kompletní příklad tohoto článku.

Poznámka:

S vydáním ASP.NET Core 5.0.1 a pro všechny další verze 5.x komponenta Router zahrnuje parametr PreferExactMatches nastavený na @true. Další informace najdete v tématu Migrace z ASP.NET Core 3.1 na verzi 5.0.

Sestavení, která obsahují směrovatelné komponenty

Pokud seznam sestavení obsahuje směrovatelné součásti, předá se seznam sestavení pro danou cestu kolekci Router součásti AdditionalAssemblies .

V následujícím příkladu:

  • Seznam Assembly<>předá lazyLoadedAssemblies seznam sestavení do .AdditionalAssemblies Architektura vyhledá trasy a aktualizuje kolekci tras, pokud jsou nalezeny nové trasy. Pro přístup k Assembly typu je obor názvů System.Reflection zahrnutý v horní části App.razor souboru.
  • Zástupný {PATH} symbol je cesta, kde se má načíst seznam sestavení. Příklad používá podmíněnou kontrolu jedné cesty, která načte jednu sadu sestavení.
  • Zástupný {LIST OF ASSEMBLIES} symbol je čárkami oddělený seznam řetězců názvu souboru sestavení, včetně jejich přípon (například "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);
           }
    }
}

Poznámka:

Předchozí příklad nezobrazuje obsah značky Router komponenty Razor (...). Ukázku s úplným kódem najdete v části Kompletní příklad tohoto článku.

Poznámka:

S vydáním ASP.NET Core 5.0.1 a pro všechny další verze 5.x komponenta Router zahrnuje parametr PreferExactMatches nastavený na @true. Další informace najdete v tématu Migrace z ASP.NET Core 3.1 na verzi 5.0.

Další informace najdete v tématu ASP.NET Blazor Základní směrování a navigace.

Interakce uživatelů s obsahem <Navigating>

Při načítání sestavení, která mohou trvat několik sekund, Router může komponenta označit uživateli, že přechod stránky probíhá s vlastností směrovače Navigating .

Další informace najdete v tématu ASP.NET Blazor Základní směrování a navigace.

Zpracování zrušení v OnNavigateAsync

Objekt NavigationContext předaný zpětnému OnNavigateAsync volání obsahuje nastavenou CancellationToken , když dojde k nové události navigace. OnNavigateAsync Zpětné volání musí vyvolat, když je token zrušení nastavený, aby se zabránilo pokračování ve spouštění zpětného OnNavigateAsync volání v zastaralé navigaci.

Další informace najdete v tématu ASP.NET Blazor Základní směrování a navigace.

OnNavigateAsync události a přejmenované soubory sestavení

Zavaděč prostředků spoléhá na názvy sestavení definované v blazor.boot.json souboru. Pokud jsou sestavení přejmenována, názvy sestavení použité v zpětném OnNavigateAsync volání a názvy sestavení v blazor.boot.json souboru nejsou synchronizované.

Chcete-li tuto opravu napravit:

  • Zkontrolujte, jestli aplikace běží v Production prostředí při určování názvů sestavení, které se mají použít.
  • Uložte přejmenované názvy sestavení do samostatného souboru a načtěte z daného souboru, abyste zjistili, jaký název sestavení se má použít se službou LazyAssemblyLoader a OnNavigateAsync zpětným voláním.

Opožděné načtení sestavení v hostovaném Blazor WebAssembly řešení

Implementace opožděného načítání architektury podporuje opožděné načítání s předrenderingem v hostovaném Blazor WebAssemblyřešení. Během předřalení se předpokládá, že se načtou všechna sestavení, včetně těch, která jsou označená pro opožděné načítání. Ručně zaregistrujte LazyAssemblyLoader službu v Server projektu.

V horní části Program.cs souboru Server projektu přidejte obor názvů pro Microsoft.AspNetCore.Components.WebAssembly.Services:

using Microsoft.AspNetCore.Components.WebAssembly.Services;

Server V Program.cs projektu zaregistrujte službu:

builder.Services.AddScoped<LazyAssemblyLoader>();

V horní části Startup.cs souboru Server projektu přidejte obor názvů pro Microsoft.AspNetCore.Components.WebAssembly.Services:

using Microsoft.AspNetCore.Components.WebAssembly.Services;

V Startup.ConfigureServices (Startup.cs) Server projektu zaregistrujte službu:

services.AddScoped<LazyAssemblyLoader>();

Kompletní příklad

Ukázka v této části:

  • Vytvoří sestavení robota (GrantImaharaRobotControls.{FILE EXTENSION}) jako knihovnu Razor tříd (RCL), která obsahuje komponentu Robot (Robot.razor se šablonou /robottrasy ).
  • Lazily načte sestavení seznamu RCL, aby vykresloval jeho Robot komponentu, když /robot uživatel požaduje adresu URL.

Vytvořte samostatnou Blazor WebAssembly aplikaci, která demonstruje opožděné načítání Razor sestavení knihovny tříd. Pojmenujte projekt LazyLoadTest.

Do řešení přidejte projekt knihovny tříd ASP.NET Core:

  • Visual Studio: Klikněte pravým tlačítkem na soubor řešení v Průzkumník řešení a vyberte Přidat>nový projekt. V dialogovém okně nových typů projektů vyberte Razor knihovnu tříd. Pojmenujte projekt GrantImaharaRobotControls. Nezaškrtávejte políčko Stránky podpory a zobrazení.
  • Visual Studio Code/.NET CLI: Spusťte dotnet new razorclasslib -o GrantImaharaRobotControls ho z příkazového řádku. Tato -o|--output možnost vytvoří složku a pojmenuje projekt GrantImaharaRobotControls.

Ukázková komponenta uvedená dále v této části používá Blazor formulář. V projektu RCL přidejte Microsoft.AspNetCore.Components.Forms balíček do projektu.

Poznámka:

Pokyny k přidávání balíčků do aplikací .NET najdete v článcích v části Instalace a správa balíčků na webu Pracovní postup používání balíčků (dokumentace k NuGetu). Ověřte správné verze balíčků na NuGet.org.

Vytvořte HandGesture třídu v seznamu RCL metodou ThumbUp , která hypoteticky dělá robota gestem palec nahoru. Metoda přijímá argument pro osu nebo LeftRight, jako .enum Metoda se vrátí true k úspěchu.

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 }
}

Do kořenového adresáře projektu RCL přidejte následující komponentu. Tato komponenta uživateli umožňuje odeslat žádost o gesto pro palec doleva nebo doprava.

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; }
    }
}

LazyLoadTest V projektu vytvořte odkaz na projekt pro GrantImaharaRobotControls seznam RCL:

  • Visual Studio: Klikněte pravým tlačítkem myši na LazyLoadTest projekt a vyberte Přidat>odkaz na projekt a přidejte odkaz na projekt pro GrantImaharaRobotControls seznam RCL.
  • Visual Studio Code/.NET CLI: Spusťte dotnet add reference {PATH} v příkazovém prostředí ze složky projektu. Zástupný {PATH} symbol je cesta k projektu RCL.

Zadejte sestavení seznamu RCL pro opožděné načítání v LazyLoadTest souboru projektu aplikace (.csproj):

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

Následující Router komponenta ukazuje načtení GrantImaharaRobotControls.{FILE EXTENSION} sestavení, když uživatel přejde na /robot. Nahraďte výchozí App komponentu aplikace následující App komponentou.

Během přechodů stránky se uživateli zobrazí stylovaná zpráva s prvkem <Navigating> . Další informace najdete v části Interakce uživatele s obsahem<Navigating>.

Sestavení je přiřazeno AdditionalAssemblies, což vede ke směrovači, který hledá směrovatelné komponenty, kde najde komponentu Robot . Trasa Robot komponenty se přidá do kolekce tras aplikace. Další informace najdete v článku o směrování a navigaci jádra ASP.NET Blazor a sestaveních, která obsahují směrovatelné součásti tohoto článku.

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);
        }
    }
}

Sestavte a spusťte aplikaci.

Pokud je komponenta Robot z seznamu RCL požadována, /robotGrantImaharaRobotControls.{FILE EXTENSION} sestavení se načte a komponenta Robot se vykreslí. Načtení sestavení můžete zkontrolovat na kartě Síť vývojářských nástrojů prohlížeče.

Odstraňování potíží

  • Pokud dojde k neočekávanému vykreslování, například vykreslení komponenty z předchozí navigace, ověřte, že kód vyvolá, pokud je nastaven token zrušení.
  • Pokud se sestavení nakonfigurovaná pro opožděné načítání neočekávaně načítá při spuštění aplikace, zkontrolujte, jestli je sestavení označené pro opožděné načítání v souboru projektu.

Poznámka:

Známý problém existuje pro načítání typů z lazily načteného sestavení. Další informace najdete na webu Blazor WebAssembly lazy loading assemblies not working when using @ref attribute in the component (dotnet/aspnetcore #29342).

Další materiály