Delen via


ASP.NET Core Blazor JavaScript-interoperabiliteit (JS interop)

Notitie

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

Waarschuwing

Deze versie van ASP.NET Core wordt niet meer ondersteund. Zie de .NET- en .NET Core-ondersteuningsbeleidvoor meer informatie. Zie de .NET 9-versie van dit artikelvoor 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 artikelvoor de huidige release.

Een Blazor-app kan JavaScript-functies (JS) aanroepen vanuit .NET-methoden en .NET-methoden uit JS functies. Deze scenario's worden JavaScript-interoperabiliteit genoemd (JS interop-).

Meer JS interoperabiliteitsrichtlijnen vindt u in de volgende artikelen:

Notitie

JavaScript [JSImport]/[JSExport] interop-API is beschikbaar voor onderdelen aan de clientzijde in ASP.NET Core in .NET 7 of hoger.

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

Compressie voor interactieve serveronderdelen met niet-vertrouwde gegevens

Met compressie, die standaard is ingeschakeld, vermijdt u het maken van beveiligde (geverifieerde/geautoriseerde) interactieve serveronderdelen die gegevens uit niet-vertrouwde bronnen weergeven. Niet-vertrouwde bronnen omvatten routeparameters, queryreeksen, gegevens uit JS interop en andere gegevensbronnen die een externe gebruiker kan beheren (databases, externe services). Zie ASP.NET Core BlazorSignalR richtlijnen en Beveiligingsmaatregelenrichtlijnen voor ASP.NET Core Blazor interactieve server-side renderingvoor meer informatie.

JavaScript-interopabstracties en -functiespakket

Het @microsoft/dotnet-js-interop-pakket (npmjs.com) (Microsoft.JSInterop NuGet-pakket) biedt abstracties en functies voor interop-tussen .NET- en JavaScript-code (JS). Referentiebron is beschikbaar in de dotnet/aspnetcore GitHub-opslagplaats (/src/JSInterop mapje). Zie het README.md-bestand van de GitHub-opslagplaats voor meer informatie.

Notitie

Documentatiekoppelingen naar .NET-referentiebron laden meestal de standaardbranch van de opslagplaats, die de huidige ontwikkeling vertegenwoordigt voor de volgende release van .NET. Als u een tag voor een specifieke release wilt selecteren, gebruikt u de Switch-vertakkingen of tags vervolgkeuzelijst. Zie Een versietag selecteren van ASP.NET Core-broncode (dotnet/AspNetCore.Docs #26205)voor meer informatie.

Aanvullende bronnen voor het schrijven van JS interopscripts in TypeScript:

Interactie met de DOM

Muteer de DOM alleen met JavaScript (JS) wanneer het object niet communiceert met Blazor. Blazor behoudt weergaven van de DOM en communiceert rechtstreeks met DOM-objecten. Als een element dat wordt weergegeven door Blazor extern wordt gewijzigd met behulp van JS rechtstreeks of via JS Interop, komt de DOM mogelijk niet meer overeen met Blazorinterne weergave, wat kan leiden tot niet-gedefinieerd gedrag. Niet-gedefinieerd gedrag kan alleen de presentatie van elementen of hun functies verstoren, maar kan ook beveiligingsrisico's voor de app of server veroorzaken.

Deze richtlijnen zijn niet alleen van toepassing op uw eigen JS interoperabiliteitscode, maar ook op alle JS bibliotheken die door de app worden gebruikt, inclusief alles wat wordt geleverd door een framework van derden, zoals Bootstrap-JS en jQuery-.

In enkele documentatievoorbeelden wordt JS interop gebruikt om een element uitsluitend te muteren voor demonstratiedoeleinden als onderdeel van een voorbeeld. In die gevallen wordt er een waarschuwing weergegeven in de tekst.

Zie JavaScript-functies aanroepen vanuit .NET-methoden in ASP.NET Core Blazorvoor meer informatie.

JavaScript-klasse met een veld van het type functie

Een JavaScript-klasse met een veld van het type functie wordt niet ondersteund door BlazorJS interop. Gebruik Javascript-functies in klassen.

Niet ondersteund:GreetingHelpers.sayHello in de volgende klasse als veld van het functietype wordt niet gedetecteerd door de interop van BlazorJS en kan niet vanuit C#-code worden uitgevoerd.

export class GreetingHelpers {
  sayHello = function() {
    ...
  }
}

Ondersteund:GreetingHelpers.sayHello in de volgende klasse als een functie wordt ondersteund:

export class GreetingHelpers {
  sayHello() {
    ...
  }
}

Pijlfuncties worden ook ondersteund:

export class GreetingHelpers {
  sayHello = () => {
    ...
  }
}

Inline gebeurtenis handlers vermijden

Een JavaScript-functie kan rechtstreeks vanuit een inline gebeurtenis-handler worden aangeroepen. In het volgende voorbeeld is alertUser een JavaScript-functie die wordt aangeroepen wanneer de knop door de gebruiker wordt geselecteerd:

<button onclick="alertUser">Click Me!</button>

Het gebruik van inline gebeurtenis-handlers is echter een slechte ontwerpkeuze voor het aanroepen van JavaScript-functies:

We raden u aan inline gebeurtenishandlers te vermijden ten gunste van oplossingen die handlers in JavaScript toewijzen met addEventListener, zoals in het volgende voorbeeld wordt gedemonstreerd.

AlertUser.razor.js:

export function alertUser() {
  alert('The button was selected!');
}

export function addHandlers() {
  const btn = document.getElementById("btn");
  btn.addEventListener("click", alertUser);
}

AlertUser.razor:

@page "/alert-user"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>Alert User</h1>

<p>
    <button id="btn">Click Me!</button>
</p>

@code {
    private IJSObjectReference? module;

    protected async override Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSObjectReference>("import",
                "./Components/Pages/AlertUser.razor.js");

            await module.InvokeVoidAsync("addHandlers");
        }
    }

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (module is not null)
        {
            try
            {
                await module.DisposeAsync();
            }
            catch (JSDisconnectedException)
            {
            }
        }
    }
}

