Delen via


Best practices voor prestaties van ASP.NET Core Blazor JavaScript-interoperabiliteit (JS interop)

Opmerking

Dit is niet de nieuwste versie van dit artikel. Zie de .NET 9-versie van dit artikel voor de huidige release.

Waarschuwing

Deze versie van ASP.NET Core wordt niet meer ondersteund. Zie het .NET- en .NET Core-ondersteuningsbeleid voor meer informatie. Zie de .NET 9-versie van dit artikel voor de huidige release.

Belangrijk

Deze informatie heeft betrekking op een pre-releaseproduct dat aanzienlijk kan worden gewijzigd voordat het commercieel wordt uitgebracht. Microsoft geeft geen garanties, uitdrukkelijk of impliciet, met betrekking tot de informatie die hier wordt verstrekt.

Zie de .NET 9-versie van dit artikel voor de huidige release.

Voor aanroepen tussen .NET en JavaScript is extra overhead vereist, omdat:

  • Aanroepen zijn asynchroon.
  • Parameters en retourwaarden zijn JSON-geëncodeerd om een gemakkelijk te begrijpen conversiemechanisme te bieden tussen .NET en JavaScript types.

Daarnaast worden deze aanroepen doorgegeven via het netwerk voor Blazor-apps aan de serverzijde.

Vermijd overmatig fijnmazige aanroepen

Aangezien elke oproep enige overhead omvat, kan het waardevol zijn om het aantal oproepen te verminderen. Houd rekening met de volgende code, waarin een verzameling items in de localStoragevan de browser wordt opgeslagen:

private async Task StoreAllInLocalStorage(IEnumerable<TodoItem> items)
{
    foreach (var item in items)
    {
        await JS.InvokeVoidAsync("localStorage.setItem", item.Id, 
            JsonSerializer.Serialize(item));
    }
}

In het voorgaande voorbeeld wordt voor elk item een afzonderlijke JS interop-aanroep gedaan. In plaats daarvan vermindert de volgende benadering de JS interoperabiliteit tot één aanroep:

private async Task StoreAllInLocalStorage(IEnumerable<TodoItem> items)
{
    await JS.InvokeVoidAsync("storeAllInLocalStorage", items);
}

De bijbehorende JavaScript-functie slaat de hele verzameling items op de client op:

function storeAllInLocalStorage(items) {
  items.forEach(item => {
    localStorage.setItem(item.id, JSON.stringify(item));
  });
}

Voor Blazor WebAssembly apps verbetert het rollen van afzonderlijke JS interop-aanroepen in één aanroep meestal alleen de prestaties aanzienlijk als het onderdeel een groot aantal JS interop-aanroepen doet.

Overweeg het gebruik van synchrone aanroepen

JavaScript aanroepen vanuit .NET

Deze sectie is alleen van toepassing op onderdelen aan de clientzijde.

JS interop-aanroepen zijn altijd asynchroon, ongeacht of de aangeroepen code synchroon of asynchroon is. Aanroepen zijn asynchroon om ervoor te zorgen dat onderdelen compatibel zijn met de rendermodi aan de serverzijde en clientzijde. Op de server moeten alle JS interop-aanroepen asynchroon zijn omdat ze via een netwerkverbinding worden verzonden.

Als u zeker weet dat uw onderdeel alleen wordt uitgevoerd op WebAssembly, kunt u ervoor kiezen om synchrone JS interop-aanroepen uit te voeren. Dit heeft iets minder overhead dan het maken van asynchrone aanroepen en kan leiden tot minder rendercycli, omdat er geen tussenliggende status is tijdens het wachten op resultaten.

Als u een synchrone aanroep wilt maken van .NET naar JavaScript in een onderdeel aan de clientzijde, cast IJSRuntime naar IJSInProcessRuntime om de JS interop-aanroep te maken:

@inject IJSRuntime JS

...

@code {
    protected override void HandleSomeEvent()
    {
        var jsInProcess = (IJSInProcessRuntime)JS;
        var value = jsInProcess.Invoke<string>("javascriptFunctionIdentifier");
    }
}

Wanneer u werkt met IJSObjectReference onderdelen aan de clientzijde van .NET 5 of hoger, kunt u in plaats daarvan synchroon gebruiken IJSInProcessObjectReference . IJSInProcessObjectReference implementeert IAsyncDisposable/IDisposable en moet worden vrijgegeven voor garbage collection om een geheugenlek te voorkomen, zoals in het volgende voorbeeld wordt gedemonstreerd:

@inject IJSRuntime JS
@implements IDisposable

...

@code {
    ...
    private IJSInProcessObjectReference? module;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            var jsInProcess = (IJSInProcessRuntime)JS;
            module = await jsInProcess.Invoke<IJSInProcessObjectReference>("import", 
                "./scripts.js");
            var value = module.Invoke<string>("javascriptFunctionIdentifier");
        }
    }

    ...

    void IDisposable.Dispose()
    {
        if (module is not null)
        {
            await module.Dispose();
        }
    }
}

In het voorgaande voorbeeld loopt een JSDisconnectedException niet vast tijdens het verwijderen van de module omdat er in een Blazor-app geen SignalR-Blazor WebAssembly circuit is dat kan verloren gaan. Zie ASP.NET Core Blazor JavaScript-interoperabiliteit (JS interop)voor meer informatie.

.NET aanroepen vanuit JavaScript

Deze sectie is alleen van toepassing op onderdelen aan de clientzijde.

