Assembly di caricamento differita in ASP.NET Core Blazor WebAssembly

Nota

Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Importante

Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.

Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Blazor WebAssembly Le prestazioni di avvio dell'app possono essere migliorate attendendo di caricare gli assembly dell'app creati dallo sviluppatore fino a quando non sono necessari gli assembly, denominato caricamento differita.

Le sezioni iniziali di questo articolo illustrano la configurazione dell'app. Per una dimostrazione funzionante, vedere la sezione Complete example (Esempio completo) alla fine di questo articolo.

Questo articolo si applica solo alle Blazor WebAssembly app. Il caricamento differita degli assembly non offre vantaggi per le app lato server perché le app sottoposte a rendering del server non scaricano assembly nel client.

Il caricamento differita non deve essere usato per gli assembly di runtime di base, che potrebbero essere eliminati durante la pubblicazione e non disponibili nel client quando l'app viene caricata.

Segnaposto dell'estensione file ({FILE EXTENSION}) per i file di assembly

I file di assembly usano il formato di creazione di pacchetti Webcil per gli assembly .NET con un'estensione .wasm di file.

In tutto l'articolo, il {FILE EXTENSION} segnaposto rappresenta "wasm".

I file di assembly sono basati sulle librerie a collegamento dinamico (DLL) con un'estensione .dll di file.

In tutto l'articolo, il {FILE EXTENSION} segnaposto rappresenta "dll".

Configurazione del file di progetto

Contrassegnare gli assembly per il caricamento differita nel file di progetto dell'app (.csproj) usando l'elemento BlazorWebAssemblyLazyLoad . Usare il nome dell'assembly con estensione di file. Il Blazor framework impedisce il caricamento dell'assembly all'avvio dell'app.

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

Il {ASSEMBLY NAME} segnaposto è il nome dell'assembly e il {FILE EXTENSION} segnaposto è l'estensione di file. L'estensione del file è obbligatoria.

Includere un BlazorWebAssemblyLazyLoad elemento per ogni assembly. Se un assembly ha dipendenze, includere una BlazorWebAssemblyLazyLoad voce per ogni dipendenza.

Router configurazione dei componenti

Il Blazor framework registra automaticamente un servizio singleton per il caricamento differita di assembly nelle app sul lato Blazor WebAssembly client, LazyAssemblyLoader. Il metodo LazyAssemblyLoader.LoadAssembliesAsync:

  • Usa JS l'interoperabilità per recuperare gli assembly tramite una chiamata di rete.
  • Carica gli assembly nel runtime in esecuzione in WebAssembly nel browser.

Nota

Le linee guida per le soluzioni ospitateBlazor WebAssemblysono illustrate nella sezione Assembly di caricamento differita in una soluzione ospitata.Blazor WebAssembly

BlazorIl componente di Router designa gli assembly che Blazor cercano componenti instradabili ed è anche responsabile del rendering del componente per la route in cui si sposta l'utente. Il Router metodo del OnNavigateAsync componente viene usato insieme al caricamento differita per caricare gli assembly corretti per gli endpoint che un utente richiede.

La logica viene implementata all'interno OnNavigateAsync per determinare gli assembly da caricare con LazyAssemblyLoader. Le opzioni per la struttura della logica includono:

  • Controlli condizionali all'interno del OnNavigateAsync metodo .
  • Tabella di ricerca che esegue il mapping delle route ai nomi di assembly, inseriti nel componente o implementati all'interno del @code blocco.

Nell'esempio seguente :

  • Lo spazio dei nomi per Microsoft.AspNetCore.Components.WebAssembly.Services viene specificato.
  • Il LazyAssemblyLoader servizio viene inserito (AssemblyLoader).
  • Il {PATH} segnaposto è il percorso in cui deve essere caricato l'elenco di assembly. Nell'esempio viene usato un controllo condizionale per un singolo percorso che carica un singolo set di assembly.
  • Il {LIST OF ASSEMBLIES} segnaposto è l'elenco delimitato da virgole di stringhe del nome file di assembly, incluse le estensioni di file , ad esempio "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);
           }
    }
}

Nota

L'esempio precedente non mostra il contenuto del Router markup del Razor componente (...). Per una dimostrazione con codice completo, vedere la sezione Esempio completo di questo articolo.

Nota

Con la versione di ASP.NET Core 5.0.1 e per eventuali versioni 5.x aggiuntive, il componente Router include il parametro PreferExactMatches impostato su @true. Per altre informazioni, vedere Eseguire la migrazione da ASP.NET Core 3.1 a 5.0.

