ASP.NET Core Blazor JavaScript-Interoperabilität (JS Interop)

Hinweis

Dies ist nicht die neueste Version dieses Artikels. Die aktuelle Version finden Sie in der .NET 10-Version dieses Artikels.

Warnung

Diese Version von ASP.NET Core wird nicht mehr unterstützt. Weitere Informationen finden Sie in der .NET- und .NET Core-Supportrichtlinie. Die aktuelle Version finden Sie in der .NET 10-Version dieses Artikels.

Eine Blazor-App kann JavaScript-(JS)-Funktionen über .NET-Methoden und .NET-Methoden über JS-Funktionen aufrufen. Diese Szenarien werden als JavaScript-Interoperabilität (JS Interop) bezeichnet.

Weitere JS-Interop-Anleitungen finden Sie in den folgenden Artikeln:

Hinweis

Die JavaScript-Interop-API [JSImport]/[JSExport] ist für clientseitige Komponenten in ASP.NET Core in .NET 7 oder höher verfügbar.

Weitere Informationen finden Sie unter JavaScript-Interoperabilität von JSImport/JSExport mit ASP.NET CoreBlazor.

Komprimierung für interaktive Serverkomponenten mit nicht vertrauenswürdigen Daten

Mit standardmäßiger aktivierter Komprimierung vermeiden Sie die Erstellung sicherer (authentifizierter/autorisierter) serverseitiger Komponenten, die Daten aus nicht vertrauenswürdigen Quellen rendern. Nicht vertrauenswürdige Quellen umfassen Routenparameter, Abfragezeichenfolgen, Daten aus JS-Interoperabilität und andere Datenquellen, die ein Drittbenutzer steuern kann (Datenbanken, externe Dienste). Weitere Informationen finden Sie unter ASP.NET Core BlazorSignalR Anleitungen und Anleitung zur Risikominderung für ASP.NET Core Blazor interaktives serverseitiges Rendering.

Paket mit JavaScript-Interop-Abstraktionen und -Features

Das @microsoft/dotnet-js-interop Paket (npmjs.com) (Microsoft.JSInterop NuGet-Paket) stellt Abstraktionen und Funktionen für die Interoperabilität zwischen .NET- und JavaScript-Code (JS) bereit. Die Referenzquelle finden Sie im GitHub-Repository dotnet/aspnetcore (im Ordner /src/JSInterop). Weitere Informationen finden Sie im GitHub-Repository in der Datei README.md.

Hinweis