In het voorgaande voorbeeld komt JSDisconnectedException vast te zitten tijdens het verwijderen van de module als het SignalR-circuit van Blazoruitvalt. Als de voorgaande code wordt gebruikt in een Blazor WebAssembly-app, is er geen SignalR verbinding verloren gegaan, zodat u het try-catch blok kunt verwijderen en de regel kunt verlaten waarmee de module (await module.DisposeAsync();) wordt verwijderd. Zie ASP.NET Core Blazor JavaScript-interoperabiliteit (JS interop)voor meer informatie.

Zie de volgende bronnen voor meer informatie:

Asynchrone JavaScript-aanroepen

JS interop-aanroepen zijn asynchroon, ongeacht of de aangeroepen code synchroon of asynchroon is. Aanroepen zijn asynchroon om ervoor te zorgen dat onderdelen compatibel zijn tussen renderingmodellen aan de serverzijde en clientzijde. Bij het toepassen van server-side rendering moeten JS interop-aanroepen asynchroon zijn omdat ze via een netwerkverbinding worden verzonden. Voor apps die uitsluitend gebruikmaken van rendering aan de clientzijde, worden synchrone JS interop-aanroepen ondersteund.

Objectserialisatie

Blazor gebruikt System.Text.Json voor serialisatie met de volgende vereisten en standaardgedrag:

  • Typen moeten een standaardconstructor hebben, get/set accessors moeten openbaar zijn en velden worden nooit geserialiseerd.
  • Globale standaardserialisatie is niet aanpasbaar om te voorkomen dat bestaande onderdeelbibliotheken worden onderbroken, wat invloed heeft op de prestaties en beveiliging, en dat de betrouwbaarheid wordt verminderd.
  • Het serialiseren van .NET-lidnamen resulteert in JSON-sleutelnamen in kleine letters.
  • JSON wordt gedeserialiseerd als JsonElement C#-exemplaren, waardoor gemengde behuizing mogelijk is. Interne casting voor toewijzing aan C#-modeleigenschappen werkt zoals verwacht, ondanks eventuele verschillen tussen JSON-sleutelnamen en C#-eigenschappennamen.
  • Complexe frameworktypen, zoals KeyValuePair, kunnen worden afgekapt door de IL Trimmer bij het publiceren van en niet aanwezig zijn voor JS interop. We raden u aan aangepaste typen te maken voor typen die de IL Trimmer verwijdert.
  • Blazor is altijd afhankelijk van reflectie voor JSON-serialisatie, inclusief bij het gebruik van C# brongeneratie. Als u JsonSerializerIsReflectionEnabledByDefault instelt op false in het projectbestand van de app, treedt er een fout op bij het serialiseren.

JsonConverter-API is beschikbaar voor aangepaste serialisatie. Eigenschappen kunnen worden geannoteerd met een [JsonConverter] kenmerk om standaardserialisatie voor een bestaand gegevenstype te overschrijven.

Zie de volgende bronnen in de .NET-documentatie voor meer informatie:

Blazor ondersteunt geoptimaliseerde byte-array JS interop die het coderen/decoderen van byte-arrays naar Base64 vermijdt. De app kan aangepaste serialisatie toepassen en de resulterende bytes doorgeven. Zie JavaScript-functies aanroepen vanuit .NET-methoden in ASP.NET Core Blazorvoor meer informatie.