Assembly che includono componenti instradabili

Quando l'elenco di assembly include componenti instradabili, l'elenco di assembly per un determinato percorso viene passato alla Router raccolta del AdditionalAssemblies componente.

Nell'esempio seguente :

  • L'elenco Assembly<>in lazyLoadedAssemblies passa l'elenco di assembly a .AdditionalAssemblies Il framework cerca le route negli assembly e aggiorna la raccolta di route se vengono trovate nuove route. Per accedere al Assembly tipo, lo spazio dei nomi per System.Reflection viene incluso nella parte superiore del App.razor file.
  • Il {PATH} segnaposto è il percorso in cui deve essere caricato l'elenco di assembly. Nell'esempio viene usato un controllo condizionale per un singolo percorso che carica un singolo set di assembly.
  • Il {LIST OF ASSEMBLIES} segnaposto è l'elenco delimitato da virgole di stringhe del nome file di assembly, incluse le estensioni di file , ad esempio "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);
           }
    }
}

Nota

L'esempio precedente non mostra il contenuto del Router markup del Razor componente (...). Per una dimostrazione con codice completo, vedere la sezione Esempio completo di questo articolo.

Nota

Con la versione di ASP.NET Core 5.0.1 e per eventuali versioni 5.x aggiuntive, il componente Router include il parametro PreferExactMatches impostato su @true. Per altre informazioni, vedere Eseguire la migrazione da ASP.NET Core 3.1 a 5.0.

Per altre informazioni, vedere ASP.NET routing e navigazione coreBlazor.

Interazione dell'utente con <Navigating> il contenuto

Durante il caricamento degli assembly, che possono richiedere alcuni secondi, il Router componente può indicare all'utente che si sta verificando una transizione di pagina con la proprietà del Navigating router.

Per altre informazioni, vedere ASP.NET routing e navigazione coreBlazor.

Gestire gli annullamenti in OnNavigateAsync

L'oggetto NavigationContext passato al OnNavigateAsync callback contiene un CancellationToken oggetto impostato quando si verifica un nuovo evento di navigazione. Il OnNavigateAsync callback deve generare quando il token di annullamento è impostato per evitare di continuare a eseguire il OnNavigateAsync callback in una struttura di spostamento obsoleta.

Per altre informazioni, vedere ASP.NET routing e navigazione coreBlazor.

OnNavigateAsync eventi e file di assembly rinominati

Il caricatore di risorse si basa sui nomi di assembly definiti nel blazor.boot.json file. Se gli assembly vengono rinominati, i nomi degli assembly usati in un OnNavigateAsync callback e i nomi degli assembly nel blazor.boot.json file non sono sincronizzati.

Per rettificare quanto segue:

  • Controllare se l'app è in esecuzione nell'ambiente Production quando si determinano i nomi degli assembly da usare.
  • Archiviare i nomi di assembly rinominati in un file separato e leggere da tale file per determinare il nome dell'assembly da usare con il servizio e OnNavigateAsync il LazyAssemblyLoader callback.

Caricamento differita di assembly in una soluzione ospitata Blazor WebAssembly

L'implementazione del caricamento differita del framework supporta il caricamento differita con prerendering in una soluzione ospitata.Blazor WebAssembly Durante la pre-esecuzione, si presuppone che vengano caricati tutti gli assembly, inclusi quelli contrassegnati per il caricamento differita. Registrare manualmente il LazyAssemblyLoader servizio nel Server progetto.

Nella parte superiore del Program.cs file del Server progetto aggiungere lo spazio dei nomi per Microsoft.AspNetCore.Components.WebAssembly.Services:

using Microsoft.AspNetCore.Components.WebAssembly.Services;

Server Nel Program.cs progetto registrare il servizio:

builder.Services.AddScoped<LazyAssemblyLoader>();

Nella parte superiore del Startup.cs file del Server progetto aggiungere lo spazio dei nomi per Microsoft.AspNetCore.Components.WebAssembly.Services:

using Microsoft.AspNetCore.Components.WebAssembly.Services;

In Startup.ConfigureServices (Startup.cs) del Server progetto registrare il servizio:

services.AddScoped<LazyAssemblyLoader>();

Esempio completo

La dimostrazione in questa sezione:

  • Crea un assembly di controlli robot (GrantImaharaRobotControls.{FILE EXTENSION}) come Razor libreria di classi (RCL) che include un Robot componente (Robot.razor con un modello di route di /robot).
  • Lazily carica l'assembly rcl per eseguire il rendering del relativo Robot componente quando l'URL /robot viene richiesto dall'utente.