JS interop-aanroepen zijn altijd asynchroon, ongeacht of de aangeroepen code synchroon of asynchroon is. Aanroepen zijn asynchroon om ervoor te zorgen dat onderdelen compatibel zijn met de rendermodi aan de serverzijde en clientzijde. Op de server moeten alle JS interop-aanroepen asynchroon zijn omdat ze via een netwerkverbinding worden verzonden.

Als u zeker weet dat uw onderdeel alleen wordt uitgevoerd op WebAssembly, kunt u ervoor kiezen om synchrone JS interop-aanroepen uit te voeren. Dit heeft iets minder overhead dan het maken van asynchrone aanroepen en kan leiden tot minder rendercycli, omdat er geen tussenliggende status is tijdens het wachten op resultaten.

Gebruik DotNet.invokeMethod in plaats van DotNet.invokeMethodAsyncom een synchrone aanroep van JavaScript naar .NET te maken in een onderdeel aan de clientzijde.

Synchrone aanroepen werken als:

  • Het onderdeel wordt alleen weergegeven voor uitvoering op WebAssembly.
  • De aangeroepen functie retourneert een waarde synchroon. De functie is geen async methode en retourneert geen .NET-Task of JavaScript-Promise.

Deze sectie is alleen van toepassing op onderdelen aan de clientzijde.

JS interop-aanroepen zijn altijd asynchroon, ongeacht of de aangeroepen code synchroon of asynchroon is. Aanroepen zijn asynchroon om ervoor te zorgen dat onderdelen compatibel zijn met de rendermodi aan de serverzijde en clientzijde. Op de server moeten alle JS interop-aanroepen asynchroon zijn omdat ze via een netwerkverbinding worden verzonden.

Als u zeker weet dat uw onderdeel alleen wordt uitgevoerd op WebAssembly, kunt u ervoor kiezen om synchrone JS interop-aanroepen uit te voeren. Dit heeft iets minder overhead dan het maken van asynchrone aanroepen en kan leiden tot minder rendercycli, omdat er geen tussenliggende status is tijdens het wachten op resultaten.

Als u een synchrone aanroep wilt maken van .NET naar JavaScript in een onderdeel aan de clientzijde, cast IJSRuntime naar IJSInProcessRuntime om de JS interop-aanroep te maken:

@inject IJSRuntime JS

...

@code {
    protected override void HandleSomeEvent()
    {
        var jsInProcess = (IJSInProcessRuntime)JS;
        var value = jsInProcess.Invoke<string>("javascriptFunctionIdentifier");
    }
}

Wanneer u werkt met IJSObjectReference onderdelen aan de clientzijde van .NET 5 of hoger, kunt u in plaats daarvan synchroon gebruiken IJSInProcessObjectReference . IJSInProcessObjectReference implementeert IAsyncDisposable/IDisposable en moet worden vrijgegeven voor garbage collection om een geheugenlek te voorkomen, zoals in het volgende voorbeeld wordt gedemonstreerd:

@inject IJSRuntime JS
@implements IDisposable

...

@code {
    ...
    private IJSInProcessObjectReference? module;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            var jsInProcess = (IJSInProcessRuntime)JS;
            module = await jsInProcess.Invoke<IJSInProcessObjectReference>("import", 
                "./scripts.js");
            var value = module.Invoke<string>("javascriptFunctionIdentifier");
        }
    }

    ...

    void IDisposable.Dispose()
    {
        if (module is not null)
        {
            await module.Dispose();
        }
    }
}

In het voorgaande voorbeeld loopt een JSDisconnectedException niet vast tijdens het verwijderen van de module omdat er in een Blazor-app geen SignalR-Blazor WebAssembly circuit is dat kan verloren gaan. Zie ASP.NET Core Blazor JavaScript-interoperabiliteit (JS interop)voor meer informatie.

Overweeg het gebruik van niet-gemarshallede aanroepen

Deze sectie is alleen van toepassing op Blazor WebAssembly apps.

Wanneer u op Blazor WebAssemblyuitvoert, is het mogelijk om niet-gemarshalleerde aanroepen te maken van .NET naar JavaScript. Dit zijn synchrone aanroepen die geen JSON-serialisatie van argumenten of retourwaarden uitvoeren. Alle aspecten van geheugenbeheer en vertalingen tussen .NET- en JavaScript-weergaven worden aan de ontwikkelaar overgelaten.

Waarschuwing

Hoewel het gebruik van IJSUnmarshalledRuntime de minste overhead betekent vergeleken met de JS interop-benaderingen, zijn de JavaScript-API's die nodig zijn voor interactie met deze API's momenteel niet gedocumenteerd en kunnen ze onderhevig zijn aan ingrijpende wijzigingen in toekomstige releases.

function jsInteropCall() {
  return BINDING.js_to_mono_obj("Hello world");
}
@inject IJSRuntime JS

@code {
    protected override void OnInitialized()
    {
        var unmarshalledJs = (IJSUnmarshalledRuntime)JS;
        var value = unmarshalledJs.InvokeUnmarshalled<string>("jsInteropCall");
    }
}

JavaScript-[JSImport]/[JSExport]-interop gebruiken

JavaScript [JSImport]/[JSExport] interop voor Blazor WebAssembly-apps biedt verbeterde prestaties en stabiliteit ten opzichte van de JS interop-API in frameworkreleases vóór ASP.NET Core in .NET 7.

Zie JavaScript JSImport/JSExport-interop met ASP.NET Core Blazorvoor meer informatie.