Blazor ondersteunt niet-gemartelde JS interop wanneer een groot aantal .NET-objecten snel wordt geserialiseerd of wanneer grote .NET-objecten of veel .NET-objecten moeten worden geserialiseerd. Zie JavaScript-functies aanroepen vanuit .NET-methoden in ASP.NET Core Blazorvoor meer informatie.

DOM-opschoontaken tijdens het verwijderen van onderdelen

Voer tijdens het verwijderen van onderdelen geen JS interoperabiliteitscode uit voor DOM-opschoontaken. Gebruik in plaats daarvan het MutationObserver patroon in JavaScript (JS) op de client om de volgende redenen:

  • Het onderdeel is mogelijk verwijderd uit de DOM op het moment dat de opschoningscode wordt uitgevoerd in Dispose{Async}.
  • Tijdens server-side rendering is de Blazor renderer mogelijk door het framework verwijderd tegen de tijd dat uw opschooncode wordt uitgevoerd in Dispose{Async}.

Met het MutationObserver patroon kunt u een functie uitvoeren wanneer een element uit de DOM wordt verwijderd.

In het volgende voorbeeld is de DOMCleanup-component:

  • Bevat een <div> met een id van cleanupDiv. Het <div> element wordt verwijderd uit de DOM, samen met de rest van de DOM-markeringen van het onderdeel wanneer het onderdeel wordt verwijderd uit de DOM.
  • Laadt de DOMCleanupJS-klasse uit het DOMCleanup.razor.js-bestand en roept de bijbehorende createObserver-functie aan om de MutationObserver callback in te stellen. Deze taken worden uitgevoerd in de OnAfterRenderAsync levenscyclusmethode.

DOMCleanup.razor:

@page "/dom-cleanup"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>DOM Cleanup Example</h1>

<div id="cleanupDiv"></div>

@code {
    private IJSObjectReference? module;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSObjectReference>(
                "import", "./Components/Pages/DOMCleanup.razor.js");

            await module.InvokeVoidAsync("DOMCleanup.createObserver");
        }
    }

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (module is not null)
        {
            try
            {
                await module.DisposeAsync();
            }
            catch (JSDisconnectedException)
            {
            }
        }
    }
}

In het vorige voorbeeld wordt JSDisconnectedException vastgelopen tijdens moduleverwijdering als het SignalR circuit van Blazorverloren gaat. Als de voorgaande code wordt gebruikt in een Blazor WebAssembly-app, is er geen SignalR verbinding verloren gegaan, zodat u het try-catch blok kunt verwijderen en de regel kunt verlaten waarmee de module (await module.DisposeAsync();) wordt verwijderd. Zie ASP.NET Core Blazor JavaScript-interoperabiliteit (JS interop)voor meer informatie.

In het volgende voorbeeld wordt de MutationObserver callback uitgevoerd telkens wanneer een DOM-wijziging plaatsvindt. Voer de opschooncode uit wanneer de if-instructie bevestigt dat het doelelement (cleanupDiv) is verwijderd (if (targetRemoved) { ... }). Het is belangrijk om de verbinding met het MutationObserver te verbreken en te verwijderen om een geheugenlek te voorkomen nadat de opschooncode is uitgevoerd.

DOMCleanup.razor.js naast het voorgaande DOMCleanup onderdeel geplaatst:

export class DOMCleanup {
  static observer;

  static createObserver() {
    const target = document.querySelector('#cleanupDiv');

    this.observer = new MutationObserver(function (mutations) {
      const targetRemoved = mutations.some(function (mutation) {
        const nodes = Array.from(mutation.removedNodes);
        return nodes.indexOf(target) !== -1;
      });

      if (targetRemoved) {
        // Cleanup resources here
        // ...

        // Disconnect and delete MutationObserver
        this.observer && this.observer.disconnect();
        delete this.observer;
      }
    });

    this.observer.observe(target.parentNode, { childList: true });
  }
}

window.DOMCleanup = DOMCleanup;

De voorgaande benaderingen voegen de MutationObserver toe aan target.parentNode, die werkt totdat parentNode zelf uit de DOM wordt verwijderd. Dit is een veelvoorkomend scenario, bijvoorbeeld wanneer u naar een nieuwe pagina navigeert, waardoor het hele paginaonderdeel uit de DOM wordt verwijderd. In dergelijke gevallen worden onderliggende onderdelen die wijzigingen in de pagina observeren, niet correct opgeschoond.

Stel niet dat het observeren van document.body, in plaats van target.parentNode, een beter doel is. Het observeren van document.body heeft gevolgen voor de prestaties, omdat callbacklogica wordt uitgevoerd voor alle DOM-updates, ongeacht of ze iets te maken hebben met uw element. Gebruik een van de volgende methoden:

  • In gevallen waarin u een geschikt bovenliggend knooppunt kunt identificeren om te observeren, gebruikt u MutationObserver ermee. Idealiter is deze bovenliggende voorouder afgestemd op de wijzigingen die u wilt observeren, en niet op document.body.
  • In plaats van MutationObserverte gebruiken, kunt u een aangepast element en disconnectedCallbackgebruiken. De gebeurtenis wordt altijd geactiveerd wanneer de verbinding met uw aangepaste element wordt verbroken, ongeacht waar het zich in de DOM bevindt ten opzichte van de DOM-wijziging.