Creare un'app autonoma Blazor WebAssembly per illustrare il caricamento differita dell'assembly di una Razor libreria di classi. Assegnare al progetto il nome LazyLoadTest.

Aggiungere un progetto di libreria di classi core ASP.NET alla soluzione:

  • Visual Studio: fare clic con il pulsante destro del mouse sul file della soluzione in Esplora soluzioni e scegliere Aggiungi>nuovo progetto. Nella finestra di dialogo dei nuovi tipi di progetto selezionare Razor Libreria di classi. Assegnare al progetto il nome GrantImaharaRobotControls. Non selezionare la casella di controllo Pagine di supporto e visualizzazione.
  • Interfaccia della riga di comando di Visual Studio Code/.NET: eseguire dotnet new razorclasslib -o GrantImaharaRobotControls da un prompt dei comandi. L'opzione -o|--output crea una cartella e assegna un nome al progetto GrantImaharaRobotControls.

Il componente di esempio presentato più avanti in questa sezione usa un Blazor modulo. Nel progetto RCL aggiungere il Microsoft.AspNetCore.Components.Forms pacchetto al progetto.

Nota

Per indicazioni sull'aggiunta di pacchetti alle app .NET, vedere gli articoli sotto Installare e gestire pacchetti in Flusso di lavoro dell'utilizzo di pacchetti (documentazione di NuGet). Confermare le versioni corrette del pacchetto all'indirizzo NuGet.org.

Creare una HandGesture classe nell'RCL con un ThumbUp metodo che ipoteticamente rende un robot eseguire un movimento di identificazione personale. Il metodo accetta un argomento per l'asse o LeftRight, come enum. Il metodo restituisce true l'esito positivo.

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

Aggiungere il componente seguente alla radice del progetto RCL. Il componente consente all'utente di inviare una richiesta di movimento thumb-up sinistro o destro.

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 Nel progetto creare un riferimento al progetto per l'RCLGrantImaharaRobotControls:

  • Visual Studio: fare clic con il pulsante destro del mouse sul LazyLoadTest progetto e selezionare Aggiungi>riferimento al progetto per aggiungere un riferimento al progetto per l'RCL.GrantImaharaRobotControls
  • Interfaccia della riga di comando di Visual Studio Code/.NET: eseguire dotnet add reference {PATH} in una shell dei comandi dalla cartella del progetto. Il {PATH} segnaposto è il percorso del progetto RCL.

Specificare l'assembly RCL per il LazyLoadTest caricamento differita nel file di progetto dell'app (.csproj):

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

Il componente seguente Router illustra il caricamento dell'assembly GrantImaharaRobotControls.{FILE EXTENSION} quando l'utente passa a /robot. Sostituire il componente predefinito App dell'app con il componente seguente App .

Durante le transizioni di pagina, viene visualizzato un messaggio con stile all'utente con l'elemento <Navigating> . Per altre informazioni, vedere la sezione Interazione dell'utente con <Navigating> il contenuto .

L'assembly viene assegnato a AdditionalAssemblies, che determina la ricerca nell'assembly di componenti instradabili, in cui trova il Robot componente. La Robot route del componente viene aggiunta alla raccolta di route dell'app. Per altre informazioni, vedere l'articolo ASP.NET Blazor core routing e navigazione e la sezione Assembly che includono componenti instradabili di questo articolo.

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

Compilare ed eseguire l'app.

Quando il Robot componente dell'RCL viene richiesto in /robot, l'assembly GrantImaharaRobotControls.{FILE EXTENSION} viene caricato e viene eseguito il rendering del Robot componente. È possibile esaminare il caricamento dell'assembly nella scheda Rete degli strumenti di sviluppo del browser.

Risoluzione dei problemi

  • Se si verifica un rendering imprevisto, ad esempio il rendering di un componente da una struttura di spostamento precedente, verificare che il codice generi se il token di annullamento è impostato.
  • Se gli assembly configurati per il caricamento differita si caricano in modo imprevisto all'avvio dell'app, verificare che l'assembly sia contrassegnato per il caricamento differita nel file di progetto.

Nota

Esiste un problema noto per il caricamento di tipi da un assembly con caricamento differimento. Per ulteriori informazioni, vedere Blazor WebAssembly lazy loading assemblies not working when using @ref attribute in the component (dotnet/aspnetcore #29342).

Risorse aggiuntive