Dokumentationslinks zur .NET-Referenzquelle laden in der Regel den Standardbranch des Repositorys, der die aktuelle Entwicklung für das nächste Release von .NET darstellt. Um ein Tag für ein bestimmtes Release auszuwählen, wählen Sie diesen mit der Dropdownliste Switch branches or tags (Branches oder Tags wechseln) aus. Weitere Informationen finden Sie unter How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205) (Auswählen eines Versionstags von ASP.NET Core-Quellcode (dotnet/AspNetCore.Docs #26205)).

Weitere Ressourcen zum Schreiben von JS-Interop-Skripts in TypeScript:

Interaktion mit dem DOM

Mutieren Sie das DOM nur mit JavaScript (JS), wenn das Objekt nicht mit Blazor interagiert. Blazor verwaltet Darstellungen des DOM und interagiert direkt mit DOM-Objekten. Wenn ein von Blazor gerendertes Element extern mithilfe von JS direkt oder über JS-Interop geändert wird, stimmt das DOM möglicherweise nicht mehr der internen Darstellung von Blazor überein, sodass es zu einem undefinierte Verhalten kommen kann. Ein undefiniertes Verhalten kann lediglich die Darstellung von Elementen oder deren Funktionen beeinträchtigen, aber auch Sicherheitsrisiken für die App oder den Server mit sich bringen.

Diese Anleitung gilt nicht nur für Ihren eigenen JS-Interop-Code, sondern auch für alle JS-Bibliotheken, die von der App verwendet werden, einschließlich aller von einem Drittanbieterframework bereitgestellten Inhalte wie Bootstrap JS und jQuery.

In einigen Dokumentationsbeispielen wird JS-Interop verwendet, um ein Element als Teil eines Beispiels lediglich zu Demonstrationszwecken zu mutieren. In diesen Fällen wird im Text eine Warnung angezeigt.

Weitere Informationen finden Sie unter Aufrufen von JavaScript-Funktionen über .NET-Methoden in Blazor in ASP.NET Core.

JavaScript-Klasse mit einem Feld vom Typ „Funktion“

Eine JavaScript-Klasse mit einem Feld vom Typ Funktion wird von BlazorJS-Interoperabilität nicht unterstützt. Verwenden Sie Javascript-Funktionen in Klassen.

Nicht unterstützt:GreetingHelpers.sayHello in der folgenden Klasse wird als Feld des Typs „Funktion“ von der JS-Interop von Blazor nicht erkannt und kann nicht aus C#-Code heraus ausgeführt werden:

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

Unterstützt:GreetingHelpers.sayHello in der folgenden Klasse als Funktion unterstützt:

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

Pfeilfunktionen werden ebenfalls unterstützt:

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

Vermeiden Sie Inline-Event-Handler

Eine JavaScript-Funktion kann direkt aus einem Inlineereignishandler aufgerufen werden. Im folgenden Beispiel ist alertUser eine JavaScript-Funktion, die aufgerufen wird, wenn die Schaltfläche von den Benutzenden ausgewählt wird:

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

Die Verwendung von Inlineereignishandlern ist jedoch eine schlechte Designentscheidung für den Aufruf von JavaScript-Funktionen:

Es wird empfohlen, Inlineereignishandler zu vermeiden und stattdessen Ansätze zu verwenden, bei denen Handler in JavaScript mit addEventListener zugewiesen werden, wie das folgende Beispiel zeigt:

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

Im vorherigen Beispiel wird JSDisconnectedException während der Freigabe des Moduls aufgefangen, falls der SignalR-Schaltkreis von Blazor verloren geht. Wenn der oben gezeigte Code in einer Blazor WebAssembly-App verwendet wird, gibt es keine SignalR-Verbindung, die verloren gehen könnte, sodass Sie den try-catch-Block entfernen und die Zeile beibehalten können, die das Modul freigibt (await module.DisposeAsync();). Weitere Informationen finden Sie unter JavaScript-Interoperabilität von ASP.NET Core-Blazor (JS-Interop).

Weitere Informationen finden Sie in den folgenden Ressourcen:

Asynchrone JavaScript-Aufrufe

JS Interop-Aufrufe sind asynchron, unabhängig davon, ob der aufgerufene Code synchron oder asynchron ist. Die Aufrufe sind asynchron, um sicherzustellen, dass die Komponenten mit den serverseitigen und clientseitigen Rendering-Modellen kompatibel sind. Bei Verwendung von serverseitigem Rendering müssen JS-Interop-Aufrufe asynchron sein, da sie über eine Netzwerkverbindung gesendet werden. Für Apps, die ausschließlich clientseitiges Rendering verwenden, werden synchrone JS-Interop-Aufrufe unterstützt.

Weitere Informationen finden Sie in den folgenden Artikeln:

Weitere Informationen finden Sie unter Aufrufen von JavaScript-Funktionen über .NET-Methoden in Blazor in ASP.NET Core.

Objektserialisierung

Blazor verwendet System.Text.Json für die Serialisierung. Dabei gelten die folgenden Anforderungen und Standardverhalten:

  • Typen müssen über einen Standardkonstruktor verfügen, get/set-Zugriffsmethoden müssen öffentlich sein, und Felder werden nie serialisiert.
  • Die globale Standardserialisierung kann nicht angepasst werden, damit vorhandene Komponentenbibliotheken ihre Funktionalität nicht verlieren, die Leistung und Sicherheit nicht beeinträchtigt werden und die Zuverlässigkeit nicht verringert wird.
  • Das Serialisieren von .NET-Membernamen führt zu JSON-Schlüsselnamen in Kleinbuchstaben.
  • JSON wird als JsonElementC#-Instanzen deserialisiert, die gemischte Groß- und Kleinschreibung zulassen. Die interne Umwandlung für die Zuweisung zu C#-Modelleigenschaften funktioniert trotz aller Unterschiede zwischen JSON-Schlüsselnamen und C#-Eigenschaftsnamen erwartungsgemäß.
  • Komplexe Framework-Typen könnten beim Veröffentlichen vom IL Trimmer weggekürzt werden und dann für die Interoperabilität mit JS oder die JSON-Serialisierung/-Deserialisierung nicht verfügbar sein. Wir empfehlen, benutzerdefinierte Typen für Typen zu erstellen, die der IL Trimmer entfernt.
  • Blazor basiert immer auf Reflexion für die JSON-Serialisierung, einschließlich der Verwendung der C#-Quellgenerierung. Das Festlegen von JsonSerializerIsReflectionEnabledByDefault auf false in der Projektdatei der App führt zu einem Fehler, wenn versucht wird, die Serialisierung durchzuführen.

Die benutzerdefinierte Serialisierung der JsonConverter-API ist möglich. Eigenschaften können mit einem [JsonConverter]-Attribut versehen werden, um die Standardserialisierung eines vorhandenen Datentyps zu überschreiben.

Weitere Informationen finden Sie in den folgenden Ressourcen der .NET-Dokumentation:

Blazor unterstützt optimiertes Bytearray-JS-Interop, mit dem sich das Codieren/Decodieren von Bytearrays in Base64 vermeiden lässt. Die App kann die benutzerdefinierte Serialisierung anwenden und die resultierenden Bytes übergeben. Weitere Informationen finden Sie unter Aufrufen von JavaScript-Funktionen über .NET-Methoden in Blazor in ASP.NET Core.

Blazor unterstützt unmarshalled JS-Interop, wenn eine große Anzahl von .NET-Objekten schnell serialisiert wird oder wenn große .NET-Objekte oder viele .NET-Objekte serialisiert werden müssen. Weitere Informationen finden Sie unter Aufrufen von JavaScript-Funktionen über .NET-Methoden in Blazor in ASP.NET Core.

DOM-Bereinigungsvorgänge während der Beseitigung von Komponenten

Führen Sie nicht den JS-Interopcode für die DOM-Bereinigungsvorgänge während der Beseitigung von Komponenten aus. Verwenden Sie stattdessen aus folgenden Gründen auf dem Client das MutationObserver-Muster in JavaScript (JS):

  • Die Komponente wurde möglicherweise nach Ausführung des Bereinigungscodes in Dispose{Async} aus dem DOM entfernt.
  • Beim serverseitigen Rendering wurde der Blazor-Renderer möglicherweise bereits durch das Framework verworfen, wenn der Bereinigungscode in Dispose{Async} ausgeführt wird.

Mit dem Muster MutationObserver können Sie eine Funktion ausführen, wenn ein Element aus dem DOM entfernt wird.

Im folgenden Beispiel die DOMCleanup-Komponente:

  • Enthält ein <div> mit einem id von cleanupDiv. Das <div>-Element wird zusammen mit dem restlichen DOM-Markup der Komponente aus dem DOM entfernt, wenn die Komponente aus dem DOM entfernt wird.
  • Lädt die DOMCleanup-JS-Klasse aus der Datei DOMCleanup.razor.js und ruft die createObserver-Funktion auf, um den MutationObserver-Rückruf einzurichten. Diese Aufgaben werden in der OnAfterRenderAsync-Lebenszyklusmethode durchgeführt.

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

Im vorherigen Beispiel wird JSDisconnectedException während der Freigabe des Moduls aufgefangen, falls der SignalR-Schaltkreis von Blazor verloren geht. Wenn der oben gezeigte Code in einer Blazor WebAssembly-App verwendet wird, gibt es keine SignalR-Verbindung, die verloren gehen könnte, sodass Sie den try-catch-Block entfernen und die Zeile beibehalten können, die das Modul freigibt (await module.DisposeAsync();). Weitere Informationen finden Sie unter JavaScript-Interoperabilität von ASP.NET Core-Blazor (JS-Interop).

Im folgenden Beispiel wird der MutationObserver-Rückruf jedes Mal ausgeführt, wenn eine DOM-Änderung erfolgt. Führen Sie den Bereinigungscode aus, wenn die if-Anweisung bestätigt, dass das Zielelement (cleanupDiv) entfernt wurde (if (targetRemoved) { ... }). Es ist wichtig, die Verbindung zu MutationObserver zu trennen und es zu löschen, um ein Speicherleck zu vermeiden, nachdem Ihr Bereinigungscode ausgeführt wurde.

DOMCleanup.razor.js nebeneinander mit der vorhergehenden DOMCleanup-Komponente:

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;

Die vorstehenden Ansätze fügen die MutationObserver an target.parentNodean, die funktioniert, bis parentNode selbst aus dem DOM entfernt wird. Dies ist ein häufiges Szenario, z. B. beim Navigieren zu einer neuen Seite, wodurch die gesamte Seitenkomponente aus dem DOM entfernt wird. In solchen Fällen werden alle untergeordneten Komponenten, die Änderungen innerhalb der Seite beobachten, nicht ordnungsgemäß bereinigt.

Gehen Sie nicht davon aus, dass das Beobachten von document.bodyanstelle von target.parentNodeein besseres Ziel ist. Das Beobachten von document.body hat Leistungsauswirkungen, da die Rückruflogik für bei allen DOM-Updates ausgeführt wird, unabhängig davon, ob sie etwas mit Ihrem Element zu tun haben oder nicht. Verwenden Sie eine der folgenden Ansätze:

  • In Fällen, in denen Sie einen geeigneten Vorgängerknoten identifizieren können, der beobachtet werden soll, verwenden Sie MutationObserver damit. Im Idealfall ist dieser Vorgänger auf die Änderungen begrenzt, die Sie beobachten möchten, anstatt document.body.
  • Anstatt MutationObserverzu verwenden, sollten Sie ein benutzerdefiniertes Element und disconnectedCallbackverwenden. Das Ereignis wird immer ausgelöst, wenn das benutzerdefinierte Element getrennt wird, unabhängig von seiner Position im DOM im Verhältnis zu der DOM-Änderung.

JavaScript-Interopaufrufe ohne Leitung

Dieser Abschnitt gilt nur für serverseitige Apps.

JavaScript-Interopaufrufe (JS) können nicht ausgeführt werden, nachdem der SignalR-Circuit von Blazor getrennt wurde. Ohne eine Leitung während der Entsorgung einer Komponente oder zu jedem anderen Zeitpunkt, zu dem keine Leitung vorhanden ist, schlagen die folgenden Methodenaufrufe fehl und protokollieren eine Meldung, dass die Leitung als JSDisconnectedException getrennt ist:

Um die Protokollierung von JSDisconnectedException zu vermeiden oder benutzerdefinierte Informationen zu protokollieren, fangen Sie die Ausnahme in einer try-catch-Anweisung ab.

Für das folgende Beispiel zur Entsorgung von Komponenten:

  • Die serverseitige Komponente implementiert IAsyncDisposable.
  • module ist ein IJSObjectReference für ein JS-Modul.
  • JSDisconnectedException wird abgefangen und nicht protokolliert.
  • Optional können Sie benutzerdefinierte Informationen in der catch-Anweisung auf beliebiger Protokollebene protokollieren. Im folgenden Beispiel werden keine benutzerdefinierten Informationen protokolliert, da davon ausgegangen wird, dass es dem Entwickler gleichgültig ist, wann oder wo Verbindungen während der Komponentenbereinigung getrennt werden.
async ValueTask IAsyncDisposable.DisposeAsync()
{
    try
    {
        if (module is not null)
        {
            await module.DisposeAsync();
        }
    }
    catch (JSDisconnectedException)
    {
    }
}

Wenn Sie Ihre eigenen JS Objekte bereinigen oder anderen JS Code auf dem Client ausführen müssen, nachdem ein Schaltkreis in einer serverseitigen Blazor App verloren gegangen ist, verwenden Sie das MutationObserver Muster auf JS dem Client. Mit dem Muster MutationObserver können Sie eine Funktion ausführen, wenn ein Element aus dem DOM entfernt wird.

Weitere Informationen finden Sie in den folgenden Artikeln:

Zwischengespeicherte JavaScript-Dateien

JavaScript-Dateien (JS) und andere statische Objekte werden in der Regel während der Entwicklung in der Development-Umgebung nicht auf Clients zwischengespeichert. Während der Entwicklung enthalten statische Ressourcenanforderungen den Cache-Control-Header mit einem Wert von no-cache oder max-age mit einem Wert 0 (0).

Während der Produktion in der Production-Umgebung werden JS-Dateien in der Regel von Clients zwischengespeichert.

Um das clientseitige Zwischenspeichern in Browsern zu deaktivieren, verwenden Entwickler in der Regel einen der folgenden Ansätze:

  • Deaktivieren Sie die Zwischenspeicherung, wenn die Entwicklertoolkonsole des Browsers geöffnet ist. Anleitungen finden Sie in der Dokumentation zur Entwicklertools der einzelnen Browser-Maintainer:
  • Führen Sie eine manuelle Browseraktualisierung einer Webseite der App Blazor durch, um JS-Dateien erneut vom Server zu laden. ASP.NET Core http response caching middleware berücksichtigt immer einen gültigen No-Cache-HeaderCache-Control, der von einem Client gesendet wird.

Weitere Informationen finden Sie unter:

Größenbeschränkungen bei JavaScript-Interop-Aufrufen

Dieser Abschnitt gilt nur für interaktive Komponenten in serverseitigen Apps. Für clientseitige Komponenten gibt das Framework keine Größenbeschränkung für JavaScript (JS)-Interop-Eingaben und -Ausgaben vor.

Bei interaktiven Komponenten in serverseitigen Apps wird die Größe von JS-Interop-Aufrufen, die Daten vom Client an den Server übergeben, durch die maximale Größe eingehender SignalR-Nachrichten beschränkt, die für Hubmethoden zulässig sind. Dies wird durch HubOptions.MaximumReceiveMessageSize erzwungen (Standardwert: 32 KB). JS-Nachrichten an .NET von SignalR, die größer als MaximumReceiveMessageSize sind, führen zu einem Fehler. Das Framework beinhaltet keine Beschränkungen hinsichtlich der Größe einer SignalR-Nachricht vom Hub an einen Client. Weitere Informationen zum Größenlimit sowie Fehlermeldungen und Anleitungen zum Umgang mit Nachrichtengrößenbeschränkungen finden Sie im ASP.NET Core-Blazor-SignalR-Leitfaden unter Maximale Größe für empfangene Nachrichten.

Ermitteln, wo die App ausgeführt wird

Wenn es für die App relevant ist, zu wissen, wo Code für JS-Interopaufrufe ausgeführt wird, verwenden Sie OperatingSystem.IsBrowser, um festzustellen, ob die Komponente im Kontext des Browsers in WebAssembly ausgeführt wird.