JavaScript-interop-aanroepen zonder circuit

Deze sectie is alleen van toepassing op apps aan de serverzijde.

Interop-aanroepen van JavaScript (JS) kunnen niet worden uitgegeven nadat BlazorSignalR circuit is verbroken. Zonder een circuit, tijdens het verwijderen van componenten of op elk ander moment waarop er geen circuit is, mislukken de volgende methode-aanroepen en wordt er een bericht gelogd dat het circuit is ontkoppeld met code JSDisconnectedException.

Om te voorkomen dat JSDisconnectedException wordt gelogd of om aangepaste informatie vast te leggen, kunt u de uitzondering in een try-catch-instructie ondervangen.

Voor het volgende voorbeeld van het verwijderen van onderdelen:

  • Het onderdeel aan de serverzijde implementeert IAsyncDisposable.
  • module is een IJSObjectReference-module voor een JS.
  • JSDisconnectedException wordt gevangen en niet geregistreerd.
  • Desgewenst kunt u aangepaste gegevens in de catch-instructie vastleggen op het gewenste logboekniveau. In het volgende voorbeeld worden geen aangepaste gegevens vastgelegd, omdat wordt ervan uitgegaan dat de ontwikkelaar er niet om geeft wanneer of waar circuits worden losgekoppeld tijdens het verwijderen van onderdelen.
async ValueTask IAsyncDisposable.DisposeAsync()
{
    try
    {
        if (module is not null)
        {
            await module.DisposeAsync();
        }
    }
    catch (JSDisconnectedException)
    {
    }
}

Als u uw eigen JS objecten moet opschonen of andere JS code op de client moet uitvoeren nadat een circuit in een Blazor-app aan de serverzijde verloren is gegaan, gebruikt u het MutationObserver patroon in JS op de client. Met het MutationObserver patroon kunt u een functie uitvoeren wanneer een element uit de DOM wordt verwijderd.

Zie de volgende artikelen voor meer informatie:

JavaScript-bestanden in cache

JavaScript-bestanden (JS) en andere statische assets worden doorgaans niet opgeslagen in de cache van clients tijdens de ontwikkeling in de Development-omgeving. Tijdens de ontwikkeling bevatten statische asset-aanvragen de -Cache-Control-header met een waarde van no-cache of max-age, die een waarde van nul hebben (0).

Tijdens de productie in de Production omgevingworden JS bestanden meestal in de cache opgeslagen door clients.

Om caching aan de clientzijde in browsers uit te schakelen, gebruiken ontwikkelaars meestal een van de volgende methoden:

  • Cache uitschakelen wanneer de console voor ontwikkelaarshulpprogramma's van de browser is geopend. Richtlijnen vindt u in de documentatie voor ontwikkelhulpprogramma's van elke browseronderhouder:
  • Voer een handmatige browservernieuwing uit van een webpagina van de Blazor-app om JS bestanden van de server opnieuw te laden. ASP.NET Core's HTTP Caching Middleware eert altijd een geldige no-cache Cache-Control header die door een client is verzonden.

Zie voor meer informatie:

Groottelimieten voor JavaScript-interop-aanroepen

Deze sectie is alleen van toepassing op interactieve onderdelen in apps aan de serverzijde. Voor onderdelen aan de clientzijde legt het framework geen limiet op voor de grootte van JavaScript (JS) interop-invoer en -uitvoer.

Voor interactieve onderdelen in apps aan de serverzijde worden JS interop-aanroepen die gegevens doorgeven van de client aan de server, beperkt tot de maximale binnenkomende SignalR berichtgrootte die is toegestaan voor hubmethoden, die worden afgedwongen door HubOptions.MaximumReceiveMessageSize (standaard: 32 kB). JS naar .NET SignalR berichten die groter zijn dan MaximumReceiveMessageSize zullen een fout veroorzaken. Het framework legt geen limiet op voor de grootte van een SignalR bericht van de hub naar een client. Zie voor meer informatie over de limiet, foutberichten en richtlijnen voor het omgaan met berichtgroottelimieten ASP.NET Core BlazorSignalR richtlijnen.

Bepalen waar de app wordt uitgevoerd

Als het relevant is voor de app om te weten waar code wordt uitgevoerd voor JS interop-aanroepen, gebruik OperatingSystem.IsBrowser om te bepalen of het onderdeel wordt uitgevoerd in de context van een browser op WebAssembly.