Aufrufen von JavaScript-Funktionen über .NET-Methoden in ASP.NET Core Blazor

In diesem Artikel wird erläutert, wie JavaScript-Funktionen (JS) aus .NET aufgerufen werden.

Informationen zum Aufrufen von .NET-Methoden aus JS finden Sie unter JS.

IJSRuntime wird vom Blazor-Framework registriert. Fügen Sie für einen Aufruf an JS über .NET die IJSRuntime-Abstraktion ein, und rufen Sie eine der folgenden Methoden auf:

Für die oben genannten .NET-Methoden, die JS-Funktionen aufrufen, gilt Folgendes:

  • Der Funktionsbezeichner (String) ist relativ zum globalen Bereich (window). Der Bezeichner zum Aufrufen von window.someScope.someFunction lautet someScope.someFunction. Die Funktion muss nicht registriert werden, bevor sie aufgerufen wird.
  • Übergeben Sie eine beliebige Anzahl von serialisierbaren JSON-Argumenten in Object[] an eine JS-Funktion.
  • Das Abbruchstoken (CancellationToken) leitet eine Benachrichtigung weiter, dass Vorgänge abgebrochen werden sollen.
  • TimeSpan stellt ein Zeitlimit für einen JS-Vorgang dar.
  • Der Rückgabetyp TValue muss ebenfalls mit JSON serialisierbar sein. TValue sollte mit dem .NET-Typ übereinstimmen, der dem zurückgegebenen JSON-Typ am ehesten entspricht.
  • Ein JS Promise-Objekt wird für InvokeAsync-Methoden zurückgegeben. InvokeAsync entpackt das Promise-Objekt und gibt den Wert zurück, der vom Promise-Objekt erwartet wird.

Für Blazor-Apps mit aktiviertem Voarabrendering sind Aufrufe von JS während des Voarabrenderings nicht möglich. Weitere Informationen finden Sie im Abschnitt Voarabrendering.

Das folgende Beispiel basiert auf TextDecoder, dabei handelt es sich um einen auf JS basierenden Decoder. Im Beispiel wird veranschaulicht, wie eine JS-Funktion aus einer C#-Methode aufgerufen wird, die eine Anforderung aus Entwicklercode in eine vorhandene JS-API auslagert. Die JS-Funktion akzeptiert ein Bytearray von einer C#-Methode, decodiert das Array und gibt den Text zum Anzeigen an die Komponente zurück.

<script>
  window.convertArray = (win1251Array) => {
    var win1251decoder = new TextDecoder('windows-1251');
    var bytes = new Uint8Array(win1251Array);
    var decodedArray = win1251decoder.decode(bytes);
    console.log(decodedArray);
    return decodedArray;
  };
</script>

Hinweis

Allgemeine Anleitungen zum Speicherort von JS und unsere Empfehlungen für Produktions-Apps finden Sie unter JavaScript-Interoperabilität von Blazor in ASP.NET Core (JS-Interoperabilität).

Die folgende CallJsExample1-Komponente:

  • ruft die JS-Funktion convertArray mit InvokeAsync auf, wenn auf eine Schaltfläche geklickt wird (Convert Array).
  • Nachdem die JS-Funktion aufgerufen wurde, wird das übergebene Array in eine Zeichenfolge konvertiert. Die Zeichenfolge wird zur Anzeige an die Komponente zurückgegeben (text).

Pages/CallJsExample1.razor:

@page "/call-js-example-1"
@inject IJSRuntime JS

<h1>Call JS <code>convertArray</code> Function</h1>

<p>
    <button @onclick="ConvertArray">Convert Array</button>
</p>

<p>
    @text
</p>

<p>
    Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>: 
    <a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
    <a href="https://www.imdb.com/name/nm0472710/">David Krumholtz on IMDB</a>
</p>

@code {
    private MarkupString text;

    private uint[] quoteArray = 
        new uint[]
        {
            60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
            116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
            108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
            105, 118, 101, 114, 115, 101, 10, 10,
        };

    private async Task ConvertArray()
    {
        text = new(await JS.InvokeAsync<string>("convertArray", quoteArray));
    }
}

JavaScript-API auf Benutzergesten beschränkt

Dieser Abschnitt gilt nur für -Apps.

Einige JavaScript-APIs (JS) für Browser können nur im Zusammenhang mit einer Benutzergeste ausgeführt werden, z. B. mit JS. Diese APIs können nicht über den JS-Interop-Mechanismus in Blazor Server-Apps aufgerufen werden, da die Behandlung von Ereignissen auf der Benutzeroberfläche asynchron und im Allgemeinen nicht mehr im Kontext der Benutzergeste erfolgt. Die App muss das Ereignis auf der Benutzeroberfläche vollständig in JavaScript verarbeiten. Verwenden Sie daher onclick anstelle des Attributs Blazor der @onclick-Anweisung.

Aufrufen von JavaScript-Funktionen ohne Lesen eines zurückgegebenen Werts (InvokeVoidAsync)

Verwenden Sie InvokeVoidAsync in folgenden Fällen:

  • .NET ist nicht erforderlich, um das Ergebnis eines JavaScript-Aufrufs (JS) zu lesen.
  • JS-Funktionen geben JS oder undefined zurück.

Stellen Sie eine displayTickerAlert1JS-Funktion bereit. Die Funktion wird mit InvokeVoidAsync aufgerufen und gibt keinen Wert zurück:

<script>
  window.displayTickerAlert1 = (symbol, price) => {
    alert(`${symbol}: $${price}!`);
  };
</script>

Hinweis

Allgemeine Anleitungen zum Speicherort von JS und unsere Empfehlungen für Produktions-Apps finden Sie unter JavaScript-Interoperabilität von Blazor in ASP.NET Core (JS-Interoperabilität).

Beispiel (InvokeVoidAsync) für die .razor-Komponente

TickerChanged ruft die handleTickerChanged1-Methode in der folgenden CallJsExample2-Komponente auf.

Pages/CallJsExample2.razor:

@page "/call-js-example-2"
@inject IJSRuntime JS

<h1>Call JS Example 2</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new();
    private string? stockSymbol;
    private decimal price;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
    }
}

Beispiel (InvokeVoidAsync) für die .cs-Klasse

JsInteropClasses1.cs:

using Microsoft.JSInterop;

public class JsInteropClasses1 : IDisposable
{
    private readonly IJSRuntime js;

    public JsInteropClasses1(IJSRuntime js)
    {
        this.js = js;
    }

    public async ValueTask TickerChanged(string symbol, decimal price)
    {
        await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
    }

    public void Dispose()
    {
    }
}

TickerChanged ruft die handleTickerChanged1-Methode in der folgenden CallJsExample3-Komponente auf.

Pages/CallJsExample3.razor:

@page "/call-js-example-3"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call JS Example 3</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new();
    private string? stockSymbol;
    private decimal price;
    private JsInteropClasses1? jsClass;

    protected override void OnInitialized()
    {
        jsClass = new(JS);
    }

    private async Task SetStock()
    {
        if (jsClass is not null)
        {
            stockSymbol = 
                $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
            price = r.Next(1, 101);
            await jsClass.TickerChanged(stockSymbol, price);
        }
    }

    public void Dispose() => jsClass?.Dispose();
}

Aufrufen von JavaScript-Funktionen und Lesen eines zurückgegebenen Werts (InvokeAsync)

Verwenden Sie InvokeAsync, wenn .NET das Ergebnis eines JavaScript-Aufrufs (JS) lesen soll.

Stellen Sie eine displayTickerAlert2JS-Funktion bereit. Im folgenden Beispiel wird eine anzuzeigende Zeichenfolge vom Aufrufer zurückgegeben:

<script>
  window.displayTickerAlert2 = (symbol, price) => {
    if (price < 20) {
      alert(`${symbol}: $${price}!`);
      return "User alerted in the browser.";
    } else {
      return "User NOT alerted.";
    }
  };
</script>

Hinweis

Allgemeine Anleitungen zum Speicherort von JS und unsere Empfehlungen für Produktions-Apps finden Sie unter JavaScript-Interoperabilität von Blazor in ASP.NET Core (JS-Interoperabilität).

Beispiel (InvokeAsync) für die .razor-Komponente

TickerChanged ruft die handleTickerChanged2-Methode auf und zeigt die zurückgegebene Zeichenfolge in der folgenden CallJsExample4-Komponente an.

Pages/CallJsExample4.razor:

@page "/call-js-example-4"
@inject IJSRuntime JS

<h1>Call JS Example 4</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private Random r = new();
    private string? stockSymbol;
    private decimal price;
    private string? result;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        var interopResult = 
            await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
        result = $"Result of TickerChanged call for {stockSymbol} at " +
            $"{price.ToString("c")}: {interopResult}";
    }
}

Beispiel (InvokeAsync) für die .cs-Klasse

JsInteropClasses2.cs:

using Microsoft.JSInterop;

public class JsInteropClasses2 : IDisposable
{
    private readonly IJSRuntime js;

    public JsInteropClasses2(IJSRuntime js)
    {
        this.js = js;
    }

    public async ValueTask<string> TickerChanged(string symbol, decimal price)
    {
        return await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
    }

    public void Dispose()
    {
    }
}

TickerChanged ruft die handleTickerChanged2-Methode auf und zeigt die zurückgegebene Zeichenfolge in der folgenden CallJsExample5-Komponente an.

Pages/CallJsExample5.razor:

@page "/call-js-example-5"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call JS Example 5</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private Random r = new();
    private string? stockSymbol;
    private decimal price;
    private JsInteropClasses2? jsClass;
    private string? result;

    protected override void OnInitialized()
    {
        jsClass = new(JS);
    }

    private async Task SetStock()
    {
        if (jsClass is not null)
        {
            stockSymbol = 
                $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
            price = r.Next(1, 101);
            var interopResult = await jsClass.TickerChanged(stockSymbol, price);
            result = $"Result of TickerChanged call for {stockSymbol} at " +
                $"{price.ToString("c")}: {interopResult}";
        }
    }

    public void Dispose() => jsClass?.Dispose();
}

Szenarios der dynamischen Inhaltsgenerierung

Verwenden Sie das -Attribut für die dynamische Inhaltsgenerierung mit BuildRenderTree:

[Inject]
IJSRuntime JS { get; set; }

Voarabrendering

Dieser Abschnitt gilt für Blazor Server und gehostete Blazor WebAssembly-Apps, die Razor-Komponenten vorab rendern. Informationen zum Prerendering finden Sie in Prerendering und Integrieren von -Komponenten in ASP.NET Core.

Während eine App vorab gerendert wird, sind bestimmte Aktionen nicht möglich, z. B. Aufrufe in JavaScript (JS).

Im folgenden Beispiel wird die Funktion setElementText1 im Element <head> platziert. Die Funktion wird mit JSRuntimeExtensions.InvokeVoidAsync aufgerufen und gibt keinen Wert zurück.

Hinweis

Allgemeine Anleitungen zum Speicherort von JS und unsere Empfehlungen für Produktions-Apps finden Sie unter ASP.NET Core Blazor JavaScript-Interoperabilität (JS-Interop).

<script>
  window.setElementText1 = (element, text) => element.innerText = text;
</script>

Warnung

Im vorangehenden Beispiel wird das Dokumentobjektmodell (DOM) direkt zu Demonstrationszwecken geändert. Das direkte Ändern des DOM mit JS wird in den meisten Szenarios nicht empfohlen, da JS die Änderungsnachverfolgung von Blazor beeinträchtigen kann. Weitere Informationen finden Sie unter JavaScript-Interoperabilität von in ASP.NET Core (JS-Interoperabilität).

Das OnAfterRender{Async}-Lebenszyklusereignis wird beim Voarabrendering auf dem Server nicht aufgerufen. Überschreiben Sie die OnAfterRender{Async}-Methode, um JS-Interopaufrufe zu verzögern, bis die Komponente nach dem Vorabendering gerendert und auf dem Client interaktiv ist.

Pages/PrerenderedInterop1.razor:

@page "/prerendered-interop-1"
@using Microsoft.JSInterop
@inject IJSRuntime JS

<div @ref="divElement">Text during render</div>

@code {
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JS.InvokeVoidAsync(
                "setElementText1", divElement, "Text after render");
        }
    }
}

Hinweis

Das Beispiel oben „verschmutzt“ den Client mit globalen Methoden. Einen besseren Ansatz für Produktions-Apps finden Sie unter JavaScript-Isolation in JavaScript-Modulen.

Beispiel:

export setElementText1 = (element, text) => element.innerText = text;

Die folgende Komponente veranschaulicht, wie JS-Interop als Teil der Initialisierungslogik einer Komponente auf eine Weise verwendet werden kann, die mit dem Prerendering kompatibel ist. Die Komponente zeigt, dass es möglich ist, in OnAfterRenderAsync ein Renderingupdate zu initiieren. Der Entwickler muss in diesem Szenario unbedingt vermeiden, eine Endlosschleife zu erstellen.

Im folgenden Beispiel wird die Funktion setElementText2 im Element <head> platziert. Die Funktion wird mit IJSRuntime.InvokeAsync aufgerufen und gibt einen Wert zurück.

Hinweis

Allgemeine Anleitungen zum Speicherort von JS und unsere Empfehlungen für Produktions-Apps finden Sie unter ASP.NET Core Blazor JavaScript-Interoperabilität (JS-Interop).

<script>
  window.setElementText2 = (element, text) => {
    element.innerText = text;
    return text;
  };
</script>

Warnung

Im vorangehenden Beispiel wird das Dokumentobjektmodell (DOM) direkt zu Demonstrationszwecken geändert. Das direkte Ändern des DOM mit JS wird in den meisten Szenarios nicht empfohlen, da JS die Änderungsnachverfolgung von Blazor beeinträchtigen kann. Weitere Informationen finden Sie unter JavaScript-Interoperabilität von in ASP.NET Core (JS-Interoperabilität).

Wenn JSRuntime.InvokeAsync aufgerufen wird, wird ElementReference nur in OnAfterRenderAsync und nicht in einer früheren Lebenszyklusmethode verwendet, da es kein JS-Element gibt, bis die Komponente gerendert wird.

StateHasChanged wird aufgerufen, um die Komponente mit dem neuen Zustand, der vom JS-Interop-Aufruf abgerufen wurde, erneut zu rendern. Weitere Informationen erhalten Sie unter Razor-Komponentenrendering in ASP.NET Core. Der Code erstellt keine Endlosschleife, da StateHasChanged nur aufgerufen wird, wenn datanull ist.

Pages/PrerenderedInterop2.razor:

@page "/prerendered-interop-2"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<p>
    Get value via JS interop call:
    <strong id="val-get-by-interop">@(data ?? "No value yet")</strong>
</p>

<p>
    Set value via JS interop call:
</p>

<div id="val-set-by-interop" @ref="divElement"></div>

@code {
    private string? data;
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && data == null)
        {
            data = await JS.InvokeAsync<string>(
                "setElementText2", divElement, "Hello from interop call!");

            StateHasChanged();
        }
    }
}

Hinweis

Das Beispiel oben „verschmutzt“ den Client mit globalen Methoden. Einen besseren Ansatz für Produktions-Apps finden Sie unter JavaScript-Isolation in JavaScript-Modulen.

Beispiel:

export setElementText2 = (element, text) => {
  element.innerText = text;
  return text;
};

Synchrones JS-Interop in Blazor WebAssembly-Apps

Dieser Abschnitt gilt nur für Blazor WebAssembly-Apps.

JS-Interop-Aufrufe sind standardmäßig asynchron, und zwar unabhängig davon, ob der aufgerufene Code synchron oder asynchron ist. Aufrufe sind standardmäßig asynchron, um sicherzustellen, dass Komponenten mit beiden Blazor-Hostingmodellen (Blazor Serverund Blazor WebAssembly) kompatibel sind. Für Blazor Server müssen alle JS-Interopaufrufe asynchron sein, weil sie über eine Netzwerkverbindung gesendet werden.

Wenn Sie sicher sind, dass Ihre App nur mit Blazor WebAssembly ausgeführt wird, können Sie synchrone JS-Interopaufrufe durchführen. Dies ist mit etwas weniger Mehraufwand verbunden als asynchrone Aufrufe und kann zu weniger Renderingzyklen führen, da es keinen Zwischenzustand gibt, während auf die Ergebnisse gewartet wird.

Um einen synchronen Aufruf von .NET zu JavaScript in einer Blazor WebAssembly-App auszuführen, wandeln Sie IJSRuntime in IJSInProcessRuntime um, um den JS-Interopaufruf auszuführen:

@inject IJSRuntime JS

...

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

Wenn Sie in Blazor WebAssembly-Apps mit ASP.NET Core 5.0 oder höher mit IJSObjectReference arbeiten, können Sie IJSInProcessObjectReference stattdessen synchron verwenden. IJSInProcessObjectReference implementiert IAsyncDisposable/IDisposable und sollte für die Garbage Collection verworfen werden, um Arbeitsspeicherverlust zu verhindern, wie das folgende Beispiel veranschaulicht:

...

@inject IJSRuntime JS
@implements IAsyncDisposable

...

@code {
    ...
    private IJSInProcessObjectReference? module;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSInProcessObjectReference>("import", 
            "./scripts.js");
        }
    }

    ...

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

Speicherort von JavaScipt

Laden Sie JavaScript-Code (JS) mithilfe einer der Vorgehensweisen, die im Artikel JS beschrieben werden:

Informationen zum Isolieren von Skripts in -Modulen finden Sie im Abschnitt zur JavaScript-Isolation in JavaScript-Modulen.

Warnung

Platzieren Sie kein <script>-Tag in einer Komponentendatei (.razor), weil das <script>-Tag nicht dynamisch aktualisiert werden kann.

JavaScript-Isolierung in JavaScript-Modulen

Blazor aktiviert die JavaScript-Isolation (JS) in Blazor (JS). Das Laden von JavaScript-Modulen funktioniert in Blazor genauso wie bei anderen Arten von Web-Apps, und Sie können die Definition von Modulen in Ihrer App anpassen. Eine Anleitung zur Verwendung von JavaScript-Modulen finden Sie in den MDN Web Docs: JavaScript modules.

JS-Isolierung bietet die folgenden Vorteile:

  • Importiertes JS verschmutzt nicht mehr den globalen Namespace.
  • Consumer einer Bibliothek und Komponenten müssen nicht das zugehörige JS importieren.

Der dynamische Import mit dem import()-Operator wird mit ASP.NET Core und Blazor unterstützt:

if ({CONDITION}) import("/additionalModule.js");

Im vorherigen Beispiel steht der Platzhalter {CONDITION} für eine bedingte Überprüfung, mit der festgestellt wird, ob das Modul geladen werden soll.

Informationen zur Browserkompatibilität finden Sie unter Can I use: JavaScript modules: dynamic import.

Das folgende JS-Modul exportiert beispielsweise eine JS-Funktion zum Anzeigen eines JS. Platzieren Sie den folgenden JS-Code in einer externen JS-Datei.

wwwroot/scripts.js:

export function showPrompt(message) {
  return prompt(message, 'Type anything here');
}

Fügen Sie das obige JS-Modul als statisches Webobjekt zu einer App oder Klassenbibliothek im wwwroot-Ordner hinzu, und importieren Sie das Modul dann in den .NET-Code, indem Sie InvokeAsync für die IJSRuntime-Instanz aufrufen.

IJSRuntime importiert das Modul als IJSObjectReference, das einen Verweis auf ein JS-Objekt aus .NET-Code darstellt. Verwenden Sie IJSObjectReference, um exportierte JS-Funktionen aus dem Modul aufzurufen.

Pages/CallJsExample6.razor:

@page "/call-js-example-6"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>Call JS Example 6</h1>

<p>
    <button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>

<p>
    @result
</p>

@code {
    private IJSObjectReference? module;
    private string? result;

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

    private async Task TriggerPrompt()
    {
        result = await Prompt("Provide some text");
    }

    public async ValueTask<string?> Prompt(string message) =>
        module is not null ? 
            await module.InvokeAsync<string>("showPrompt", message) : null;

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

Im vorherigen Beispiel:

  • Per Konvention handelt es sich beim import-Bezeichner um einen besonderen Bezeichner, der spezifisch zum Importieren eines JS-Moduls verwendet wird.
  • Geben Sie die externe JS-Datei des Moduls mithilfe des stabilen Pfads des statischen Webobjekts an: ./{SCRIPT PATH AND FILE NAME (.js)}. Dabei gilt Folgendes:
    • Das Pfadsegment für das aktuelle Verzeichnis (./) ist erforderlich, damit der korrekte Pfad zu statischen Objekten in der JS-Datei erstellt werden kann.
    • Der Platzhalter {SCRIPT PATH AND FILE NAME (.js)} entspricht dem Pfad und Dateinamen unter wwwroot.
  • Dieser gibt die IJSObjectReference für die IJSObjectReference in IAsyncDisposable.DisposeAsync zurück.

Das dynamische Importieren eines Moduls erfordert eine Netzwerkanforderung, sodass es nur asynchron durch Aufrufen von InvokeAsync erreicht werden kann.

IJSInProcessObjectReference steht für einen Verweis auf ein JS-Objekt, dessen Funktionen synchron in Blazor WebAssembly-Apps aufgerufen werden können. Weitere Informationen finden Sie im Abschnitt Synchrones JS-Interop in Blazor WebAssembly-Apps.

Hinweis

Wenn die externe JS-Datei von einer JS bereitgestellt wird, geben Sie die JS-Datei des Moduls mithilfe des stabilen Pfads des statischen Webobjekts an: ./_content/{PACKAGE ID}/{SCRIPT PATH AND FILE NAME (.js)}:

  • Das Pfadsegment für das aktuelle Verzeichnis (./) ist erforderlich, damit der korrekte Pfad zu statischen Objekten in der JS-Datei erstellt werden kann.
  • Der Platzhalter {PACKAGE ID} ist diePaket-IDder Bibliothek. Die Paket-ID wird standardmäßig auf den Assemblynamen des Projekts festgelegt, wenn <PackageId> nicht in der Projektdatei angegeben ist. Im folgenden Beispiel lautet der Assemblyname der Bibliothek ComponentLibrary, und die Projektdatei der Bibliothek gibt <PackageId> nicht an.
  • Der Platzhalter {SCRIPT PATH AND FILE NAME (.js)} entspricht dem Pfad und Dateinamen unter wwwroot. Im folgenden Beispiel wird die externe JS-Datei (script.js) im wwwroot-Ordner der Klassenbibliothek platziert.
var module = await js.InvokeAsync<IJSObjectReference>(
    "import", "./_content/ComponentLibrary/scripts.js");

Weitere Informationen finden Sie unter Nutzen von ASP.NET Core -Komponenten über eine Razor-Klassenbibliothek (RCL).

Erfassen von Verweisen auf Elemente

Einige JavaScript-Interop-Szenarios (JS) erfordern Verweise auf HTML-Elemente. Beispielsweise erfordert eine Benutzeroberflächenbibliothek möglicherweise einen Elementverweis für die Initialisierung, oder Sie müssen befehlsähnliche APIs für ein Element aufrufen, z. B. click oder play.

Erfassen Sie mithilfe des folgenden Ansatzes Verweise auf HTML-Elemente in einer Komponente:

  • Fügen Sie ein @ref-Attribut zum HTML-Element hinzu.
  • Definieren Sie ein Feld vom Typ ElementReference, dessen Name mit dem Wert des @ref-Attributs übereinstimmt.

Im folgenden Beispiel wird das Erfassen eines Verweises auf das <input>-Element username veranschaulicht:

<input @ref="username" ... />

@code {
    private ElementReference username;
}

Warnung

Verwenden Sie Elementverweise nur, um die Inhalte eines leeren Elements zu ändern, das nicht mit Blazor interagiert. Dieses Szenario ist nützlich, wenn eine Drittanbieter-API Inhalt für das Element bereitstellt. Da Blazor nicht mit dem Element interagiert, kann kein Konflikt zwischen der Darstellung von Blazor des Elements und dem DOM (Dokumentobjektmodell) auftreten.

Im folgenden Beispiel ist es riskant, die Inhalte der unsortierten Liste () zu ändern, weil Blazor mit dem DOM interagiert, um die Listenelemente (<li>) dieses Elements anhand des Todos-Objekts aufzufüllen:

<ul @ref="MyList">
    @foreach (var item in Todos)
    {
        <li>@item.Text</li>
    }
</ul>

Wenn ein JS-Interop-Aufruf die Inhalte des MyList-Elements ändert und Blazor versucht, Diff-Algorithmen auf das Element anzuwenden, stimmen diese nicht mit dem DOM überein.

Weitere Informationen finden Sie unter JavaScript-Interoperabilität von in ASP.NET Core (JS-Interoperabilität).

Eine ElementReference-Struktur wird über einen JS-Interop-Aufruf an den JS-Code übergeben. Der JS-Code empfängt eine HTMLElement-Instanz, die er mit normalen DOM-APIs verwenden kann. Im folgenden Code wird beispielsweise eine .NET-Erweiterungsmethode (TriggerClickEvent) definiert, die das Senden eines Mausklicks an ein Element ermöglicht.

Die JS-Funktion clickElement erstellt ein click-Ereignis an das übergebene HTML-Element (element):

window.interopFunctions = {
  clickElement : function (element) {
    element.click();
  }
}

Verwenden Sie JSRuntimeExtensions.InvokeVoidAsync, um eine JS-Funktion aufzurufen, die keinen Wert zurückgibt. Der folgende Code löst ein clientseitiges click-Ereignis aus, indem die vorherige JS-Funktion mit der erfassten ElementReference-Struktur aufgerufen wird:

@inject IJSRuntime JS

<button @ref="exampleButton">Example Button</button>

<button @onclick="TriggerClick">
    Trigger click event on <code>Example Button</code>
</button>

@code {
    private ElementReference exampleButton;

    public async Task TriggerClick()
    {
        await JS.InvokeVoidAsync(
            "interopFunctions.clickElement", exampleButton);
    }
}

Erstellen Sie eine statische Erweiterungsmethode, die die IJSRuntime-Instanz empfängt, um eine Erweiterungsmethode zu verwenden:

public static async Task TriggerClickEvent(this ElementReference elementRef, 
    IJSRuntime js)
{
    await js.InvokeVoidAsync("interopFunctions.clickElement", elementRef);
}

Die clickElement-Methode wird direkt für das Objekt aufgerufen. Im folgenden Beispiel wird davon ausgegangen, dass die TriggerClickEvent-Methode vom JsInteropClasses-Namespace verfügbar ist:

@inject IJSRuntime JS
@using JsInteropClasses

<button @ref="exampleButton">Example Button</button>

<button @onclick="TriggerClick">
    Trigger click event on <code>Example Button</code>
</button>

@code {
    private ElementReference exampleButton;

    public async Task TriggerClick()
    {
        await exampleButton.TriggerClickEvent(JS);
    }
}

Wichtig

Die exampleButton-Variable wird erst aufgefüllt, nachdem die Komponente gerendert wurde. Wenn eine aufgefüllte ElementReference-Struktur an JS-Code übergeben wird, empfängt der JS-Code einen null-Wert. Sie können Elementverweise bearbeiten, nachdem die Komponente das Rendering abgeschlossen hat, indem Sie die Lebenszyklusmethoden oder OnAfterRender verwenden.

Verwenden Sie ValueTask<TResult>, wenn Sie mit generischen Typen arbeiten und einen Wert zurückgeben:

public static ValueTask<T> GenericMethod<T>(this ElementReference elementRef, 
    IJSRuntime js)
{
    return js.InvokeAsync<T>("{JAVASCRIPT FUNCTION}", elementRef);
}

Der Platzhalter {JAVASCRIPT FUNCTION} ist der Bezeichner für die JS-Funktion.

GenericMethod wird direkt für das Objekt mit einem Typ aufgerufen. Im folgenden Beispiel wird davon ausgegangen, dass die GenericMethod-Methode vom JsInteropClasses-Namespace verfügbar ist:

@inject IJSRuntime JS
@using JsInteropClasses

<input @ref="username" />

<button @onclick="OnClickMethod">Do something generic</button>

<p>
    returnValue: @returnValue
</p>

@code {
    private ElementReference username;
    private string? returnValue;

    private async Task OnClickMethod()
    {
        returnValue = await username.GenericMethod<string>(JS);
    }
}

Komponentenübergreifende Verweiselemente

Ein ElementReference kann aus folgenden Gründen nicht zwischen Komponenten übermittelt werden:

Damit eine übergeordnete Komponente einen Elementverweis anderen Komponenten zur Verfügung stellen kann, kann die übergeordnete Komponente:

  • zulassen, dass untergeordnete Komponenten Rückrufe registrieren.
  • die registrierten Rückrufe während des OnAfterRender-Ereignisses mit dem übergebenen Elementverweis aufrufen. Dieser Ansatz ermöglicht untergeordneten Komponenten indirekt die Interaktion mit dem Elementverweis des übergeordneten Elements.
<style>
    .red { color: red }
</style>
<script>
  function setElementClass(element, className) {
    var myElement = element;
    myElement.classList.add(className);
  }
</script>

Hinweis

Allgemeine Anleitungen zum Speicherort von JS und unsere Empfehlungen für Produktions-Apps finden Sie unter JavaScript-Interoperabilität von Blazor in ASP.NET Core (JS-Interoperabilität).

Pages/CallJsExample7.razor (übergeordnete Komponente):

@page "/call-js-example-7"

<h1>Call JS Example 7</h1>

<h2 @ref="title">Hello, world!</h2>

Welcome to your new app.

<SurveyPrompt Parent="@this" Title="How is Blazor working for you?" />

Pages/CallJsExample7.razor.cs:

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;

namespace BlazorSample.Pages;

public partial class CallJsExample7 : 
    ComponentBase, IObservable<ElementReference>, IDisposable
{
    private bool disposing;
    private IList<IObserver<ElementReference>> subscriptions = 
        new List<IObserver<ElementReference>>();
    private ElementReference title;

    protected override void OnAfterRender(bool firstRender)
    {
        base.OnAfterRender(firstRender);

        foreach (var subscription in subscriptions)
        {
            try
            {
                subscription.OnNext(title);
            }
            catch (Exception)
            {
                throw;
            }
        }
    }

    public void Dispose()
    {
        disposing = true;

        foreach (var subscription in subscriptions)
        {
            try
            {
                subscription.OnCompleted();
            }
            catch (Exception)
            {
            }
        }

        subscriptions.Clear();
    }

    public IDisposable Subscribe(IObserver<ElementReference> observer)
    {
        if (disposing)
        {
            throw new InvalidOperationException("Parent being disposed");
        }

        subscriptions.Add(observer);

        return new Subscription(observer, this);
    }

    private class Subscription : IDisposable
    {
        public Subscription(IObserver<ElementReference> observer, 
            CallJsExample7 self)
        {
            Observer = observer;
            Self = self;
        }

        public IObserver<ElementReference> Observer { get; }
        public CallJsExample7 Self { get; }

        public void Dispose()
        {
            Self.subscriptions.Remove(Observer);
        }
    }
}

Im vorherigen Beispiel ist BlazorSample der Namespace der App mit Komponenten im Ordner Pages. Wenn Sie den Code lokal testen möchten, müssen Sie den Namespace ändern.

Shared/SurveyPrompt.razor (untergeordnete Komponente):

<div class="alert alert-secondary mt-4">
    <span class="oi oi-pencil me-2" aria-hidden="true"></span>
    <strong>@Title</strong>

    <span class="text-nowrap">
        Please take our
        <a target="_blank" class="font-weight-bold link-dark" href="https://go.microsoft.com/fwlink/?linkid=2186157">brief survey</a>
    </span>
    and tell us what you think.
</div>

@code {
    // Demonstrates how a parent component can supply parameters
    [Parameter]
    public string? Title { get; set; }
}

Im vorherigen Beispiel ist BlazorSample der Namespace der App mit gemeinsamen Komponenten im Ordner Shared. Wenn Sie den Code lokal testen möchten, müssen Sie den Namespace ändern.

Festschreiben von JavaScript-Interop-Aufrufen

Dieser Abschnitt gilt hauptsächlich für -Apps, jedoch können Blazor WebAssembly-Apps auch JS-Interop-Timeouts festlegen, wenn die Bedingungen dies rechtfertigen.

In Blazor Server-Apps können JavaScript-Interop-Aufrufe (JS) aufgrund von Netzwerkfehlern fehlschlagen, weshalb sie als unzuverlässig betrachtet werden sollten. Blazor Server-Apps verwenden standardmäßig einen einminütigen Timeout für JS-Interop-Aufrufe. Wenn eine App ein engeres Timeout tolerieren kann, legen Sie das Timeout mit einem der folgenden Ansätze fest:

Legen Sie mit CircuitOptions.JSInteropDefaultCallTimeout ein globales Timeout in der Program.cs-Datei fest:

builder.Services.AddServerSideBlazor(
    options => options.JSInteropDefaultCallTimeout = {TIMEOUT});

Der Platzhalter {TIMEOUT} ist ein TimeSpan (z. B. TimeSpan.FromSeconds(80)).

Legen Sie im Komponentencode ein Timeout pro Aufruf fest. Das festgelegte Timeout überschreibt das globale Timeout, das von JSInteropDefaultCallTimeout festgelegt wird:

var result = await JS.InvokeAsync<string>("{ID}", {TIMEOUT}, new[] { "Arg1" });

Im vorherigen Beispiel:

  • Der Platzhalter {TIMEOUT} ist ein TimeSpan (z. B. TimeSpan.FromSeconds(80)).
  • Der Platzhalter {ID} ist der Bezeichner für die aufzurufende Funktion. Der Wert someScope.someFunction ruft beispielsweise die Funktion window.someScope.someFunction auf.

Obwohl Netzwerkfehler in Blazor Server-Apps eine gängige Ursache von JS-Interop-Fehlern sind, können Timeouts pro Aufruf für JS-Interop-Aufrufe in Blazor WebAssembly-Apps festgelegt werden. Obwohl keine SignalR-Leitung in einer Blazor WebAssembly-App vorhanden ist, können JS-Interop-Aufrufe aus anderen Gründen fehlschlagen, die sich auf Blazor WebAssembly-Apps beziehen.

Weitere Informationen zur Ressourcenerschöpfung finden Sie unter Leitfaden zur Bedrohungsabwehr für in ASP.NET Core.

Vermeiden von Objektzirkelbezügen

Objekte, die Zirkelbezüge enthalten, können auf dem Client für folgende Vorgänge nicht serialisiert werden:

  • .NET-Methodenaufrufe.
  • JavaScript-Methodenaufrufe von C#, wenn der Rückgabetyp Zirkelbezüge enthält.

JavaScript-Bibliotheken zum Rendern der Benutzeroberfläche

Möglicherweise möchten Sie manchmal JavaScript-Bibliotheken (JS) verwenden, die sichtbare Benutzeroberflächenelemente im DOM erzeugen. Auf den ersten Blick scheint dies schwierig zu sein, da das Vergleichssystem von Blazor darauf beruht, die Kontrolle über die Struktur von DOM-Elementen zu haben. Außerdem treten Fehler auf, wenn externer Code die DOM-Struktur bearbeitet, was den Mechanismus für die Diff-Anwendung unmöglich macht. Dabei handelt es sich um keine Blazor-spezifische Einschränkung. Die gleiche Herausforderung stellt sich bei jedem anderen Diff-basierten Benutzeroberflächenframework.

Glücklicherweise ist es einfach, extern erstellte Benutzeroberflächenelemente innerhalb der Oberfläche einer Razor-Komponente zuverlässig einzubetten. Die hierfür empfohlene Methode besteht darin, den Code der Komponente (.razor-Datei) ein leeres Element erzeugen zu lassen. Im Vergleichssystem von Blazor ist das Element immer leer, der Renderer durchläuft das Element also nicht rekursiv, sondern berücksichtigt die Inhalte gar nicht. Dies führt dazu, dass das Auffüllen des Elements mit zufälligem extern verwalteten Inhalt sicher ist.

Das folgende Beispiel veranschaulicht das Konzept. Interagieren Sie mithilfe von JS außerhalb von Blazor mit unmanagedElement, wenn firstRender in der if-Anweisung den Wert true aufweist. Rufen Sie beispielsweise eine externe JS-Bibliothek auf, um das Element aufzufüllen. Blazor ignoriert die Inhalte des Elements, bis die Komponente entfernt wurde. Wenn die Komponente entfernt wird, wird die gesamte DOM-Unterstruktur der Komponente ebenfalls entfernt.

<h1>Hello! This is a Razor component rendered at @DateTime.Now</h1>

<div @ref="unmanagedElement"></div>

@code {
    private ElementReference unmanagedElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            ...
        }
    }
}

Betrachten Sie das folgende Beispiel, in dem eine interaktive Karte mithilfe der Open-Source-Mapbox-APIs gerendert wird.

Das folgende JS-Modul wird in der App platziert oder über eine Razor-Klassenbibliothek zur Verfügung gestellt.

Hinweis

Rufen Sie zum Erstellen der Mapbox-Karte ein Zugriffstoken von der Mapbox-Anmeldung ab, und geben Sie dieses an der Stelle an, an der sich im folgenden Code befindet.

wwwroot/mapComponent.js:

import 'https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js';

mapboxgl.accessToken = '{ACCESS TOKEN}';

export function addMapToElement(element) {
  return new mapboxgl.Map({
    container: element,
    style: 'mapbox://styles/mapbox/streets-v11',
    center: [-74.5, 40],
    zoom: 9
  });
}

export function setMapCenter(map, latitude, longitude) {
  map.setCenter([longitude, latitude]);
}

Fügen Sie das folgende Formatvorlagentag zur HTML-Hostseite hinzu, um die korrekte Formatierung zu erzielen.

Fügen Sie das folgende Element <link> zum Markup des Elements <head> hinzu (Speicherort des <head>-Inhalts):

<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css" 
    rel="stylesheet" />

Pages/CallJsExample8.razor:

@page "/call-js-example-8"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>Call JS Example 8</h1>

<div @ref="mapElement" style='width:400px;height:300px'></div>

<button @onclick="() => ShowAsync(51.454514, -2.587910)">Show Bristol, UK</button>
<button @onclick="() => ShowAsync(35.6762, 139.6503)">Show Tokyo, Japan</button>

@code
{
    private ElementReference mapElement;
    private IJSObjectReference? mapModule;
    private IJSObjectReference? mapInstance;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            mapModule = await JS.InvokeAsync<IJSObjectReference>(
                "import", "./mapComponent.js");
            mapInstance = await mapModule.InvokeAsync<IJSObjectReference>(
                "addMapToElement", mapElement);
        }
    }

    private async Task ShowAsync(double latitude, double longitude)
    {
        if (mapModule is not null && mapInstance is not null)
        {
            await mapModule.InvokeVoidAsync("setMapCenter", mapInstance, 
                latitude, longitude).AsTask();
        }
    }

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (mapInstance is not null)
        {
            await mapInstance.DisposeAsync();
        }

        if (mapModule is not null)
        {
            await mapModule.DisposeAsync();
        }
    }
}

Im obigen Beispiel wird eine interaktive Kartenbenutzeroberfläche erzeugt. Der Benutzer verfügt über folgende Möglichkeiten:

  • Ziehen der Karte zum Scrollen oder Zoomen
  • Klicken von Schaltflächen zum Springen zu vordefinierten Standorten

Mapbox-Straßenkarte von Tokio in Japan, mit Schaltflächen, über die Bristol im Vereinigten Königreich und Tokio in Japan ausgewählt werden können

Im vorherigen Beispiel:

  • Für Blazor gilt, dass <div> mit @ref="mapElement" leer bleibt. Das mapbox-gl.js-Skript kann das Element sicher auffüllen und dessen Inhalte ändern. Sie können diese Vorgehensweise mit jeder beliebigen JS-Bibliothek verwenden, die eine Benutzeroberfläche rendert. Sie können Komponenten eines JS-SPA-Frameworks eines Drittanbieters in Razor-Komponenten einbetten, solange diese nicht versuchen, andere Teile der Seite zu ändern. Es ist nicht sicher, wenn externer -Code Elemente ändert, die Blazor nicht als leer betrachtet.
  • Bei Verwendung dieses Ansatzes sollten Sie die Regeln im Hinterkopf behalten, wie Blazor DOM-Elemente beibehält oder löscht. Die Komponente verarbeitet Schaltflächen-Klickereignisse auf sichere weise und aktualisiert die vorhandene Karteninstanz, weil DOM-Elemente standardmäßig beibehalten werden, wenn dies möglich ist. Wenn Sie eine Liste von Kartenelementen innerhalb einer @foreach-Schleife rendern, sollten Sie @key verwenden, um für die Beibehaltung der Komponenteninstanzen zu sorgen. Andernfalls könnten Änderungen der Listendaten dazu führen, dass Komponenteninstanzen den Status vorheriger Instanzen auf nicht gewünschte Weise beibehalten. Weitere Informationen finden Sie unter using @key to preserve elements and components.
  • Das Beispiel kapselt JS-Logik und -Abhängigkeiten in einem ES6-Modul und lädt das Modul dynamisch mithilfe des import-Bezeichners. Weitere Informationen finden Sie unter JavaScript-Isolation in JavaScript-Modulen.

Bytearrayunterstützung

Blazor unterstützt optimiertes Bytearray-JavaScript (JS)-Interop, mit dem sich das Codieren/Decodieren von Bytearrays in Base64 vermeiden lässt. Im folgenden Beispiel wird JS-Interop verwendet, um ein Bytearray an JavaScript zu übergeben.

Stellen Sie eine receiveByteArrayJS-Funktion bereit. Die Funktion wird mit InvokeVoidAsync aufgerufen und gibt keinen Wert zurück:

<script>
  window.receiveByteArray = (bytes) => {
    let utf8decoder = new TextDecoder();
    let str = utf8decoder.decode(bytes);
    return str;
  };
</script>

Hinweis

Allgemeine Anleitungen zum Speicherort von JS und unsere Empfehlungen für Produktions-Apps finden Sie unter JavaScript-Interoperabilität von Blazor in ASP.NET Core (JS-Interoperabilität).

Pages/CallJsExample9.razor:

@page "/call-js-example-9"
@inject IJSRuntime JS

<h1>Call JS Example 9</h1>

<p>
    <button @onclick="SendByteArray">Send Bytes</button>
</p>

<p>
    @result
</p>

<p>
    Quote &copy;2005 <a href="https://www.uphe.com">Universal Pictures</a>:
    <a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
    <a href="https://www.imdb.com/name/nm0821612/">Jewel Staite on IMDB</a>
</p>

@code {
    private string? result;

    private async Task SendByteArray()
    {
        var bytes = new byte[] { 0x45, 0x76, 0x65, 0x72, 0x79, 0x74, 0x68, 0x69,
            0x6e, 0x67, 0x27, 0x73, 0x20, 0x73, 0x68, 0x69, 0x6e, 0x79, 0x2c,
            0x20, 0x43, 0x61, 0x70, 0x74, 0x69, 0x61, 0x6e, 0x2e, 0x20, 0x4e,
            0x6f, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x72, 0x65, 0x74, 0x2e };

        result = await JS.InvokeAsync<string>("receiveByteArray", bytes);
    }
}

Informationen zur Verwendung eines Bytearrays beim Aufruf von .NET aus JavaScript finden Sie unter Aufrufen von .NET-Methoden über JavaScript-Funktionen in in ASP.NET Core.

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

Dieser Abschnitt gilt nur für Blazor Server-Apps. In Blazor WebAssembly gibt das Framework keine Einschränkungen hinsichtlich der Größe von JavaScript-Interop-Eingaben und -Ausgaben (JS) vor.

In Blazor Server wird die Größe von JS-Interop-Aufrufen durch die maximale SignalR-Nachrichtengröße eingeschränkt, die für Hubmethoden zulässig sind. Dies wird von HubOptions.MaximumReceiveMessageSize erzwungen (Standardwert: 32 KB). SignalR-Nachrichten von JS an .NET, die größer als MaximumReceiveMessageSize sich, lösen einen Fehler aus. Das Framework beinhaltet keine Beschränkungen hinsichtlich der Größe einer SignalR-Nachricht vom Hub an einen Client.

Wenn die SignalR-Protokollierung nicht auf SignalR oder Überwachung festgelegt ist, wird ein Fehler zur Nachrichtengröße nur in der Konsole für Entwicklertools des Browsers angezeigt:

Fehler: Connection disconnected with error „Error: Server returned an error on close: Connection closed with an error.“ (Die Verbindung wurde durch den folgenden Fehler getrennt: „Der Server hat beim Schließen einen Fehler zurückgegeben: Die Verbindung wurde durch einen Fehler beendet.“)

Wenn die serverseitige Protokollierung in auf Debuggen oder Überwachung festgelegt ist, tritt bei der serverseitigen Protokollierung eine -Ausnahme für einen Fehler in Bezug auf die Nachrichtengröße auf.

appsettings.Development.json:

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "Microsoft.AspNetCore.SignalR": "Debug"
    }
  }
}

Error:

System.IO.InvalidDataException: The maximum message size of 32768B was exceeded. The message size can be configured in AddHubOptions. (System.IO.InvalidDataException: Die maximale Nachrichtengröße von 32.768 Byte wurde überschritten. Die Nachrichtengröße kann unter AddHubOptions konfiguriert werden.)

Erhöhen Sie den Grenzwert, indem Sie MaximumReceiveMessageSize in Program.cs festlegen. Im folgenden Beispiel wird die maximale Größe eingehender Nachrichten auf 64 KB festgelegt:

builder.Services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

Wenn Sie den Grenzwert für die Größe eingehender Nachrichten von SignalR erhöhen, verbrauchen Sie auch mehr Serverressourcen und setzen den Server einem erhöhten Risiko durch böswillige Benutzer aus. Darüber hinaus kann das Lesen sehr großer Inhalte in den Arbeitsspeicher als Zeichenfolgen oder Bytearrays zu Zuordnungen führen, die vom Garbage Collector nur schlecht verarbeitet werden können. Dies kann zu zusätzlichen Leistungseinbußen führen.

Beachten Sie die folgenden Anleitungen, wenn Sie Code zum Übertragen großer Datenmengen zwischen JS und Blazor in Blazor Server-Apps entwickeln:

  • Nutzen Sie zur Übertragung von Daten, deren Größe die Begrenzung für eingehende SignalR-Nachrichten überschreitet, die Unterstützung für native Streaming-Interop-Aufrufe.
  • Allgemeine Tipps:
    • Ordnen Sie in JS- und C#-Code keine großen Objekte zu.
    • Geben Sie belegten Arbeitsspeicher frei, wenn der Prozess beendet oder abgebrochen wird.
    • Erzwingen Sie die folgenden zusätzlichen Anforderungen aus Sicherheitsgründen:
      • Deklarieren Sie die maximale Datei- oder Datengröße, die übermittelt werden kann.
      • Deklarieren Sie die minimale Uploadrate vom Client an den Server.
    • Nachdem die Daten vom Server empfangen wurden, ist mit den Daten Folgendes möglich:
      • Sie können temporär in einem Speicherpuffer gespeichert werden, bis alle Segmente gesammelt wurden.
      • Sie können sofort verarbeitet werden. Beispielsweise können die Daten sofort in einer Datenbank gespeichert oder auf den Datenträger geschrieben werden, wenn die einzelnen Segmente empfangen werden.

Streamen von .NET zu JavaScript

Blazor unterstützt das direkte Streamen von Daten von .NET an JavaScript. Streams werden mithilfe von DotNetStreamReference erstellt.

DotNetStreamReference stellt einen .NET-Stream dar und verwendet die folgenden Parameter:

  • stream: Der an JavaScript gesendete Stream
  • leaveOpen: Legt fest, ob der Stream nach der Übertragung geöffnet bleibt. Wenn kein Wert angegeben wird, wird leaveOpen standardmäßig auf false festgelegt.

Verwenden Sie in JavaScript einen Arraypuffer oder einen lesbaren Stream, um die Daten zu empfangen:

  • Unter Verwendung von ArrayBuffer:

    async function streamToJavaScript(streamRef) {
      const data = await streamRef.arrayBuffer();
    }
    
  • Unter Verwendung von ReadableStream:

    async function streamToJavaScript(streamRef) {
      const stream = await streamRef.stream();
    }
    

In C#-Code:

using var streamRef = new DotNetStreamReference(stream: {STREAM}, leaveOpen: false);
await JS.InvokeVoidAsync("streamToJavaScript", streamRef);

Im vorherigen Beispiel:

  • Der Platzhalter {STREAM} stellt den an JavaScript gesendeten Stream dar.
  • JS ist eine eingefügte IJSRuntime-Instanz.

Unter Aufrufen von .NET-Methoden über JavaScript-Funktionen in in ASP.NET Core wird der umgekehrte Vorgang behandelt, nämlich das Streamen aus JavaScript in .NET.

Unter -Dateidownloads in ASP.NET Core wird das Herunterladen einer Datei in Blazor behandelt.

Abfangen von JavaScript-Ausnahmen

Zum Abfangen von JS-Ausnahmen schließen Sie JS-Interop in einen JS ein, und fangen Sie eine JSException ab.

Im folgenden Beispiel ist die JS-Funktion nonFunction nicht vorhanden. Wenn die Funktion nicht gefunden wird, wird die JSException mit einer Message abgefangen, die den folgenden Fehler angibt:

Could not find 'nonFunction' ('nonFunction' was undefined).

Pages/CallJsExample11.razor:

@page "/call-js-example-11"
@inject IJSRuntime JS

<h1>Call JS Example 11</h1>

<p>
    <button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>

<p>
    @result
</p>

<p>
    @errorMessage
</p>

@code {
    private string? errorMessage;
    private string? result;

    private async Task CatchUndefinedJSFunction()
    {
        try
        {
            result = await JS.InvokeAsync<string>("nonFunction");
        }
        catch (JSException e)
        {
            errorMessage = $"Error Message: {e.Message}";
        }
    }
}

Abbrechen einer JavaScript-Funktion mit langer Ausführungszeit

Verwenden Sie einen JSAbortController mit einer CancellationTokenSource in der Komponente, um eine JavaScript-Funktion mit langer Ausführungszeit aus C#-Code abzubrechen.

Die folgende JSHelpers-Klasse enthält eine simulierte Funktion mit langer Ausführungszeit (longRunningFn), die kontinuierlich zählt, bis AbortController.signal angibt, dass AbortController.abort aufgerufen wurde. Die sleep-Funktion dient zu Demonstrationszwecken, um die langsame Ausführung der Funktion mit langer Ausführungszeit zu simulieren, und wäre im Produktionscode nicht vorhanden. Wenn eine Komponente stopFn aufruft, wird longRunningFn signalisiert, den Vorgang über die bedingte while-Schleifenüberprüfung für AbortSignal.aborted abzubrechen.

<script>
  class Helpers {
    static #controller = new AbortController();

    static async #sleep(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }

    static async longRunningFn() {
      var i = 0;
      while (!this.#controller.signal.aborted) {
        i++;
        console.log(`longRunningFn: ${i}`);
        await this.#sleep(1000);
      }
    }

    static stopFn() {
      this.#controller.abort();
      console.log('longRunningFn aborted!');
    }
  }

  window.Helpers = Helpers;
</script>

Hinweis

Allgemeine Anleitungen zum Speicherort von JS und unsere Empfehlungen für Produktions-Apps finden Sie unter JavaScript-Interoperabilität von Blazor in ASP.NET Core (JS-Interoperabilität).

Die folgende CallJsExample12-Komponente:

Pages/CallJsExample12.razor:

@page "/call-js-example-12"
@inject IJSRuntime JS

<h1>Cancel long-running JS interop</h1>

<p>
    <button @onclick="StartTask">Start Task</button>
    <button @onclick="CancelTask">Cancel Task</button>
</p>

@code {
    private CancellationTokenSource? cts;

    private async Task StartTask()
    {
        cts = new CancellationTokenSource();
        cts.Token.Register(() => JS.InvokeVoidAsync("Helpers.stopFn"));

        await JS.InvokeVoidAsync("Helpers.longRunningFn");
    }

    private void CancelTask()
    {
        cts?.Cancel();
    }

    public void Dispose()
    {
        cts?.Cancel();
        cts?.Dispose();
    }
}

Die Konsole mit den Entwicklertools eines Browsers gibt die Ausführung der JS-Funktion mit langer Ausführungsdauer an, nachdem die Schaltfläche Start Task ausgewählt und die Funktion abgebrochen wurde, nachdem die Schaltfläche Cancel Task ausgewählt wurde:

longRunningFn: 1
longRunningFn: 2
longRunningFn: 3
longRunningFn aborted!

JavaScript-Interoperabilität mit [JSImport]/[JSExport]

Dieser Abschnitt gilt für Blazor WebAssembly-Apps.

Als Alternative zur Interaktion mit JavaScript (JS) in Blazor WebAssembly-Apps, die den JS-Interopmechanismus von Blazor basierend auf der IJSRuntime-Schnittstelle verwenden, ist auch eine JS[JSImport]/[JSExport]-Interop-API für Apps mit .NET 7 oder höher als Zielversion verfügbar.

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

JavaScript-Interop-Aufrufe mit rückgängig gemachtem Marshallen

Dieser Abschnitt gilt für Blazor WebAssembly-Apps.

Interop-Aufrufe ohne Marshalling mit der Schnittstelle IJSUnmarshalledRuntime sind veraltet und sollten durch JavaScript-Interop-Aufrufe [JSImport]/[JSExport] ersetzt werden.

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

Löschen von Objektverweisen bei JavaScript-Interoperabilität

Alle Beispiele im Artikel zur JavaScript-Interoperabilität (JS) veranschaulichen typische Muster zum Löschen von Objekten:

Objektverweise bei JS-Interoperabilität werden als Zuordnung implementiert, die durch einen Bezeichner auf der Seite des JS-Interoperabilitätsaufrufs definiert ist, der den Verweis erstellt. Wenn die Objektlöschung von .NET oder JS aus initiiert wird, entfernt Blazor den Eintrag aus der Zuordnung, und das Objekt kann durch die Garbage Collection gelöscht werden, sofern kein anderer starker Verweis auf das Objekt vorhanden ist.

Sie sollten immer mindestens die auf .NET-Seite erstellten Objekte löschen, um Verluste im von .NET verwalteten Arbeitsspeicher zu vermeiden.

DOM-Bereinigungsvorgänge (Document Object Model, Dokumentobjektmodell) 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 das MutationObserver-Muster in JavaScript auf dem Client:

  • Die Komponente wurde möglicherweise nach Ausführung des Bereinigungscodes in Dispose{Async} aus dem DOM entfernt.
  • In einer Blazor Server-App wurde der Blazor-Renderer möglicherweise 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.

JavaScript-Interopaufrufe ohne Verbindung

Dieser Abschnitt gilt nur für Blazor Server-Apps.

JavaScript-Interopaufrufe (JS) können nicht ausgegeben werden, nachdem eine SignalR-Verbindung getrennt wurde. Ohne eine Verbindung während der Komponentenbereinigung oder zu jedem anderen Zeitpunkt, an dem keine Verbindung vorhanden ist, schlagen die folgenden Methodenaufrufe fehl und protokollieren eine Meldung, dass die Verbindung als JSDisconnectedException getrennt wurde:

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 für Komponbereinigung gilt:

  • Die Komponente implementiert IAsyncDisposable.
  • objInstance ist eine IJSObjectReference.
  • 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 (objInstance is not null)
        {
            await objInstance.DisposeAsync();
        }
    }
    catch (JSDisconnectedException)
    {
    }
}

Wenn Sie ihre eigenen JS-Objekte bereinigen oder anderen JS-Code auf dem Client ausführen müssen, nachdem eine Verbindung verloren gegangen ist, verwenden Sie das MutationObserver-Muster in JS auf 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:

Zusätzliche Ressourcen

Informationen zum Aufrufen von .NET-Methoden aus JS finden Sie unter JS.

IJSRuntime wird vom Blazor-Framework registriert. Fügen Sie für einen Aufruf an JS über .NET die IJSRuntime-Abstraktion ein, und rufen Sie eine der folgenden Methoden auf:

Für die oben genannten .NET-Methoden, die JS-Funktionen aufrufen, gilt Folgendes:

  • Der Funktionsbezeichner (String) ist relativ zum globalen Bereich (window). Der Bezeichner zum Aufrufen von window.someScope.someFunction lautet someScope.someFunction. Die Funktion muss nicht registriert werden, bevor sie aufgerufen wird.
  • Übergeben Sie eine beliebige Anzahl von serialisierbaren JSON-Argumenten in Object[] an eine JS-Funktion.
  • Das Abbruchstoken (CancellationToken) leitet eine Benachrichtigung weiter, dass Vorgänge abgebrochen werden sollen.
  • TimeSpan stellt ein Zeitlimit für einen JS-Vorgang dar.
  • Der Rückgabetyp TValue muss ebenfalls mit JSON serialisierbar sein. TValue sollte mit dem .NET-Typ übereinstimmen, der dem zurückgegebenen JSON-Typ am ehesten entspricht.
  • Ein JS Promise-Objekt wird für InvokeAsync-Methoden zurückgegeben. InvokeAsync entpackt das Promise-Objekt und gibt den Wert zurück, der vom Promise-Objekt erwartet wird.

Für Blazor-Apps mit aktiviertem Voarabrendering sind Aufrufe von JS während des Voarabrenderings nicht möglich. Weitere Informationen finden Sie im Abschnitt Voarabrendering.

Das folgende Beispiel basiert auf TextDecoder, dabei handelt es sich um einen auf JS basierenden Decoder. Im Beispiel wird veranschaulicht, wie eine JS-Funktion aus einer C#-Methode aufgerufen wird, die eine Anforderung aus Entwicklercode in eine vorhandene JS-API auslagert. Die JS-Funktion akzeptiert ein Bytearray von einer C#-Methode, decodiert das Array und gibt den Text zum Anzeigen an die Komponente zurück.

<script>
  window.convertArray = (win1251Array) => {
    var win1251decoder = new TextDecoder('windows-1251');
    var bytes = new Uint8Array(win1251Array);
    var decodedArray = win1251decoder.decode(bytes);
    console.log(decodedArray);
    return decodedArray;
  };
</script>

Hinweis

Allgemeine Anleitungen zum Speicherort von JS und unsere Empfehlungen für Produktions-Apps finden Sie unter JavaScript-Interoperabilität von Blazor in ASP.NET Core (JS-Interoperabilität).

Die folgende CallJsExample1-Komponente:

  • ruft die JS-Funktion convertArray mit InvokeAsync auf, wenn auf eine Schaltfläche geklickt wird (Convert Array).
  • Nachdem die JS-Funktion aufgerufen wurde, wird das übergebene Array in eine Zeichenfolge konvertiert. Die Zeichenfolge wird zur Anzeige an die Komponente zurückgegeben (text).

Pages/CallJsExample1.razor:

@page "/call-js-example-1"
@inject IJSRuntime JS

<h1>Call JS <code>convertArray</code> Function</h1>

<p>
    <button @onclick="ConvertArray">Convert Array</button>
</p>

<p>
    @text
</p>

<p>
    Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>: 
    <a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
    <a href="https://www.imdb.com/name/nm0472710/">David Krumholtz on IMDB</a>
</p>

@code {
    private MarkupString text;

    private uint[] quoteArray = 
        new uint[]
        {
            60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
            116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
            108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
            105, 118, 101, 114, 115, 101, 10, 10,
        };

    private async Task ConvertArray()
    {
        text = new(await JS.InvokeAsync<string>("convertArray", quoteArray));
    }
}

JavaScript-API auf Benutzergesten beschränkt

Dieser Abschnitt gilt nur für -Apps.

Einige JavaScript-APIs (JS) für Browser können nur im Zusammenhang mit einer Benutzergeste ausgeführt werden, z. B. mit JS. Diese APIs können nicht über den JS-Interop-Mechanismus in Blazor Server-Apps aufgerufen werden, da die Behandlung von Ereignissen auf der Benutzeroberfläche asynchron und im Allgemeinen nicht mehr im Kontext der Benutzergeste erfolgt. Die App muss das Ereignis auf der Benutzeroberfläche vollständig in JavaScript verarbeiten. Verwenden Sie daher onclick anstelle des Attributs Blazor der @onclick-Anweisung.

Aufrufen von JavaScript-Funktionen ohne Lesen eines zurückgegebenen Werts (InvokeVoidAsync)

Verwenden Sie InvokeVoidAsync in folgenden Fällen:

  • .NET ist nicht erforderlich, um das Ergebnis eines JavaScript-Aufrufs (JS) zu lesen.
  • JS-Funktionen geben JS oder undefined zurück.

Stellen Sie eine displayTickerAlert1JS-Funktion bereit. Die Funktion wird mit InvokeVoidAsync aufgerufen und gibt keinen Wert zurück:

<script>
  window.displayTickerAlert1 = (symbol, price) => {
    alert(`${symbol}: $${price}!`);
  };
</script>

Hinweis

Allgemeine Anleitungen zum Speicherort von JS und unsere Empfehlungen für Produktions-Apps finden Sie unter JavaScript-Interoperabilität von Blazor in ASP.NET Core (JS-Interoperabilität).

Beispiel (InvokeVoidAsync) für die .razor-Komponente

TickerChanged ruft die handleTickerChanged1-Methode in der folgenden CallJsExample2-Komponente auf.

Pages/CallJsExample2.razor:

@page "/call-js-example-2"
@inject IJSRuntime JS

<h1>Call JS Example 2</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new();
    private string? stockSymbol;
    private decimal price;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
    }
}

Beispiel (InvokeVoidAsync) für die .cs-Klasse

JsInteropClasses1.cs:

using Microsoft.JSInterop;

public class JsInteropClasses1 : IDisposable
{
    private readonly IJSRuntime js;

    public JsInteropClasses1(IJSRuntime js)
    {
        this.js = js;
    }

    public async ValueTask TickerChanged(string symbol, decimal price)
    {
        await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
    }

    public void Dispose()
    {
    }
}

TickerChanged ruft die handleTickerChanged1-Methode in der folgenden CallJsExample3-Komponente auf.

Pages/CallJsExample3.razor:

@page "/call-js-example-3"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call JS Example 3</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new();
    private string? stockSymbol;
    private decimal price;
    private JsInteropClasses1? jsClass;

    protected override void OnInitialized()
    {
        jsClass = new(JS);
    }

    private async Task SetStock()
    {
        if (jsClass is not null)
        {
            stockSymbol = 
                $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
            price = r.Next(1, 101);
            await jsClass.TickerChanged(stockSymbol, price);
        }
    }

    public void Dispose() => jsClass?.Dispose();
}

Aufrufen von JavaScript-Funktionen und Lesen eines zurückgegebenen Werts (InvokeAsync)

Verwenden Sie InvokeAsync, wenn .NET das Ergebnis eines JavaScript-Aufrufs (JS) lesen soll.

Stellen Sie eine displayTickerAlert2JS-Funktion bereit. Im folgenden Beispiel wird eine anzuzeigende Zeichenfolge vom Aufrufer zurückgegeben:

<script>
  window.displayTickerAlert2 = (symbol, price) => {
    if (price < 20) {
      alert(`${symbol}: $${price}!`);
      return "User alerted in the browser.";
    } else {
      return "User NOT alerted.";
    }
  };
</script>

Hinweis

Allgemeine Anleitungen zum Speicherort von JS und unsere Empfehlungen für Produktions-Apps finden Sie unter JavaScript-Interoperabilität von Blazor in ASP.NET Core (JS-Interoperabilität).

Beispiel (InvokeAsync) für die .razor-Komponente

TickerChanged ruft die handleTickerChanged2-Methode auf und zeigt die zurückgegebene Zeichenfolge in der folgenden CallJsExample4-Komponente an.

Pages/CallJsExample4.razor:

@page "/call-js-example-4"
@inject IJSRuntime JS

<h1>Call JS Example 4</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private Random r = new();
    private string? stockSymbol;
    private decimal price;
    private string? result;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        var interopResult = 
            await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
        result = $"Result of TickerChanged call for {stockSymbol} at " +
            $"{price.ToString("c")}: {interopResult}";
    }
}

Beispiel (InvokeAsync) für die .cs-Klasse

JsInteropClasses2.cs:

using Microsoft.JSInterop;

public class JsInteropClasses2 : IDisposable
{
    private readonly IJSRuntime js;

    public JsInteropClasses2(IJSRuntime js)
    {
        this.js = js;
    }

    public async ValueTask<string> TickerChanged(string symbol, decimal price)
    {
        return await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
    }

    public void Dispose()
    {
    }
}

TickerChanged ruft die handleTickerChanged2-Methode auf und zeigt die zurückgegebene Zeichenfolge in der folgenden CallJsExample5-Komponente an.

Pages/CallJsExample5.razor:

@page "/call-js-example-5"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call JS Example 5</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private Random r = new();
    private string? stockSymbol;
    private decimal price;
    private JsInteropClasses2? jsClass;
    private string? result;

    protected override void OnInitialized()
    {
        jsClass = new(JS);
    }

    private async Task SetStock()
    {
        if (jsClass is not null)
        {
            stockSymbol = 
                $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
            price = r.Next(1, 101);
            var interopResult = await jsClass.TickerChanged(stockSymbol, price);
            result = $"Result of TickerChanged call for {stockSymbol} at " +
                $"{price.ToString("c")}: {interopResult}";
        }
    }

    public void Dispose() => jsClass?.Dispose();
}

Szenarios der dynamischen Inhaltsgenerierung

Verwenden Sie das -Attribut für die dynamische Inhaltsgenerierung mit BuildRenderTree:

[Inject]
IJSRuntime JS { get; set; }

Voarabrendering

Dieser Abschnitt gilt für Blazor Server und gehostete Blazor WebAssembly-Apps, die Razor-Komponenten vorab rendern. Informationen zum Prerendering finden Sie in Prerendering und Integrieren von -Komponenten in ASP.NET Core.

Während eine App vorab gerendert wird, sind bestimmte Aktionen nicht möglich, z. B. Aufrufe in JavaScript (JS).

Im folgenden Beispiel wird die Funktion setElementText1 im Element <head> platziert. Die Funktion wird mit JSRuntimeExtensions.InvokeVoidAsync aufgerufen und gibt keinen Wert zurück.

Hinweis

Allgemeine Anleitungen zum Speicherort von JS und unsere Empfehlungen für Produktions-Apps finden Sie unter ASP.NET Core Blazor JavaScript-Interoperabilität (JS-Interop).

<script>
  window.setElementText1 = (element, text) => element.innerText = text;
</script>

Warnung

Im vorangehenden Beispiel wird das Dokumentobjektmodell (DOM) direkt zu Demonstrationszwecken geändert. Das direkte Ändern des DOM mit JS wird in den meisten Szenarios nicht empfohlen, da JS die Änderungsnachverfolgung von Blazor beeinträchtigen kann. Weitere Informationen finden Sie unter JavaScript-Interoperabilität von in ASP.NET Core (JS-Interoperabilität).

Das OnAfterRender{Async}-Lebenszyklusereignis wird beim Voarabrendering auf dem Server nicht aufgerufen. Überschreiben Sie die OnAfterRender{Async}-Methode, um JS-Interopaufrufe zu verzögern, bis die Komponente nach dem Vorabendering gerendert und auf dem Client interaktiv ist.

Pages/PrerenderedInterop1.razor:

@page "/prerendered-interop-1"
@using Microsoft.JSInterop
@inject IJSRuntime JS

<div @ref="divElement">Text during render</div>

@code {
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JS.InvokeVoidAsync(
                "setElementText1", divElement, "Text after render");
        }
    }
}

Hinweis

Das Beispiel oben „verschmutzt“ den Client mit globalen Methoden. Einen besseren Ansatz für Produktions-Apps finden Sie unter JavaScript-Isolation in JavaScript-Modulen.

Beispiel:

export setElementText1 = (element, text) => element.innerText = text;

Die folgende Komponente veranschaulicht, wie JS-Interop als Teil der Initialisierungslogik einer Komponente auf eine Weise verwendet werden kann, die mit dem Prerendering kompatibel ist. Die Komponente zeigt, dass es möglich ist, in OnAfterRenderAsync ein Renderingupdate zu initiieren. Der Entwickler muss in diesem Szenario unbedingt vermeiden, eine Endlosschleife zu erstellen.

Im folgenden Beispiel wird die Funktion setElementText2 im Element <head> platziert. Die Funktion wird mit IJSRuntime.InvokeAsync aufgerufen und gibt einen Wert zurück.

Hinweis

Allgemeine Anleitungen zum Speicherort von JS und unsere Empfehlungen für Produktions-Apps finden Sie unter ASP.NET Core Blazor JavaScript-Interoperabilität (JS-Interop).

<script>
  window.setElementText2 = (element, text) => {
    element.innerText = text;
    return text;
  };
</script>

Warnung

Im vorangehenden Beispiel wird das Dokumentobjektmodell (DOM) direkt zu Demonstrationszwecken geändert. Das direkte Ändern des DOM mit JS wird in den meisten Szenarios nicht empfohlen, da JS die Änderungsnachverfolgung von Blazor beeinträchtigen kann. Weitere Informationen finden Sie unter JavaScript-Interoperabilität von in ASP.NET Core (JS-Interoperabilität).

Wenn JSRuntime.InvokeAsync aufgerufen wird, wird ElementReference nur in OnAfterRenderAsync und nicht in einer früheren Lebenszyklusmethode verwendet, da es kein JS-Element gibt, bis die Komponente gerendert wird.

StateHasChanged wird aufgerufen, um die Komponente mit dem neuen Zustand, der vom JS-Interop-Aufruf abgerufen wurde, erneut zu rendern. Weitere Informationen erhalten Sie unter Razor-Komponentenrendering in ASP.NET Core. Der Code erstellt keine Endlosschleife, da StateHasChanged nur aufgerufen wird, wenn datanull ist.

Pages/PrerenderedInterop2.razor:

@page "/prerendered-interop-2"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<p>
    Get value via JS interop call:
    <strong id="val-get-by-interop">@(data ?? "No value yet")</strong>
</p>

<p>
    Set value via JS interop call:
</p>

<div id="val-set-by-interop" @ref="divElement"></div>

@code {
    private string? data;
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && data == null)
        {
            data = await JS.InvokeAsync<string>(
                "setElementText2", divElement, "Hello from interop call!");

            StateHasChanged();
        }
    }
}

Hinweis

Das Beispiel oben „verschmutzt“ den Client mit globalen Methoden. Einen besseren Ansatz für Produktions-Apps finden Sie unter JavaScript-Isolation in JavaScript-Modulen.

Beispiel:

export setElementText2 = (element, text) => {
  element.innerText = text;
  return text;
};

Synchrones JS-Interop in Blazor WebAssembly-Apps

Dieser Abschnitt gilt nur für Blazor WebAssembly-Apps.

JS-Interop-Aufrufe sind standardmäßig asynchron, und zwar unabhängig davon, ob der aufgerufene Code synchron oder asynchron ist. Aufrufe sind standardmäßig asynchron, um sicherzustellen, dass Komponenten mit beiden Blazor-Hostingmodellen (Blazor Serverund Blazor WebAssembly) kompatibel sind. Für Blazor Server müssen alle JS-Interopaufrufe asynchron sein, weil sie über eine Netzwerkverbindung gesendet werden.

Wenn Sie sicher sind, dass Ihre App nur mit Blazor WebAssembly ausgeführt wird, können Sie synchrone JS-Interopaufrufe durchführen. Dies ist mit etwas weniger Mehraufwand verbunden als asynchrone Aufrufe und kann zu weniger Renderingzyklen führen, da es keinen Zwischenzustand gibt, während auf die Ergebnisse gewartet wird.

Um einen synchronen Aufruf von .NET zu JavaScript in einer Blazor WebAssembly-App auszuführen, wandeln Sie IJSRuntime in IJSInProcessRuntime um, um den JS-Interopaufruf auszuführen:

@inject IJSRuntime JS

...

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

Wenn Sie in Blazor WebAssembly-Apps mit ASP.NET Core 5.0 oder höher mit IJSObjectReference arbeiten, können Sie IJSInProcessObjectReference stattdessen synchron verwenden. IJSInProcessObjectReference implementiert IAsyncDisposable/IDisposable und sollte für die Garbage Collection verworfen werden, um Arbeitsspeicherverlust zu verhindern, wie das folgende Beispiel veranschaulicht:

...

@inject IJSRuntime JS
@implements IAsyncDisposable

...

@code {
    ...
    private IJSInProcessObjectReference? module;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSInProcessObjectReference>("import", 
            "./scripts.js");
        }
    }

    ...

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

Speicherort von JavaScipt

Laden Sie JavaScript-Code (JS) mithilfe einer der Vorgehensweisen, die im Artikel JS beschrieben werden:

Informationen zum Isolieren von Skripts in -Modulen finden Sie im Abschnitt zur JavaScript-Isolation in JavaScript-Modulen.

Warnung

Platzieren Sie kein <script>-Tag in einer Komponentendatei (.razor), weil das <script>-Tag nicht dynamisch aktualisiert werden kann.

JavaScript-Isolierung in JavaScript-Modulen

Blazor aktiviert die JavaScript-Isolation (JS) in Blazor (JS). Das Laden von JavaScript-Modulen funktioniert in Blazor genauso wie bei anderen Arten von Web-Apps, und Sie können die Definition von Modulen in Ihrer App anpassen. Eine Anleitung zur Verwendung von JavaScript-Modulen finden Sie in den MDN Web Docs: JavaScript modules.

JS-Isolierung bietet die folgenden Vorteile:

  • Importiertes JS verschmutzt nicht mehr den globalen Namespace.
  • Consumer einer Bibliothek und Komponenten müssen nicht das zugehörige JS importieren.

Der dynamische Import mit dem import()-Operator wird mit ASP.NET Core und Blazor unterstützt:

if ({CONDITION}) import("/additionalModule.js");

Im vorherigen Beispiel steht der Platzhalter {CONDITION} für eine bedingte Überprüfung, mit der festgestellt wird, ob das Modul geladen werden soll.

Informationen zur Browserkompatibilität finden Sie unter Can I use: JavaScript modules: dynamic import.

Das folgende JS-Modul exportiert beispielsweise eine JS-Funktion zum Anzeigen eines JS. Platzieren Sie den folgenden JS-Code in einer externen JS-Datei.

wwwroot/scripts.js:

export function showPrompt(message) {
  return prompt(message, 'Type anything here');
}

Fügen Sie das obige JS-Modul als statisches Webobjekt zu einer App oder Klassenbibliothek im wwwroot-Ordner hinzu, und importieren Sie das Modul dann in den .NET-Code, indem Sie InvokeAsync für die IJSRuntime-Instanz aufrufen.

IJSRuntime importiert das Modul als IJSObjectReference, das einen Verweis auf ein JS-Objekt aus .NET-Code darstellt. Verwenden Sie IJSObjectReference, um exportierte JS-Funktionen aus dem Modul aufzurufen.

Pages/CallJsExample6.razor:

@page "/call-js-example-6"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>Call JS Example 6</h1>

<p>
    <button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>

<p>
    @result
</p>

@code {
    private IJSObjectReference? module;
    private string? result;

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

    private async Task TriggerPrompt()
    {
        result = await Prompt("Provide some text");
    }

    public async ValueTask<string?> Prompt(string message) =>
        module is not null ? 
            await module.InvokeAsync<string>("showPrompt", message) : null;

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

Im vorherigen Beispiel:

  • Per Konvention handelt es sich beim import-Bezeichner um einen besonderen Bezeichner, der spezifisch zum Importieren eines JS-Moduls verwendet wird.
  • Geben Sie die externe JS-Datei des Moduls mithilfe des stabilen Pfads des statischen Webobjekts an: ./{SCRIPT PATH AND FILE NAME (.js)}. Dabei gilt Folgendes:
    • Das Pfadsegment für das aktuelle Verzeichnis (./) ist erforderlich, damit der korrekte Pfad zu statischen Objekten in der JS-Datei erstellt werden kann.
    • Der Platzhalter {SCRIPT PATH AND FILE NAME (.js)} entspricht dem Pfad und Dateinamen unter wwwroot.
  • Dieser gibt die IJSObjectReference für die IJSObjectReference in IAsyncDisposable.DisposeAsync zurück.

Das dynamische Importieren eines Moduls erfordert eine Netzwerkanforderung, sodass es nur asynchron durch Aufrufen von InvokeAsync erreicht werden kann.

IJSInProcessObjectReference steht für einen Verweis auf ein JS-Objekt, dessen Funktionen synchron in Blazor WebAssembly-Apps aufgerufen werden können. Weitere Informationen finden Sie im Abschnitt Synchrones JS-Interop in Blazor WebAssembly-Apps.

Hinweis

Wenn die externe JS-Datei von einer JS bereitgestellt wird, geben Sie die JS-Datei des Moduls mithilfe des stabilen Pfads des statischen Webobjekts an: ./_content/{PACKAGE ID}/{SCRIPT PATH AND FILE NAME (.js)}:

  • Das Pfadsegment für das aktuelle Verzeichnis (./) ist erforderlich, damit der korrekte Pfad zu statischen Objekten in der JS-Datei erstellt werden kann.
  • Der Platzhalter {PACKAGE ID} ist diePaket-IDder Bibliothek. Die Paket-ID wird standardmäßig auf den Assemblynamen des Projekts festgelegt, wenn <PackageId> nicht in der Projektdatei angegeben ist. Im folgenden Beispiel lautet der Assemblyname der Bibliothek ComponentLibrary, und die Projektdatei der Bibliothek gibt <PackageId> nicht an.
  • Der Platzhalter {SCRIPT PATH AND FILE NAME (.js)} entspricht dem Pfad und Dateinamen unter wwwroot. Im folgenden Beispiel wird die externe JS-Datei (script.js) im wwwroot-Ordner der Klassenbibliothek platziert.
var module = await js.InvokeAsync<IJSObjectReference>(
    "import", "./_content/ComponentLibrary/scripts.js");

Weitere Informationen finden Sie unter Nutzen von ASP.NET Core -Komponenten über eine Razor-Klassenbibliothek (RCL).

Erfassen von Verweisen auf Elemente

Einige JavaScript-Interop-Szenarios (JS) erfordern Verweise auf HTML-Elemente. Beispielsweise erfordert eine Benutzeroberflächenbibliothek möglicherweise einen Elementverweis für die Initialisierung, oder Sie müssen befehlsähnliche APIs für ein Element aufrufen, z. B. click oder play.

Erfassen Sie mithilfe des folgenden Ansatzes Verweise auf HTML-Elemente in einer Komponente:

  • Fügen Sie ein @ref-Attribut zum HTML-Element hinzu.
  • Definieren Sie ein Feld vom Typ ElementReference, dessen Name mit dem Wert des @ref-Attributs übereinstimmt.

Im folgenden Beispiel wird das Erfassen eines Verweises auf das <input>-Element username veranschaulicht:

<input @ref="username" ... />

@code {
    private ElementReference username;
}

Warnung

Verwenden Sie Elementverweise nur, um die Inhalte eines leeren Elements zu ändern, das nicht mit Blazor interagiert. Dieses Szenario ist nützlich, wenn eine Drittanbieter-API Inhalt für das Element bereitstellt. Da Blazor nicht mit dem Element interagiert, kann kein Konflikt zwischen der Darstellung von Blazor des Elements und dem DOM (Dokumentobjektmodell) auftreten.

Im folgenden Beispiel ist es riskant, die Inhalte der unsortierten Liste () zu ändern, weil Blazor mit dem DOM interagiert, um die Listenelemente (<li>) dieses Elements anhand des Todos-Objekts aufzufüllen:

<ul @ref="MyList">
    @foreach (var item in Todos)
    {
        <li>@item.Text</li>
    }
</ul>

Wenn ein JS-Interop-Aufruf die Inhalte des MyList-Elements ändert und Blazor versucht, Diff-Algorithmen auf das Element anzuwenden, stimmen diese nicht mit dem DOM überein.

Weitere Informationen finden Sie unter JavaScript-Interoperabilität von in ASP.NET Core (JS-Interoperabilität).

Eine ElementReference-Struktur wird über einen JS-Interop-Aufruf an den JS-Code übergeben. Der JS-Code empfängt eine HTMLElement-Instanz, die er mit normalen DOM-APIs verwenden kann. Im folgenden Code wird beispielsweise eine .NET-Erweiterungsmethode (TriggerClickEvent) definiert, die das Senden eines Mausklicks an ein Element ermöglicht.

Die JS-Funktion clickElement erstellt ein click-Ereignis an das übergebene HTML-Element (element):

window.interopFunctions = {
  clickElement : function (element) {
    element.click();
  }
}

Verwenden Sie JSRuntimeExtensions.InvokeVoidAsync, um eine JS-Funktion aufzurufen, die keinen Wert zurückgibt. Der folgende Code löst ein clientseitiges click-Ereignis aus, indem die vorherige JS-Funktion mit der erfassten ElementReference-Struktur aufgerufen wird:

@inject IJSRuntime JS

<button @ref="exampleButton">Example Button</button>

<button @onclick="TriggerClick">
    Trigger click event on <code>Example Button</code>
</button>

@code {
    private ElementReference exampleButton;

    public async Task TriggerClick()
    {
        await JS.InvokeVoidAsync(
            "interopFunctions.clickElement", exampleButton);
    }
}

Erstellen Sie eine statische Erweiterungsmethode, die die IJSRuntime-Instanz empfängt, um eine Erweiterungsmethode zu verwenden:

public static async Task TriggerClickEvent(this ElementReference elementRef, 
    IJSRuntime js)
{
    await js.InvokeVoidAsync("interopFunctions.clickElement", elementRef);
}

Die clickElement-Methode wird direkt für das Objekt aufgerufen. Im folgenden Beispiel wird davon ausgegangen, dass die TriggerClickEvent-Methode vom JsInteropClasses-Namespace verfügbar ist:

@inject IJSRuntime JS
@using JsInteropClasses

<button @ref="exampleButton">Example Button</button>

<button @onclick="TriggerClick">
    Trigger click event on <code>Example Button</code>
</button>

@code {
    private ElementReference exampleButton;

    public async Task TriggerClick()
    {
        await exampleButton.TriggerClickEvent(JS);
    }
}

Wichtig

Die exampleButton-Variable wird erst aufgefüllt, nachdem die Komponente gerendert wurde. Wenn eine aufgefüllte ElementReference-Struktur an JS-Code übergeben wird, empfängt der JS-Code einen null-Wert. Sie können Elementverweise bearbeiten, nachdem die Komponente das Rendering abgeschlossen hat, indem Sie die Lebenszyklusmethoden oder OnAfterRender verwenden.

Verwenden Sie ValueTask<TResult>, wenn Sie mit generischen Typen arbeiten und einen Wert zurückgeben:

public static ValueTask<T> GenericMethod<T>(this ElementReference elementRef, 
    IJSRuntime js)
{
    return js.InvokeAsync<T>("{JAVASCRIPT FUNCTION}", elementRef);
}

Der Platzhalter {JAVASCRIPT FUNCTION} ist der Bezeichner für die JS-Funktion.

GenericMethod wird direkt für das Objekt mit einem Typ aufgerufen. Im folgenden Beispiel wird davon ausgegangen, dass die GenericMethod-Methode vom JsInteropClasses-Namespace verfügbar ist:

@inject IJSRuntime JS
@using JsInteropClasses

<input @ref="username" />

<button @onclick="OnClickMethod">Do something generic</button>

<p>
    returnValue: @returnValue
</p>

@code {
    private ElementReference username;
    private string? returnValue;

    private async Task OnClickMethod()
    {
        returnValue = await username.GenericMethod<string>(JS);
    }
}

Komponentenübergreifende Verweiselemente

Ein ElementReference kann aus folgenden Gründen nicht zwischen Komponenten übermittelt werden:

Damit eine übergeordnete Komponente einen Elementverweis anderen Komponenten zur Verfügung stellen kann, kann die übergeordnete Komponente:

  • zulassen, dass untergeordnete Komponenten Rückrufe registrieren.
  • die registrierten Rückrufe während des OnAfterRender-Ereignisses mit dem übergebenen Elementverweis aufrufen. Dieser Ansatz ermöglicht untergeordneten Komponenten indirekt die Interaktion mit dem Elementverweis des übergeordneten Elements.
<style>
    .red { color: red }
</style>
<script>
  function setElementClass(element, className) {
    var myElement = element;
    myElement.classList.add(className);
  }
</script>

Hinweis

Allgemeine Anleitungen zum Speicherort von JS und unsere Empfehlungen für Produktions-Apps finden Sie unter JavaScript-Interoperabilität von Blazor in ASP.NET Core (JS-Interoperabilität).

Pages/CallJsExample7.razor (übergeordnete Komponente):

@page "/call-js-example-7"

<h1>Call JS Example 7</h1>

<h2 @ref="title">Hello, world!</h2>

Welcome to your new app.

<SurveyPrompt Parent="@this" Title="How is Blazor working for you?" />

Pages/CallJsExample7.razor.cs:

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;

namespace BlazorSample.Pages;

public partial class CallJsExample7 : 
    ComponentBase, IObservable<ElementReference>, IDisposable
{
    private bool disposing;
    private IList<IObserver<ElementReference>> subscriptions = 
        new List<IObserver<ElementReference>>();
    private ElementReference title;

    protected override void OnAfterRender(bool firstRender)
    {
        base.OnAfterRender(firstRender);

        foreach (var subscription in subscriptions)
        {
            try
            {
                subscription.OnNext(title);
            }
            catch (Exception)
            {
                throw;
            }
        }
    }

    public void Dispose()
    {
        disposing = true;

        foreach (var subscription in subscriptions)
        {
            try
            {
                subscription.OnCompleted();
            }
            catch (Exception)
            {
            }
        }

        subscriptions.Clear();
    }

    public IDisposable Subscribe(IObserver<ElementReference> observer)
    {
        if (disposing)
        {
            throw new InvalidOperationException("Parent being disposed");
        }

        subscriptions.Add(observer);

        return new Subscription(observer, this);
    }

    private class Subscription : IDisposable
    {
        public Subscription(IObserver<ElementReference> observer, 
            CallJsExample7 self)
        {
            Observer = observer;
            Self = self;
        }

        public IObserver<ElementReference> Observer { get; }
        public CallJsExample7 Self { get; }

        public void Dispose()
        {
            Self.subscriptions.Remove(Observer);
        }
    }
}

Im vorherigen Beispiel ist BlazorSample der Namespace der App mit Komponenten im Ordner Pages. Wenn Sie den Code lokal testen möchten, müssen Sie den Namespace ändern.

Shared/SurveyPrompt.razor (untergeordnete Komponente):

@inject IJSRuntime JS

<div class="alert alert-secondary mt-4" role="alert">
    <span class="oi oi-pencil mr-2" aria-hidden="true"></span>
    <strong>@Title</strong>

    <span class="text-nowrap">
        Please take our
        <a target="_blank" class="font-weight-bold" 
            href="https://go.microsoft.com/fwlink/?linkid=2109206">brief survey</a>
    </span>
    and tell us what you think.
</div>

@code {
    [Parameter]
    public string? Title { get; set; }
}

Shared/SurveyPrompt.razor.cs:

using System;
using Microsoft.AspNetCore.Components;

namespace BlazorSample.Shared;

public partial class SurveyPrompt : 
    ComponentBase, IObserver<ElementReference>, IDisposable
{
    private IDisposable? subscription = null;

    [Parameter]
    public IObservable<ElementReference>? Parent { get; set; }

    protected override void OnParametersSet()
    {
        base.OnParametersSet();

        subscription?.Dispose();
        subscription = 
            Parent is not null ? Parent.Subscribe(this) : null;
    }

    public void OnCompleted()
    {
        subscription = null;
    }

    public void OnError(Exception error)
    {
        subscription = null;
    }

    public void OnNext(ElementReference value)
    {
        JS.InvokeAsync<object>(
            "setElementClass", new object[] { value, "red" });
    }

    public void Dispose()
    {
        subscription?.Dispose();
    }
}

Im vorherigen Beispiel ist BlazorSample der Namespace der App mit gemeinsamen Komponenten im Ordner Shared. Wenn Sie den Code lokal testen möchten, müssen Sie den Namespace ändern.

Festschreiben von JavaScript-Interop-Aufrufen

Dieser Abschnitt gilt hauptsächlich für -Apps, jedoch können Blazor WebAssembly-Apps auch JS-Interop-Timeouts festlegen, wenn die Bedingungen dies rechtfertigen.

In Blazor Server-Apps können JavaScript-Interop-Aufrufe (JS) aufgrund von Netzwerkfehlern fehlschlagen, weshalb sie als unzuverlässig betrachtet werden sollten. Blazor Server-Apps verwenden standardmäßig einen einminütigen Timeout für JS-Interop-Aufrufe. Wenn eine App ein engeres Timeout tolerieren kann, legen Sie das Timeout mit einem der folgenden Ansätze fest:

Legen Sie mit CircuitOptions.JSInteropDefaultCallTimeout ein globales Timeout in der Program.cs-Datei fest:

builder.Services.AddServerSideBlazor(
    options => options.JSInteropDefaultCallTimeout = {TIMEOUT});

Der Platzhalter {TIMEOUT} ist ein TimeSpan (z. B. TimeSpan.FromSeconds(80)).

Legen Sie im Komponentencode ein Timeout pro Aufruf fest. Das festgelegte Timeout überschreibt das globale Timeout, das von JSInteropDefaultCallTimeout festgelegt wird:

var result = await JS.InvokeAsync<string>("{ID}", {TIMEOUT}, new[] { "Arg1" });

Im vorherigen Beispiel:

  • Der Platzhalter {TIMEOUT} ist ein TimeSpan (z. B. TimeSpan.FromSeconds(80)).
  • Der Platzhalter {ID} ist der Bezeichner für die aufzurufende Funktion. Der Wert someScope.someFunction ruft beispielsweise die Funktion window.someScope.someFunction auf.

Obwohl Netzwerkfehler in Blazor Server-Apps eine gängige Ursache von JS-Interop-Fehlern sind, können Timeouts pro Aufruf für JS-Interop-Aufrufe in Blazor WebAssembly-Apps festgelegt werden. Obwohl keine SignalR-Leitung in einer Blazor WebAssembly-App vorhanden ist, können JS-Interop-Aufrufe aus anderen Gründen fehlschlagen, die sich auf Blazor WebAssembly-Apps beziehen.

Weitere Informationen zur Ressourcenerschöpfung finden Sie unter Leitfaden zur Bedrohungsabwehr für in ASP.NET Core.

Vermeiden von Objektzirkelbezügen

Objekte, die Zirkelbezüge enthalten, können auf dem Client für folgende Vorgänge nicht serialisiert werden:

  • .NET-Methodenaufrufe.
  • JavaScript-Methodenaufrufe von C#, wenn der Rückgabetyp Zirkelbezüge enthält.

JavaScript-Bibliotheken zum Rendern der Benutzeroberfläche

Möglicherweise möchten Sie manchmal JavaScript-Bibliotheken (JS) verwenden, die sichtbare Benutzeroberflächenelemente im DOM erzeugen. Auf den ersten Blick scheint dies schwierig zu sein, da das Vergleichssystem von Blazor darauf beruht, die Kontrolle über die Struktur von DOM-Elementen zu haben. Außerdem treten Fehler auf, wenn externer Code die DOM-Struktur bearbeitet, was den Mechanismus für die Diff-Anwendung unmöglich macht. Dabei handelt es sich um keine Blazor-spezifische Einschränkung. Die gleiche Herausforderung stellt sich bei jedem anderen Diff-basierten Benutzeroberflächenframework.

Glücklicherweise ist es einfach, extern erstellte Benutzeroberflächenelemente innerhalb der Oberfläche einer Razor-Komponente zuverlässig einzubetten. Die hierfür empfohlene Methode besteht darin, den Code der Komponente (.razor-Datei) ein leeres Element erzeugen zu lassen. Im Vergleichssystem von Blazor ist das Element immer leer, der Renderer durchläuft das Element also nicht rekursiv, sondern berücksichtigt die Inhalte gar nicht. Dies führt dazu, dass das Auffüllen des Elements mit zufälligem extern verwalteten Inhalt sicher ist.

Das folgende Beispiel veranschaulicht das Konzept. Interagieren Sie mithilfe von JS außerhalb von Blazor mit unmanagedElement, wenn firstRender in der if-Anweisung den Wert true aufweist. Rufen Sie beispielsweise eine externe JS-Bibliothek auf, um das Element aufzufüllen. Blazor ignoriert die Inhalte des Elements, bis die Komponente entfernt wurde. Wenn die Komponente entfernt wird, wird die gesamte DOM-Unterstruktur der Komponente ebenfalls entfernt.

<h1>Hello! This is a Razor component rendered at @DateTime.Now</h1>

<div @ref="unmanagedElement"></div>

@code {
    private ElementReference unmanagedElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            ...
        }
    }
}

Betrachten Sie das folgende Beispiel, in dem eine interaktive Karte mithilfe der Open-Source-Mapbox-APIs gerendert wird.

Das folgende JS-Modul wird in der App platziert oder über eine Razor-Klassenbibliothek zur Verfügung gestellt.

Hinweis

Rufen Sie zum Erstellen der Mapbox-Karte ein Zugriffstoken von der Mapbox-Anmeldung ab, und geben Sie dieses an der Stelle an, an der sich im folgenden Code befindet.

wwwroot/mapComponent.js:

import 'https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js';

mapboxgl.accessToken = '{ACCESS TOKEN}';

export function addMapToElement(element) {
  return new mapboxgl.Map({
    container: element,
    style: 'mapbox://styles/mapbox/streets-v11',
    center: [-74.5, 40],
    zoom: 9
  });
}

export function setMapCenter(map, latitude, longitude) {
  map.setCenter([longitude, latitude]);
}

Fügen Sie das folgende Formatvorlagentag zur HTML-Hostseite hinzu, um die korrekte Formatierung zu erzielen.

Fügen Sie das folgende Element <link> zum Markup des Elements <head> hinzu (Speicherort des <head>-Inhalts):

<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css" 
    rel="stylesheet" />

Pages/CallJsExample8.razor:

@page "/call-js-example-8"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>Call JS Example 8</h1>

<div @ref="mapElement" style='width:400px;height:300px'></div>

<button @onclick="() => ShowAsync(51.454514, -2.587910)">Show Bristol, UK</button>
<button @onclick="() => ShowAsync(35.6762, 139.6503)">Show Tokyo, Japan</button>

@code
{
    private ElementReference mapElement;
    private IJSObjectReference? mapModule;
    private IJSObjectReference? mapInstance;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            mapModule = await JS.InvokeAsync<IJSObjectReference>(
                "import", "./mapComponent.js");
            mapInstance = await mapModule.InvokeAsync<IJSObjectReference>(
                "addMapToElement", mapElement);
        }
    }

    private async Task ShowAsync(double latitude, double longitude)
    {
        if (mapModule is not null && mapInstance is not null)
        {
            await mapModule.InvokeVoidAsync("setMapCenter", mapInstance, 
                latitude, longitude).AsTask();
        }
    }

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (mapInstance is not null)
        {
            await mapInstance.DisposeAsync();
        }

        if (mapModule is not null)
        {
            await mapModule.DisposeAsync();
        }
    }
}

Im obigen Beispiel wird eine interaktive Kartenbenutzeroberfläche erzeugt. Der Benutzer verfügt über folgende Möglichkeiten:

  • Ziehen der Karte zum Scrollen oder Zoomen
  • Klicken von Schaltflächen zum Springen zu vordefinierten Standorten

Mapbox-Straßenkarte von Tokio in Japan, mit Schaltflächen, über die Bristol im Vereinigten Königreich und Tokio in Japan ausgewählt werden können

Im vorherigen Beispiel:

  • Für Blazor gilt, dass <div> mit @ref="mapElement" leer bleibt. Das mapbox-gl.js-Skript kann das Element sicher auffüllen und dessen Inhalte ändern. Sie können diese Vorgehensweise mit jeder beliebigen JS-Bibliothek verwenden, die eine Benutzeroberfläche rendert. Sie können Komponenten eines JS-SPA-Frameworks eines Drittanbieters in Razor-Komponenten einbetten, solange diese nicht versuchen, andere Teile der Seite zu ändern. Es ist nicht sicher, wenn externer -Code Elemente ändert, die Blazor nicht als leer betrachtet.
  • Bei Verwendung dieses Ansatzes sollten Sie die Regeln im Hinterkopf behalten, wie Blazor DOM-Elemente beibehält oder löscht. Die Komponente verarbeitet Schaltflächen-Klickereignisse auf sichere weise und aktualisiert die vorhandene Karteninstanz, weil DOM-Elemente standardmäßig beibehalten werden, wenn dies möglich ist. Wenn Sie eine Liste von Kartenelementen innerhalb einer @foreach-Schleife rendern, sollten Sie @key verwenden, um für die Beibehaltung der Komponenteninstanzen zu sorgen. Andernfalls könnten Änderungen der Listendaten dazu führen, dass Komponenteninstanzen den Status vorheriger Instanzen auf nicht gewünschte Weise beibehalten. Weitere Informationen finden Sie unter using @key to preserve elements and components.
  • Das Beispiel kapselt JS-Logik und -Abhängigkeiten in einem ES6-Modul und lädt das Modul dynamisch mithilfe des import-Bezeichners. Weitere Informationen finden Sie unter JavaScript-Isolation in JavaScript-Modulen.

Bytearrayunterstützung

Blazor unterstützt optimiertes Bytearray-JavaScript (JS)-Interop, mit dem sich das Codieren/Decodieren von Bytearrays in Base64 vermeiden lässt. Im folgenden Beispiel wird JS-Interop verwendet, um ein Bytearray an JavaScript zu übergeben.

Stellen Sie eine receiveByteArrayJS-Funktion bereit. Die Funktion wird mit InvokeVoidAsync aufgerufen und gibt keinen Wert zurück:

<script>
  window.receiveByteArray = (bytes) => {
    let utf8decoder = new TextDecoder();
    let str = utf8decoder.decode(bytes);
    return str;
  };
</script>

Hinweis

Allgemeine Anleitungen zum Speicherort von JS und unsere Empfehlungen für Produktions-Apps finden Sie unter JavaScript-Interoperabilität von Blazor in ASP.NET Core (JS-Interoperabilität).

Pages/CallJsExample9.razor:

@page "/call-js-example-9"
@inject IJSRuntime JS

<h1>Call JS Example 9</h1>

<p>
    <button @onclick="SendByteArray">Send Bytes</button>
</p>

<p>
    @result
</p>

<p>
    Quote &copy;2005 <a href="https://www.uphe.com">Universal Pictures</a>:
    <a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
    <a href="https://www.imdb.com/name/nm0821612/">Jewel Staite on IMDB</a>
</p>

@code {
    private string? result;

    private async Task SendByteArray()
    {
        var bytes = new byte[] { 0x45, 0x76, 0x65, 0x72, 0x79, 0x74, 0x68, 0x69,
            0x6e, 0x67, 0x27, 0x73, 0x20, 0x73, 0x68, 0x69, 0x6e, 0x79, 0x2c,
            0x20, 0x43, 0x61, 0x70, 0x74, 0x69, 0x61, 0x6e, 0x2e, 0x20, 0x4e,
            0x6f, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x72, 0x65, 0x74, 0x2e };

        result = await JS.InvokeAsync<string>("receiveByteArray", bytes);
    }
}

Informationen zur Verwendung eines Bytearrays beim Aufruf von .NET aus JavaScript finden Sie unter Aufrufen von .NET-Methoden über JavaScript-Funktionen in in ASP.NET Core.

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

Dieser Abschnitt gilt nur für Blazor Server-Apps. In Blazor WebAssembly gibt das Framework keine Einschränkungen hinsichtlich der Größe von JavaScript-Interop-Eingaben und -Ausgaben (JS) vor.

In Blazor Server wird die Größe von JS-Interop-Aufrufen durch die maximale SignalR-Nachrichtengröße eingeschränkt, die für Hubmethoden zulässig sind. Dies wird von HubOptions.MaximumReceiveMessageSize erzwungen (Standardwert: 32 KB). SignalR-Nachrichten von JS an .NET, die größer als MaximumReceiveMessageSize sich, lösen einen Fehler aus. Das Framework beinhaltet keine Beschränkungen hinsichtlich der Größe einer SignalR-Nachricht vom Hub an einen Client.

Wenn die SignalR-Protokollierung nicht auf SignalR oder Überwachung festgelegt ist, wird ein Fehler zur Nachrichtengröße nur in der Konsole für Entwicklertools des Browsers angezeigt:

Fehler: Connection disconnected with error „Error: Server returned an error on close: Connection closed with an error.“ (Die Verbindung wurde durch den folgenden Fehler getrennt: „Der Server hat beim Schließen einen Fehler zurückgegeben: Die Verbindung wurde durch einen Fehler beendet.“)

Wenn die serverseitige Protokollierung in auf Debuggen oder Überwachung festgelegt ist, tritt bei der serverseitigen Protokollierung eine -Ausnahme für einen Fehler in Bezug auf die Nachrichtengröße auf.

appsettings.Development.json:

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "Microsoft.AspNetCore.SignalR": "Debug"
    }
  }
}

Error:

System.IO.InvalidDataException: The maximum message size of 32768B was exceeded. The message size can be configured in AddHubOptions. (System.IO.InvalidDataException: Die maximale Nachrichtengröße von 32.768 Byte wurde überschritten. Die Nachrichtengröße kann unter AddHubOptions konfiguriert werden.)

Erhöhen Sie den Grenzwert, indem Sie MaximumReceiveMessageSize in Program.cs festlegen. Im folgenden Beispiel wird die maximale Größe eingehender Nachrichten auf 64 KB festgelegt:

builder.Services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

Wenn Sie den Grenzwert für die Größe eingehender Nachrichten von SignalR erhöhen, verbrauchen Sie auch mehr Serverressourcen und setzen den Server einem erhöhten Risiko durch böswillige Benutzer aus. Darüber hinaus kann das Lesen sehr großer Inhalte in den Arbeitsspeicher als Zeichenfolgen oder Bytearrays zu Zuordnungen führen, die vom Garbage Collector nur schlecht verarbeitet werden können. Dies kann zu zusätzlichen Leistungseinbußen führen.

Beachten Sie die folgenden Anleitungen, wenn Sie Code zum Übertragen großer Datenmengen zwischen JS und Blazor in Blazor Server-Apps entwickeln:

  • Nutzen Sie zur Übertragung von Daten, deren Größe die Begrenzung für eingehende SignalR-Nachrichten überschreitet, die Unterstützung für native Streaming-Interop-Aufrufe.
  • Allgemeine Tipps:
    • Ordnen Sie in JS- und C#-Code keine großen Objekte zu.
    • Geben Sie belegten Arbeitsspeicher frei, wenn der Prozess beendet oder abgebrochen wird.
    • Erzwingen Sie die folgenden zusätzlichen Anforderungen aus Sicherheitsgründen:
      • Deklarieren Sie die maximale Datei- oder Datengröße, die übermittelt werden kann.
      • Deklarieren Sie die minimale Uploadrate vom Client an den Server.
    • Nachdem die Daten vom Server empfangen wurden, ist mit den Daten Folgendes möglich:
      • Sie können temporär in einem Speicherpuffer gespeichert werden, bis alle Segmente gesammelt wurden.
      • Sie können sofort verarbeitet werden. Beispielsweise können die Daten sofort in einer Datenbank gespeichert oder auf den Datenträger geschrieben werden, wenn die einzelnen Segmente empfangen werden.

JavaScript-Interop-Aufrufe mit rückgängig gemachtem Marshallen

Bei Blazor WebAssembly-Komponenten kann es zu Leistungseinbußen kommen, wenn .NET-Objekte für JavaScript-Interop-Aufrufe (JS) serialisiert werden und eine der folgenden Bedingungen zutrifft:

  • Eine große Menge von .NET-Objekten wird schnell serialisiert. Es kann beispielsweise zu Leistungseinbußen kommen, wenn JS-Interop-Aufrufe auf Grundlage der Bewegung eines Eingabegeräts ausgeführt werden, z. B. beim Drehen eines Mausrads.
  • Große .NET-Objekte bzw. große Mengen von .NET-Objekten müssen für JS-Interop-Aufrufe serialisiert werden. Beispielsweise können Leistungseinbußen auftreten, wenn JS-Interop-Aufrufe die Serialisierung vieler Dateien erfordern.

IJSUnmarshalledObjectReference steht für einen Verweis auf ein JS-Objekt, dessen Funktionen aufgerufen werden können, ohne dass .NET-Daten serialisiert werden müssen.

Im folgenden Beispiel:

  • Eine Struktur, die eine Zeichenfolge und einen Integerwert enthält, wird unserialisiert an übergegeben.
  • JS-Funktionen verarbeiten die Daten und geben entweder einen booleschen Wert oder eine Zeichenfolge an den Aufrufer zurück.
  • Eine JS-Zeichenfolge ist nicht direkt in ein .NET-string-Objekt konvertierbar. Mit der unmarshalledFunctionReturnString-Funktion wird BINDING.js_string_to_mono_string aufgerufen, um die Konvertierung einer JS-Zeichenfolge zu verwalten.

Hinweis

Die folgenden Beispiele stellen keine typischen Anwendungsfälle für dieses Szenario dar, da die Struktur, die an übergeben wird, nicht zu einer schlechten Komponentenleistung führt. Im Beispiel wird ein kleines Objekt verwendet, um nur die Konzepte für das Übergeben von unserialisierten .NET-Daten zu veranschaulichen.

<script>
  window.returnObjectReference = () => {
    return {
      unmarshalledFunctionReturnBoolean: function (fields) {
        const name = Blazor.platform.readStringField(fields, 0);
        const year = Blazor.platform.readInt32Field(fields, 8);

        return name === "Brigadier Alistair Gordon Lethbridge-Stewart" &&
            year === 1968;
      },
      unmarshalledFunctionReturnString: function (fields) {
        const name = Blazor.platform.readStringField(fields, 0);
        const year = Blazor.platform.readInt32Field(fields, 8);

        return BINDING.js_string_to_mono_string(`Hello, ${name} (${year})!`);
      }
    };
  }
</script>

Hinweis

Allgemeine Anleitungen zum Speicherort von JS und unsere Empfehlungen für Produktions-Apps finden Sie unter ASP.NET Core Blazor JavaScript-Interoperabilität (JS-Interop).

Warnung

Der Name, das Verhalten und das Vorhandensein der js_string_to_mono_string-Funktion kann sich in einer zukünftigen Version von .NET ändern. Beispiel:

  • Die Funktion wird wahrscheinlich umbenannt.
  • Die Funktion selbst kann zugunsten automatischer Konvertierung von Zeichenfolgen durch das Framework entfernt werden.

Pages/CallJsExample10.razor:

@page "/call-js-example-10"
@using System.Runtime.InteropServices
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Call JS Example 10</h1>

@if (callResultForBoolean)
{
    <p>JS interop was successful!</p>
}

@if (!string.IsNullOrEmpty(callResultForString))
{
    <p>@callResultForString</p>
}

<p>
    <button @onclick="CallJSUnmarshalledForBoolean">
        Call Unmarshalled JS & Return Boolean
    </button>
    <button @onclick="CallJSUnmarshalledForString">
        Call Unmarshalled JS & Return String
    </button>
</p>

<p>
    <a href="https://www.doctorwho.tv">Doctor Who</a>
    is a registered trademark of the <a href="https://www.bbc.com/">BBC</a>.
</p>

@code {
    private bool callResultForBoolean;
    private string? callResultForString;

    private void CallJSUnmarshalledForBoolean()
    {
        var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;

        var jsUnmarshalledReference = unmarshalledRuntime
            .InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
                "returnObjectReference");

        callResultForBoolean = 
            jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, bool>(
                "unmarshalledFunctionReturnBoolean", GetStruct());
    }

    private void CallJSUnmarshalledForString()
    {
        var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;

        var jsUnmarshalledReference = unmarshalledRuntime
            .InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
                "returnObjectReference");

        callResultForString = 
            jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, string>(
                "unmarshalledFunctionReturnString", GetStruct());
    }

    private InteropStruct GetStruct()
    {
        return new InteropStruct
        {
            Name = "Brigadier Alistair Gordon Lethbridge-Stewart",
            Year = 1968,
        };
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct InteropStruct
    {
        [FieldOffset(0)]
        public string Name;

        [FieldOffset(8)]
        public int Year;
    }
}

Wenn eine IJSUnmarshalledObjectReference-Instanz nicht im C#-Code verworfen wird, kann diese in JS verworfen werden. Die folgende dispose-Funktion verwirft den Objektverweis, wenn er über JS aufgerufen wird:

window.exampleJSObjectReferenceNotDisposedInCSharp = () => {
  return {
    dispose: function () {
      DotNet.disposeJSObjectReference(this);
    },

    ...
  };
}

Arraytypen können mithilfe von js_typed_array_to_array aus JS-Objekten in .NET-Objekte konvertiert werden, aber das JS-Array muss ein typisiertes Array sein. Arrays aus JS können in C#-Code als .NET-Objektarray (object[]) gelesen werden.

Andere Datentypen, z. B. Zeichenfolgenarrays, können konvertiert werden, erfordern jedoch das Erstellen eines neuen Mono-Arrayobjekts (mono_obj_array_new) und das Festlegen seines Werts (mono_obj_array_set).

Warnung

JS-Funktionen, die vom Blazor-Framework bereitgestellt werden (z. B. js_typed_array_to_array, mono_obj_array_new und mono_obj_array_set), unterliegen Namensänderungen, Verhaltensänderungen oder der Entfernung in zukünftigen Releases von .NET.

Streamen von .NET zu JavaScript

Blazor unterstützt das direkte Streamen von Daten von .NET an JavaScript. Streams werden mithilfe von DotNetStreamReference erstellt.

DotNetStreamReference stellt einen .NET-Stream dar und verwendet die folgenden Parameter:

  • stream: Der an JavaScript gesendete Stream
  • leaveOpen: Legt fest, ob der Stream nach der Übertragung geöffnet bleibt. Wenn kein Wert angegeben wird, wird leaveOpen standardmäßig auf false festgelegt.

Verwenden Sie in JavaScript einen Arraypuffer oder einen lesbaren Stream, um die Daten zu empfangen:

  • Unter Verwendung von ArrayBuffer:

    async function streamToJavaScript(streamRef) {
      const data = await streamRef.arrayBuffer();
    }
    
  • Unter Verwendung von ReadableStream:

    async function streamToJavaScript(streamRef) {
      const stream = await streamRef.stream();
    }
    

In C#-Code:

using var streamRef = new DotNetStreamReference(stream: {STREAM}, leaveOpen: false);
await JS.InvokeVoidAsync("streamToJavaScript", streamRef);

Im vorherigen Beispiel:

  • Der Platzhalter {STREAM} stellt den an JavaScript gesendeten Stream dar.
  • JS ist eine eingefügte IJSRuntime-Instanz.

Unter Aufrufen von .NET-Methoden über JavaScript-Funktionen in in ASP.NET Core wird der umgekehrte Vorgang behandelt, nämlich das Streamen aus JavaScript in .NET.

Unter -Dateidownloads in ASP.NET Core wird das Herunterladen einer Datei in Blazor behandelt.

Abfangen von JavaScript-Ausnahmen

Zum Abfangen von JS-Ausnahmen schließen Sie JS-Interop in einen JS ein, und fangen Sie eine JSException ab.

Im folgenden Beispiel ist die JS-Funktion nonFunction nicht vorhanden. Wenn die Funktion nicht gefunden wird, wird die JSException mit einer Message abgefangen, die den folgenden Fehler angibt:

Could not find 'nonFunction' ('nonFunction' was undefined).

Pages/CallJsExample11.razor:

@page "/call-js-example-11"
@inject IJSRuntime JS

<h1>Call JS Example 11</h1>

<p>
    <button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>

<p>
    @result
</p>

<p>
    @errorMessage
</p>

@code {
    private string? errorMessage;
    private string? result;

    private async Task CatchUndefinedJSFunction()
    {
        try
        {
            result = await JS.InvokeAsync<string>("nonFunction");
        }
        catch (JSException e)
        {
            errorMessage = $"Error Message: {e.Message}";
        }
    }
}

Abbrechen einer JavaScript-Funktion mit langer Ausführungszeit

Verwenden Sie einen JSAbortController mit einer CancellationTokenSource in der Komponente, um eine JavaScript-Funktion mit langer Ausführungszeit aus C#-Code abzubrechen.

Die folgende JSHelpers-Klasse enthält eine simulierte Funktion mit langer Ausführungszeit (longRunningFn), die kontinuierlich zählt, bis AbortController.signal angibt, dass AbortController.abort aufgerufen wurde. Die sleep-Funktion dient zu Demonstrationszwecken, um die langsame Ausführung der Funktion mit langer Ausführungszeit zu simulieren, und wäre im Produktionscode nicht vorhanden. Wenn eine Komponente stopFn aufruft, wird longRunningFn signalisiert, den Vorgang über die bedingte while-Schleifenüberprüfung für AbortSignal.aborted abzubrechen.

<script>
  class Helpers {
    static #controller = new AbortController();

    static async #sleep(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }

    static async longRunningFn() {
      var i = 0;
      while (!this.#controller.signal.aborted) {
        i++;
        console.log(`longRunningFn: ${i}`);
        await this.#sleep(1000);
      }
    }

    static stopFn() {
      this.#controller.abort();
      console.log('longRunningFn aborted!');
    }
  }

  window.Helpers = Helpers;
</script>

Hinweis

Allgemeine Anleitungen zum Speicherort von JS und unsere Empfehlungen für Produktions-Apps finden Sie unter JavaScript-Interoperabilität von Blazor in ASP.NET Core (JS-Interoperabilität).

Die folgende CallJsExample12-Komponente:

Pages/CallJsExample12.razor:

@page "/call-js-example-12"
@inject IJSRuntime JS

<h1>Cancel long-running JS interop</h1>

<p>
    <button @onclick="StartTask">Start Task</button>
    <button @onclick="CancelTask">Cancel Task</button>
</p>

@code {
    private CancellationTokenSource? cts;

    private async Task StartTask()
    {
        cts = new CancellationTokenSource();
        cts.Token.Register(() => JS.InvokeVoidAsync("Helpers.stopFn"));

        await JS.InvokeVoidAsync("Helpers.longRunningFn");
    }

    private void CancelTask()
    {
        cts?.Cancel();
    }

    public void Dispose()
    {
        cts?.Cancel();
        cts?.Dispose();
    }
}

Die Konsole mit den Entwicklertools eines Browsers gibt die Ausführung der JS-Funktion mit langer Ausführungsdauer an, nachdem die Schaltfläche Start Task ausgewählt und die Funktion abgebrochen wurde, nachdem die Schaltfläche Cancel Task ausgewählt wurde:

longRunningFn: 1
longRunningFn: 2
longRunningFn: 3
longRunningFn aborted!

DOM-Bereinigungsvorgänge (Document Object Model, Dokumentobjektmodell) 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 das MutationObserver-Muster in JavaScript auf dem Client:

  • Die Komponente wurde möglicherweise nach Ausführung des Bereinigungscodes in Dispose{Async} aus dem DOM entfernt.
  • In einer Blazor Server-App wurde der Blazor-Renderer möglicherweise 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.

JavaScript-Interopaufrufe ohne Verbindung

Dieser Abschnitt gilt nur für Blazor Server-Apps.

JavaScript-Interopaufrufe (JS) können nicht ausgegeben werden, nachdem eine SignalR-Verbindung getrennt wurde. Ohne eine Verbindung während der Komponentenbereinigung oder zu jedem anderen Zeitpunkt, an dem keine Verbindung vorhanden ist, schlagen die folgenden Methodenaufrufe fehl und protokollieren eine Meldung, dass die Verbindung als JSDisconnectedException getrennt wurde:

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 für Komponbereinigung gilt:

  • Die Komponente implementiert IAsyncDisposable.
  • objInstance ist eine IJSObjectReference.
  • 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 (objInstance is not null)
        {
            await objInstance.DisposeAsync();
        }
    }
    catch (JSDisconnectedException)
    {
    }
}

Wenn Sie ihre eigenen JS-Objekte bereinigen oder anderen JS-Code auf dem Client ausführen müssen, nachdem eine Verbindung verloren gegangen ist, verwenden Sie das MutationObserver-Muster in JS auf 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:

Zusätzliche Ressourcen

Informationen zum Aufrufen von .NET-Methoden aus JS finden Sie unter JS.

Fügen Sie für einen Aufruf an JS über .NET die IJSRuntime-Abstraktion ein, und rufen Sie eine der folgenden Methoden auf:

Für die oben genannten .NET-Methoden, die JS-Funktionen aufrufen, gilt Folgendes:

  • Der Funktionsbezeichner (String) ist relativ zum globalen Bereich (window). Der Bezeichner zum Aufrufen von window.someScope.someFunction lautet someScope.someFunction. Die Funktion muss nicht registriert werden, bevor sie aufgerufen wird.
  • Übergeben Sie eine beliebige Anzahl von serialisierbaren JSON-Argumenten in Object[] an eine JS-Funktion.
  • Das Abbruchstoken (CancellationToken) leitet eine Benachrichtigung weiter, dass Vorgänge abgebrochen werden sollen.
  • TimeSpan stellt ein Zeitlimit für einen JS-Vorgang dar.
  • Der Rückgabetyp TValue muss ebenfalls mit JSON serialisierbar sein. TValue sollte mit dem .NET-Typ übereinstimmen, der dem zurückgegebenen JSON-Typ am ehesten entspricht.
  • Ein JS Promise-Objekt wird für InvokeAsync-Methoden zurückgegeben. InvokeAsync entpackt das Promise-Objekt und gibt den Wert zurück, der vom Promise-Objekt erwartet wird.

Für Blazor-Apps mit aktiviertem Voarabrendering sind Aufrufe von JS während des Voarabrenderings nicht möglich. Weitere Informationen finden Sie im Abschnitt Voarabrendering.

Das folgende Beispiel basiert auf TextDecoder, dabei handelt es sich um einen auf JS basierenden Decoder. Im Beispiel wird veranschaulicht, wie eine JS-Funktion aus einer C#-Methode aufgerufen wird, die eine Anforderung aus Entwicklercode in eine vorhandene JS-API auslagert. Die JS-Funktion akzeptiert ein Bytearray von einer C#-Methode, decodiert das Array und gibt den Text zum Anzeigen an die Komponente zurück.

Fügen Sie den folgenden JS-Code in das schließende </body>-Tag von wwwroot/index.html (Blazor WebAssembly) oder Pages/_Host.cshtml (Blazor Server) ein:

<script>
  window.convertArray = (win1251Array) => {
    var win1251decoder = new TextDecoder('windows-1251');
    var bytes = new Uint8Array(win1251Array);
    var decodedArray = win1251decoder.decode(bytes);
    console.log(decodedArray);
    return decodedArray;
  };
</script>

Die folgende CallJsExample1-Komponente:

  • ruft die JS-Funktion convertArray mit InvokeAsync auf, wenn auf eine Schaltfläche geklickt wird (Convert Array).
  • Nachdem die JS-Funktion aufgerufen wurde, wird das übergebene Array in eine Zeichenfolge konvertiert. Die Zeichenfolge wird zur Anzeige an die Komponente zurückgegeben (text).

Pages/CallJsExample1.razor:

@page "/call-js-example-1"
@inject IJSRuntime JS

<h1>Call JS <code>convertArray</code> Function</h1>

<p>
    <button @onclick="ConvertArray">Convert Array</button>
</p>

<p>
    @text
</p>

<p>
    Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>: 
    <a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
    <a href="https://www.imdb.com/name/nm0472710/">David Krumholtz on IMDB</a>
</p>

@code {
    private MarkupString text;

    private uint[] quoteArray = 
        new uint[]
        {
            60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
            116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
            108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
            105, 118, 101, 114, 115, 101, 10, 10,
        };

    private async Task ConvertArray()
    {
        text = new(await JS.InvokeAsync<string>("convertArray", quoteArray));
    }
}

JavaScript-API auf Benutzergesten beschränkt

Dieser Abschnitt gilt nur für -Apps.

Einige JavaScript-APIs (JS) für Browser können nur im Zusammenhang mit einer Benutzergeste ausgeführt werden, z. B. mit JS. Diese APIs können nicht über den JS-Interop-Mechanismus in Blazor Server-Apps aufgerufen werden, da die Behandlung von Ereignissen auf der Benutzeroberfläche asynchron und im Allgemeinen nicht mehr im Kontext der Benutzergeste erfolgt. Die App muss das Ereignis auf der Benutzeroberfläche vollständig in JavaScript verarbeiten. Verwenden Sie daher onclick anstelle des Attributs Blazor der @onclick-Anweisung.

Aufrufen von JavaScript-Funktionen ohne Lesen eines zurückgegebenen Werts (InvokeVoidAsync)

Verwenden Sie InvokeVoidAsync in folgenden Fällen:

  • .NET ist nicht erforderlich, um das Ergebnis eines JS-Aufrufs zu lesen.
  • JS-Funktionen geben JS oder undefined zurück.

Geben Sie im schließenden </body>-Tag von wwwroot/index.html (Blazor WebAssembly) oder Pages/_Host.cshtml (Blazor Server) eine displayTickerAlert1-Funktion von JS an. Die Funktion wird mit InvokeVoidAsync aufgerufen und gibt keinen Wert zurück:

<script>
  window.displayTickerAlert1 = (symbol, price) => {
    alert(`${symbol}: $${price}!`);
  };
</script>

Beispiel (InvokeVoidAsync) für die .razor-Komponente

TickerChanged ruft die handleTickerChanged1-Methode in der folgenden CallJsExample2-Komponente auf.

Pages/CallJsExample2.razor:

@page "/call-js-example-2"
@inject IJSRuntime JS

<h1>Call JS Example 2</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new();
    private string stockSymbol;
    private decimal price;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
    }
}

Beispiel (InvokeVoidAsync) für die .cs-Klasse

JsInteropClasses1.cs:

using System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses1 : IDisposable
{
    private readonly IJSRuntime js;

    public JsInteropClasses1(IJSRuntime js)
    {
        this.js = js;
    }

    public async ValueTask TickerChanged(string symbol, decimal price)
    {
        await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
    }

    public void Dispose()
    {
    }
}

TickerChanged ruft die handleTickerChanged1-Methode in der folgenden CallJsExample3-Komponente auf.

Pages/CallJsExample3.razor:

@page "/call-js-example-3"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call JS Example 3</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new();
    private string stockSymbol;
    private decimal price;
    private JsInteropClasses1 jsClass;

    protected override void OnInitialized()
    {
        jsClass = new(JS);
    }

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        await jsClass.TickerChanged(stockSymbol, price);
    }

    public void Dispose() => jsClass?.Dispose();
}

Aufrufen von JavaScript-Funktionen und Lesen eines zurückgegebenen Werts (InvokeAsync)

Verwenden Sie InvokeAsync, wenn .NET das Ergebnis eines JS-Aufrufs lesen soll.

Geben Sie im schließenden </body>-Tag von wwwroot/index.html (Blazor WebAssembly) oder Pages/_Host.cshtml (Blazor Server) eine displayTickerAlert2-Funktion von JS an. Im folgenden Beispiel wird eine anzuzeigende Zeichenfolge vom Aufrufer zurückgegeben:

<script>
  window.displayTickerAlert2 = (symbol, price) => {
    if (price < 20) {
      alert(`${symbol}: $${price}!`);
      return "User alerted in the browser.";
    } else {
      return "User NOT alerted.";
    }
  };
</script>

Beispiel (InvokeAsync) für die .razor-Komponente

TickerChanged ruft die handleTickerChanged2-Methode auf und zeigt die zurückgegebene Zeichenfolge in der folgenden CallJsExample4-Komponente an.

Pages/CallJsExample4.razor:

@page "/call-js-example-4"
@inject IJSRuntime JS

<h1>Call JS Example 4</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private Random r = new();
    private string stockSymbol;
    private decimal price;
    private string result;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        var interopResult = 
            await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
        result = $"Result of TickerChanged call for {stockSymbol} at " +
            $"{price.ToString("c")}: {interopResult}";
    }
}

Beispiel (InvokeAsync) für die .cs-Klasse

JsInteropClasses2.cs:

using System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses2 : IDisposable
{
    private readonly IJSRuntime js;

    public JsInteropClasses2(IJSRuntime js)
    {
        this.js = js;
    }

    public async ValueTask<string> TickerChanged(string symbol, decimal price)
    {
        return await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
    }

    public void Dispose()
    {
    }
}

TickerChanged ruft die handleTickerChanged2-Methode auf und zeigt die zurückgegebene Zeichenfolge in der folgenden CallJsExample5-Komponente an.

Pages/CallJsExample5.razor:

@page "/call-js-example-5"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call JS Example 5</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private Random r = new();
    private string stockSymbol;
    private decimal price;
    private JsInteropClasses2 jsClass;
    private string result;

    protected override void OnInitialized()
    {
        jsClass = new(JS);
    }

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        var interopResult = await jsClass.TickerChanged(stockSymbol, price);
        result = $"Result of TickerChanged call for {stockSymbol} at " +
            $"{price.ToString("c")}: {interopResult}";
    }

    public void Dispose() => jsClass?.Dispose();
}

Szenarios der dynamischen Inhaltsgenerierung

Verwenden Sie das -Attribut für die dynamische Inhaltsgenerierung mit BuildRenderTree:

[Inject]
IJSRuntime JS { get; set; }

Voarabrendering

Dieser Abschnitt gilt für Blazor Server und gehostete Blazor WebAssembly-Apps, die Razor-Komponenten vorab rendern. Informationen zum Prerendering finden Sie in Prerendering und Integrieren von -Komponenten in ASP.NET Core.

Während eine App vorab gerendert wird, sind bestimmte Aktionen nicht möglich, z. B. Aufrufe in JavaScript (JS).

Im folgenden Beispiel wird die Funktion setElementText1 im Element <head> platziert. Die Funktion wird mit JSRuntimeExtensions.InvokeVoidAsync aufgerufen und gibt keinen Wert zurück.

Hinweis

Allgemeine Anleitungen zum Speicherort von JS und unsere Empfehlungen für Produktions-Apps finden Sie unter ASP.NET Core Blazor JavaScript-Interoperabilität (JS-Interop).

<script>
  window.setElementText1 = (element, text) => element.innerText = text;
</script>

Warnung

Im vorangehenden Beispiel wird das Dokumentobjektmodell (DOM) direkt zu Demonstrationszwecken geändert. Das direkte Ändern des DOM mit JS wird in den meisten Szenarios nicht empfohlen, da JS die Änderungsnachverfolgung von Blazor beeinträchtigen kann. Weitere Informationen finden Sie unter JavaScript-Interoperabilität von in ASP.NET Core (JS-Interoperabilität).

Das OnAfterRender{Async}-Lebenszyklusereignis wird beim Voarabrendering auf dem Server nicht aufgerufen. Überschreiben Sie die OnAfterRender{Async}-Methode, um JS-Interopaufrufe zu verzögern, bis die Komponente nach dem Vorabendering gerendert und auf dem Client interaktiv ist.

Pages/PrerenderedInterop1.razor:

@page "/prerendered-interop-1"
@using Microsoft.JSInterop
@inject IJSRuntime JS

<div @ref="divElement">Text during render</div>

@code {
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JS.InvokeVoidAsync(
                "setElementText1", divElement, "Text after render");
        }
    }
}

Hinweis

Das Beispiel oben „verschmutzt“ den Client mit globalen Methoden. Einen besseren Ansatz für Produktions-Apps finden Sie unter JavaScript-Isolation in JavaScript-Modulen.

Beispiel:

export setElementText1 = (element, text) => element.innerText = text;

Die folgende Komponente veranschaulicht, wie JS-Interop als Teil der Initialisierungslogik einer Komponente auf eine Weise verwendet werden kann, die mit dem Prerendering kompatibel ist. Die Komponente zeigt, dass es möglich ist, in OnAfterRenderAsync ein Renderingupdate zu initiieren. Der Entwickler muss in diesem Szenario unbedingt vermeiden, eine Endlosschleife zu erstellen.

Im folgenden Beispiel wird die Funktion setElementText2 im Element <head> platziert. Die Funktion wird mit IJSRuntime.InvokeAsync aufgerufen und gibt einen Wert zurück.

Hinweis

Allgemeine Anleitungen zum Speicherort von JS und unsere Empfehlungen für Produktions-Apps finden Sie unter ASP.NET Core Blazor JavaScript-Interoperabilität (JS-Interop).

<script>
  window.setElementText2 = (element, text) => {
    element.innerText = text;
    return text;
  };
</script>

Warnung

Im vorangehenden Beispiel wird das Dokumentobjektmodell (DOM) direkt zu Demonstrationszwecken geändert. Das direkte Ändern des DOM mit JS wird in den meisten Szenarios nicht empfohlen, da JS die Änderungsnachverfolgung von Blazor beeinträchtigen kann. Weitere Informationen finden Sie unter JavaScript-Interoperabilität von in ASP.NET Core (JS-Interoperabilität).

Wenn JSRuntime.InvokeAsync aufgerufen wird, wird ElementReference nur in OnAfterRenderAsync und nicht in einer früheren Lebenszyklusmethode verwendet, da es kein JS-Element gibt, bis die Komponente gerendert wird.

StateHasChanged wird aufgerufen, um die Komponente mit dem neuen Zustand, der vom JS-Interop-Aufruf abgerufen wurde, erneut zu rendern. Weitere Informationen erhalten Sie unter Razor-Komponentenrendering in ASP.NET Core. Der Code erstellt keine Endlosschleife, da StateHasChanged nur aufgerufen wird, wenn datanull ist.

Pages/PrerenderedInterop2.razor:

@page "/prerendered-interop-2"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<p>
    Get value via JS interop call:
    <strong id="val-get-by-interop">@(data ?? "No value yet")</strong>
</p>

<p>
    Set value via JS interop call:
</p>

<div id="val-set-by-interop" @ref="divElement"></div>

@code {
    private string? data;
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && data == null)
        {
            data = await JS.InvokeAsync<string>(
                "setElementText2", divElement, "Hello from interop call!");

            StateHasChanged();
        }
    }
}

Hinweis

Das Beispiel oben „verschmutzt“ den Client mit globalen Methoden. Einen besseren Ansatz für Produktions-Apps finden Sie unter JavaScript-Isolation in JavaScript-Modulen.

Beispiel:

export setElementText2 = (element, text) => {
  element.innerText = text;
  return text;
};

Synchrones JS-Interop in Blazor WebAssembly-Apps

Dieser Abschnitt gilt nur für Blazor WebAssembly-Apps.

JS-Interop-Aufrufe sind standardmäßig asynchron, und zwar unabhängig davon, ob der aufgerufene Code synchron oder asynchron ist. Aufrufe sind standardmäßig asynchron, um sicherzustellen, dass Komponenten mit beiden Blazor-Hostingmodellen (Blazor Serverund Blazor WebAssembly) kompatibel sind. Für Blazor Server müssen alle JS-Interopaufrufe asynchron sein, weil sie über eine Netzwerkverbindung gesendet werden.

Wenn Sie sicher sind, dass Ihre App nur mit Blazor WebAssembly ausgeführt wird, können Sie synchrone JS-Interopaufrufe durchführen. Dies ist mit etwas weniger Mehraufwand verbunden als asynchrone Aufrufe und kann zu weniger Renderingzyklen führen, da es keinen Zwischenzustand gibt, während auf die Ergebnisse gewartet wird.

Um einen synchronen Aufruf von .NET zu JavaScript in einer Blazor WebAssembly-App auszuführen, wandeln Sie IJSRuntime in IJSInProcessRuntime um, um den JS-Interopaufruf auszuführen:

@inject IJSRuntime JS

...

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

Wenn Sie in Blazor WebAssembly-Apps mit ASP.NET Core 5.0 oder höher mit IJSObjectReference arbeiten, können Sie IJSInProcessObjectReference stattdessen synchron verwenden. IJSInProcessObjectReference implementiert IAsyncDisposable/IDisposable und sollte für die Garbage Collection verworfen werden, um Arbeitsspeicherverlust zu verhindern, wie das folgende Beispiel veranschaulicht:

...

@inject IJSRuntime JS
@implements IAsyncDisposable

...

@code {
    ...
    private IJSInProcessObjectReference? module;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSInProcessObjectReference>("import", 
            "./scripts.js");
        }
    }

    ...

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

Speicherort von JavaScipt

Laden Sie JavaScript-Code (JS) mithilfe einer der Vorgehensweisen, die im Artikel JS beschrieben werden:

Informationen zum Isolieren von Skripts in -Modulen finden Sie im Abschnitt zur JavaScript-Isolation in JavaScript-Modulen.

Warnung

Platzieren Sie kein <script>-Tag in einer Komponentendatei (.razor), weil das <script>-Tag nicht dynamisch aktualisiert werden kann.

JavaScript-Isolierung in JavaScript-Modulen

Blazor aktiviert die JavaScript-Isolation (JS) in Blazor (JS). Das Laden von JavaScript-Modulen funktioniert in Blazor genauso wie bei anderen Arten von Web-Apps, und Sie können die Definition von Modulen in Ihrer App anpassen. Eine Anleitung zur Verwendung von JavaScript-Modulen finden Sie in den MDN Web Docs: JavaScript modules.

JS-Isolierung bietet die folgenden Vorteile:

  • Importiertes JS verschmutzt nicht mehr den globalen Namespace.
  • Consumer einer Bibliothek und Komponenten müssen nicht das zugehörige JS importieren.

Der dynamische Import mit dem import()-Operator wird mit ASP.NET Core und Blazor unterstützt:

if ({CONDITION}) import("/additionalModule.js");

Im vorherigen Beispiel steht der Platzhalter {CONDITION} für eine bedingte Überprüfung, mit der festgestellt wird, ob das Modul geladen werden soll.

Informationen zur Browserkompatibilität finden Sie unter Can I use: JavaScript modules: dynamic import.

Das folgende JS-Modul exportiert beispielsweise eine JS-Funktion zum Anzeigen eines JS. Platzieren Sie den folgenden JS-Code in einer externen JS-Datei.

wwwroot/scripts.js:

export function showPrompt(message) {
  return prompt(message, 'Type anything here');
}

Fügen Sie das obige JS-Modul als statisches Webobjekt zu einer App oder Klassenbibliothek im wwwroot-Ordner hinzu, und importieren Sie das Modul dann in den .NET-Code, indem Sie InvokeAsync für die IJSRuntime-Instanz aufrufen.

IJSRuntime importiert das Modul als IJSObjectReference, das einen Verweis auf ein JS-Objekt aus .NET-Code darstellt. Verwenden Sie IJSObjectReference, um exportierte JS-Funktionen aus dem Modul aufzurufen.

Pages/CallJsExample6.razor:

@page "/call-js-example-6"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>Call JS Example 6</h1>

<p>
    <button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>

<p>
    @result
</p>

@code {
    private IJSObjectReference module;
    private string result;

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

    private async Task TriggerPrompt()
    {
        result = await Prompt("Provide some text");
    }

    public async ValueTask<string> Prompt(string message)
    {
        return await module.InvokeAsync<string>("showPrompt", message);
    }

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

Im vorherigen Beispiel:

  • Per Konvention handelt es sich beim import-Bezeichner um einen besonderen Bezeichner, der spezifisch zum Importieren eines JS-Moduls verwendet wird.
  • Geben Sie die externe JS-Datei des Moduls mithilfe des stabilen Pfads des statischen Webobjekts an: ./{SCRIPT PATH AND FILE NAME (.js)}. Dabei gilt Folgendes:
    • Das Pfadsegment für das aktuelle Verzeichnis (./) ist erforderlich, damit der korrekte Pfad zu statischen Objekten in der JS-Datei erstellt werden kann.
    • Der Platzhalter {SCRIPT PATH AND FILE NAME (.js)} entspricht dem Pfad und Dateinamen unter wwwroot.

Das dynamische Importieren eines Moduls erfordert eine Netzwerkanforderung, sodass es nur asynchron durch Aufrufen von InvokeAsync erreicht werden kann.

IJSInProcessObjectReference steht für einen Verweis auf ein JS-Objekt, dessen Funktionen synchron in Blazor WebAssembly-Apps aufgerufen werden können. Weitere Informationen finden Sie im Abschnitt Synchrones JS-Interop in Blazor WebAssembly-Apps.

Hinweis

Wenn die externe JS-Datei von einer JS bereitgestellt wird, geben Sie die JS-Datei des Moduls mithilfe des stabilen Pfads des statischen Webobjekts an: ./_content/{PACKAGE ID}/{SCRIPT PATH AND FILE NAME (.js)}:

  • Das Pfadsegment für das aktuelle Verzeichnis (./) ist erforderlich, damit der korrekte Pfad zu statischen Objekten in der JS-Datei erstellt werden kann.
  • Der Platzhalter {PACKAGE ID} ist diePaket-IDder Bibliothek. Die Paket-ID wird standardmäßig auf den Assemblynamen des Projekts festgelegt, wenn <PackageId> nicht in der Projektdatei angegeben ist. Im folgenden Beispiel lautet der Assemblyname der Bibliothek ComponentLibrary, und die Projektdatei der Bibliothek gibt <PackageId> nicht an.
  • Der Platzhalter {SCRIPT PATH AND FILE NAME (.js)} entspricht dem Pfad und Dateinamen unter wwwroot. Im folgenden Beispiel wird die externe JS-Datei (script.js) im wwwroot-Ordner der Klassenbibliothek platziert.
var module = await js.InvokeAsync<IJSObjectReference>(
    "import", "./_content/ComponentLibrary/scripts.js");

Weitere Informationen finden Sie unter Nutzen von ASP.NET Core -Komponenten über eine Razor-Klassenbibliothek (RCL).

Erfassen von Verweisen auf Elemente

Einige JavaScript-Interop-Szenarios (JS) erfordern Verweise auf HTML-Elemente. Beispielsweise erfordert eine Benutzeroberflächenbibliothek möglicherweise einen Elementverweis für die Initialisierung, oder Sie müssen befehlsähnliche APIs für ein Element aufrufen, z. B. click oder play.

Erfassen Sie mithilfe des folgenden Ansatzes Verweise auf HTML-Elemente in einer Komponente:

  • Fügen Sie ein @ref-Attribut zum HTML-Element hinzu.
  • Definieren Sie ein Feld vom Typ ElementReference, dessen Name mit dem Wert des @ref-Attributs übereinstimmt.

Im folgenden Beispiel wird das Erfassen eines Verweises auf das <input>-Element username veranschaulicht:

<input @ref="username" ... />

@code {
    private ElementReference username;
}

Warnung

Verwenden Sie Elementverweise nur, um die Inhalte eines leeren Elements zu ändern, das nicht mit Blazor interagiert. Dieses Szenario ist nützlich, wenn eine Drittanbieter-API Inhalt für das Element bereitstellt. Da Blazor nicht mit dem Element interagiert, kann kein Konflikt zwischen der Darstellung von Blazor des Elements und dem DOM (Dokumentobjektmodell) auftreten.

Im folgenden Beispiel ist es riskant, die Inhalte der unsortierten Liste () zu ändern, weil Blazor mit dem DOM interagiert, um die Listenelemente (<li>) dieses Elements anhand des Todos-Objekts aufzufüllen:

<ul @ref="MyList">
    @foreach (var item in Todos)
    {
        <li>@item.Text</li>
    }
</ul>

Wenn ein JS-Interop-Aufruf die Inhalte des MyList-Elements ändert und Blazor versucht, Diff-Algorithmen auf das Element anzuwenden, stimmen diese nicht mit dem DOM überein.

Weitere Informationen finden Sie unter JavaScript-Interoperabilität von in ASP.NET Core (JS-Interoperabilität).

Eine ElementReference-Struktur wird über einen JS-Interop-Aufruf an den JS-Code übergeben. Der JS-Code empfängt eine HTMLElement-Instanz, die er mit normalen DOM-APIs verwenden kann. Im folgenden Code wird beispielsweise eine .NET-Erweiterungsmethode (TriggerClickEvent) definiert, die das Senden eines Mausklicks an ein Element ermöglicht.

Die JS-Funktion clickElement erstellt ein click-Ereignis an das übergebene HTML-Element (element):

window.interopFunctions = {
  clickElement : function (element) {
    element.click();
  }
}

Verwenden Sie JSRuntimeExtensions.InvokeVoidAsync, um eine JS-Funktion aufzurufen, die keinen Wert zurückgibt. Der folgende Code löst ein clientseitiges click-Ereignis aus, indem die vorherige JS-Funktion mit der erfassten ElementReference-Struktur aufgerufen wird:

@inject IJSRuntime JS

<button @ref="exampleButton">Example Button</button>

<button @onclick="TriggerClick">
    Trigger click event on <code>Example Button</code>
</button>

@code {
    private ElementReference exampleButton;

    public async Task TriggerClick()
    {
        await JS.InvokeVoidAsync(
            "interopFunctions.clickElement", exampleButton);
    }
}

Erstellen Sie eine statische Erweiterungsmethode, die die IJSRuntime-Instanz empfängt, um eine Erweiterungsmethode zu verwenden:

public static async Task TriggerClickEvent(this ElementReference elementRef, 
    IJSRuntime js)
{
    await js.InvokeVoidAsync("interopFunctions.clickElement", elementRef);
}

Die clickElement-Methode wird direkt für das Objekt aufgerufen. Im folgenden Beispiel wird davon ausgegangen, dass die TriggerClickEvent-Methode vom JsInteropClasses-Namespace verfügbar ist:

@inject IJSRuntime JS
@using JsInteropClasses

<button @ref="exampleButton">Example Button</button>

<button @onclick="TriggerClick">
    Trigger click event on <code>Example Button</code>
</button>

@code {
    private ElementReference exampleButton;

    public async Task TriggerClick()
    {
        await exampleButton.TriggerClickEvent(JS);
    }
}

Wichtig

Die exampleButton-Variable wird erst aufgefüllt, nachdem die Komponente gerendert wurde. Wenn eine aufgefüllte ElementReference-Struktur an JS-Code übergeben wird, empfängt der JS-Code einen null-Wert. Sie können Elementverweise bearbeiten, nachdem die Komponente das Rendering abgeschlossen hat, indem Sie die Lebenszyklusmethoden oder OnAfterRender verwenden.

Verwenden Sie ValueTask<TResult>, wenn Sie mit generischen Typen arbeiten und einen Wert zurückgeben:

public static ValueTask<T> GenericMethod<T>(this ElementReference elementRef, 
    IJSRuntime js)
{
    return js.InvokeAsync<T>("{JAVASCRIPT FUNCTION}", elementRef);
}

Der Platzhalter {JAVASCRIPT FUNCTION} ist der Bezeichner für die JS-Funktion.

GenericMethod wird direkt für das Objekt mit einem Typ aufgerufen. Im folgenden Beispiel wird davon ausgegangen, dass die GenericMethod-Methode vom JsInteropClasses-Namespace verfügbar ist:

@inject IJSRuntime JS
@using JsInteropClasses

<input @ref="username" />

<button @onclick="OnClickMethod">Do something generic</button>

<p>
    returnValue: @returnValue
</p>

@code {
    private ElementReference username;
    private string returnValue;

    private async Task OnClickMethod()
    {
        returnValue = await username.GenericMethod<string>(JS);
    }
}

Komponentenübergreifende Verweiselemente

Ein ElementReference kann aus folgenden Gründen nicht zwischen Komponenten übermittelt werden:

Damit eine übergeordnete Komponente einen Elementverweis anderen Komponenten zur Verfügung stellen kann, kann die übergeordnete Komponente:

  • zulassen, dass untergeordnete Komponenten Rückrufe registrieren.
  • die registrierten Rückrufe während des OnAfterRender-Ereignisses mit dem übergebenen Elementverweis aufrufen. Dieser Ansatz ermöglicht untergeordneten Komponenten indirekt die Interaktion mit dem Elementverweis des übergeordneten Elements.

Fügen Sie die folgende Formatvorlage zum Abschnitt <head> der wwwroot/index.html-Datei (Blazor WebAssembly) oder der Pages/_Host.cshtml-Datei (Blazor Server) hinzu:

<style>
    .red { color: red }
</style>

Fügen Sie das folgende Skript innerhalb des schließenden </body>-Tags von wwwroot/index.html (Blazor WebAssembly) oder Pages/_Host.cshtml (Blazor Server) hinzu:

<script>
  function setElementClass(element, className) {
    var myElement = element;
    myElement.classList.add(className);
  }
</script>

Pages/CallJsExample7.razor (übergeordnete Komponente):

@page "/call-js-example-7"

<h1>Call JS Example 7</h1>

<h2 @ref="title">Hello, world!</h2>

Welcome to your new app.

<SurveyPrompt Parent="@this" Title="How is Blazor working for you?" />

Pages/CallJsExample7.razor.cs:

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;

namespace BlazorSample.Pages
{
    public partial class CallJsExample7 : 
        ComponentBase, IObservable<ElementReference>, IDisposable
    {
        private bool disposing;
        private IList<IObserver<ElementReference>> subscriptions = 
            new List<IObserver<ElementReference>>();
        private ElementReference title;

        protected override void OnAfterRender(bool firstRender)
        {
            base.OnAfterRender(firstRender);

            foreach (var subscription in subscriptions)
            {
                try
                {
                    subscription.OnNext(title);
                }
                catch (Exception)
                {
                    throw;
                }
            }
        }

        public void Dispose()
        {
            disposing = true;

            foreach (var subscription in subscriptions)
            {
                try
                {
                    subscription.OnCompleted();
                }
                catch (Exception)
                {
                }
            }

            subscriptions.Clear();
        }

        public IDisposable Subscribe(IObserver<ElementReference> observer)
        {
            if (disposing)
            {
                throw new InvalidOperationException("Parent being disposed");
            }

            subscriptions.Add(observer);

            return new Subscription(observer, this);
        }

        private class Subscription : IDisposable
        {
            public Subscription(IObserver<ElementReference> observer, 
                CallJsExample7 self)
            {
                Observer = observer;
                Self = self;
            }

            public IObserver<ElementReference> Observer { get; }
            public CallJsExample7 Self { get; }

            public void Dispose()
            {
                Self.subscriptions.Remove(Observer);
            }
        }
    }
}

Im vorherigen Beispiel ist BlazorSample der Namespace der App mit Komponenten im Ordner Pages. Wenn Sie den Code lokal testen möchten, müssen Sie den Namespace ändern.

Shared/SurveyPrompt.razor (untergeordnete Komponente):

@inject IJSRuntime JS

<div class="alert alert-secondary mt-4" role="alert">
    <span class="oi oi-pencil mr-2" aria-hidden="true"></span>
    <strong>@Title</strong>

    <span class="text-nowrap">
        Please take our
        <a target="_blank" class="font-weight-bold" 
            href="https://go.microsoft.com/fwlink/?linkid=2109206">brief survey</a>
    </span>
    and tell us what you think.
</div>

@code {
    [Parameter]
    public string Title { get; set; }
}

Shared/SurveyPrompt.razor.cs:

using System;
using Microsoft.AspNetCore.Components;

namespace BlazorSample.Shared
{
    public partial class SurveyPrompt : 
        ComponentBase, IObserver<ElementReference>, IDisposable
    {
        private IDisposable subscription = null;

        [Parameter]
        public IObservable<ElementReference> Parent { get; set; }

        protected override void OnParametersSet()
        {
            base.OnParametersSet();

            subscription?.Dispose();
            subscription = Parent.Subscribe(this);
        }

        public void OnCompleted()
        {
            subscription = null;
        }

        public void OnError(Exception error)
        {
            subscription = null;
        }

        public void OnNext(ElementReference value)
        {
            JS.InvokeAsync<object>(
                "setElementClass", new object[] { value, "red" });
        }

        public void Dispose()
        {
            subscription?.Dispose();
        }
    }
}

Im vorherigen Beispiel ist BlazorSample der Namespace der App mit gemeinsamen Komponenten im Ordner Shared. Wenn Sie den Code lokal testen möchten, müssen Sie den Namespace ändern.

Festschreiben von JavaScript-Interop-Aufrufen

Dieser Abschnitt gilt hauptsächlich für -Apps, jedoch können Blazor WebAssembly-Apps auch JS-Interop-Timeouts festlegen, wenn die Bedingungen dies rechtfertigen.

In Blazor Server-Apps können JavaScript-Interop-Aufrufe (JS) aufgrund von Netzwerkfehlern fehlschlagen, weshalb sie als unzuverlässig betrachtet werden sollten. Blazor Server-Apps verwenden standardmäßig einen einminütigen Timeout für JS-Interop-Aufrufe. Wenn eine App ein engeres Timeout tolerieren kann, legen Sie das Timeout mit einem der folgenden Ansätze fest:

Legen Sie ein globales Timeout in der Startup.ConfigureServices-Methode von Startup.cs mit CircuitOptions.JSInteropDefaultCallTimeout fest:

services.AddServerSideBlazor(
    options => options.JSInteropDefaultCallTimeout = {TIMEOUT});

Der Platzhalter {TIMEOUT} ist ein TimeSpan (z. B. TimeSpan.FromSeconds(80)).

Legen Sie im Komponentencode ein Timeout pro Aufruf fest. Das festgelegte Timeout überschreibt das globale Timeout, das von JSInteropDefaultCallTimeout festgelegt wird:

var result = await JS.InvokeAsync<string>("{ID}", {TIMEOUT}, new[] { "Arg1" });

Im vorherigen Beispiel:

  • Der Platzhalter {TIMEOUT} ist ein TimeSpan (z. B. TimeSpan.FromSeconds(80)).
  • Der Platzhalter {ID} ist der Bezeichner für die aufzurufende Funktion. Der Wert someScope.someFunction ruft beispielsweise die Funktion window.someScope.someFunction auf.

Obwohl Netzwerkfehler in Blazor Server-Apps eine gängige Ursache von JS-Interop-Fehlern sind, können Timeouts pro Aufruf für JS-Interop-Aufrufe in Blazor WebAssembly-Apps festgelegt werden. Obwohl keine SignalR-Leitung in einer Blazor WebAssembly-App vorhanden ist, können JS-Interop-Aufrufe aus anderen Gründen fehlschlagen, die sich auf Blazor WebAssembly-Apps beziehen.

Weitere Informationen zur Ressourcenerschöpfung finden Sie unter Leitfaden zur Bedrohungsabwehr für in ASP.NET Core.

Vermeiden von Objektzirkelbezügen

Objekte, die Zirkelbezüge enthalten, können auf dem Client für folgende Vorgänge nicht serialisiert werden:

  • .NET-Methodenaufrufe.
  • JavaScript-Methodenaufrufe von C#, wenn der Rückgabetyp Zirkelbezüge enthält.

JavaScript-Bibliotheken zum Rendern der Benutzeroberfläche

Möglicherweise möchten Sie manchmal JavaScript-Bibliotheken (JS) verwenden, die sichtbare Benutzeroberflächenelemente im DOM erzeugen. Auf den ersten Blick scheint dies schwierig zu sein, da das Vergleichssystem von Blazor darauf beruht, die Kontrolle über die Struktur von DOM-Elementen zu haben. Außerdem treten Fehler auf, wenn externer Code die DOM-Struktur bearbeitet, was den Mechanismus für die Diff-Anwendung unmöglich macht. Dabei handelt es sich um keine Blazor-spezifische Einschränkung. Die gleiche Herausforderung stellt sich bei jedem anderen Diff-basierten Benutzeroberflächenframework.

Glücklicherweise ist es einfach, extern erstellte Benutzeroberflächenelemente innerhalb der Oberfläche einer Razor-Komponente zuverlässig einzubetten. Die hierfür empfohlene Methode besteht darin, den Code der Komponente (.razor-Datei) ein leeres Element erzeugen zu lassen. Im Vergleichssystem von Blazor ist das Element immer leer, der Renderer durchläuft das Element also nicht rekursiv, sondern berücksichtigt die Inhalte gar nicht. Dies führt dazu, dass das Auffüllen des Elements mit zufälligem extern verwalteten Inhalt sicher ist.

Das folgende Beispiel veranschaulicht das Konzept. Interagieren Sie mithilfe von JS außerhalb von Blazor mit unmanagedElement, wenn firstRender in der if-Anweisung den Wert true aufweist. Rufen Sie beispielsweise eine externe JS-Bibliothek auf, um das Element aufzufüllen. Blazor ignoriert die Inhalte des Elements, bis die Komponente entfernt wurde. Wenn die Komponente entfernt wird, wird die gesamte DOM-Unterstruktur der Komponente ebenfalls entfernt.

<h1>Hello! This is a Razor component rendered at @DateTime.Now</h1>

<div @ref="unmanagedElement"></div>

@code {
    private ElementReference unmanagedElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            ...
        }
    }
}

Betrachten Sie das folgende Beispiel, in dem eine interaktive Karte mithilfe der Open-Source-Mapbox-APIs gerendert wird.

Das folgende JS-Modul wird in der App platziert oder über eine Razor-Klassenbibliothek zur Verfügung gestellt.

Hinweis

Rufen Sie zum Erstellen der Mapbox-Karte ein Zugriffstoken von der Mapbox-Anmeldung ab, und geben Sie dieses an der Stelle an, an der sich im folgenden Code befindet.

wwwroot/mapComponent.js:

import 'https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js';

mapboxgl.accessToken = '{ACCESS TOKEN}';

export function addMapToElement(element) {
  return new mapboxgl.Map({
    container: element,
    style: 'mapbox://styles/mapbox/streets-v11',
    center: [-74.5, 40],
    zoom: 9
  });
}

export function setMapCenter(map, latitude, longitude) {
  map.setCenter([longitude, latitude]);
}

Fügen Sie das folgende Formatvorlagentag zur HTML-Hostseite hinzu, um die korrekte Formatierung zu erzielen.

Fügen Sie in wwwroot/index.html (Blazor WebAssembly) oder Pages/_Host.cshtml (Blazor Server) das folgende <link>-Element zum Markup des <head>-Elements hinzu:

<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css" 
    rel="stylesheet" />

Pages/CallJsExample8.razor:

@page "/call-js-example-8"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>Call JS Example 8</h1>

<div @ref="mapElement" style='width:400px;height:300px'></div>

<button @onclick="() => ShowAsync(51.454514, -2.587910)">Show Bristol, UK</button>
<button @onclick="() => ShowAsync(35.6762, 139.6503)">Show Tokyo, Japan</button>

@code
{
    private ElementReference mapElement;
    private IJSObjectReference mapModule;
    private IJSObjectReference mapInstance;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            mapModule = await JS.InvokeAsync<IJSObjectReference>(
                "import", "./mapComponent.js");
            mapInstance = await mapModule.InvokeAsync<IJSObjectReference>(
                "addMapToElement", mapElement);
        }
    }

    private async Task ShowAsync(double latitude, double longitude)
        => await mapModule.InvokeVoidAsync("setMapCenter", mapInstance, latitude, 
            longitude).AsTask();

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (mapInstance is not null)
        {
            await mapInstance.DisposeAsync();
        }

        if (mapModule is not null)
        {
            await mapModule.DisposeAsync();
        }
    }
}

Im obigen Beispiel wird eine interaktive Kartenbenutzeroberfläche erzeugt. Der Benutzer verfügt über folgende Möglichkeiten:

  • Ziehen der Karte zum Scrollen oder Zoomen
  • Klicken von Schaltflächen zum Springen zu vordefinierten Standorten

Mapbox-Straßenkarte von Tokio in Japan, mit Schaltflächen, über die Bristol im Vereinigten Königreich und Tokio in Japan ausgewählt werden können

Im vorherigen Beispiel:

  • Für Blazor gilt, dass <div> mit @ref="mapElement" leer bleibt. Das mapbox-gl.js-Skript kann das Element sicher auffüllen und dessen Inhalte ändern. Sie können diese Vorgehensweise mit jeder beliebigen JS-Bibliothek verwenden, die eine Benutzeroberfläche rendert. Sie können Komponenten eines JS-SPA-Frameworks eines Drittanbieters in Razor-Komponenten einbetten, solange diese nicht versuchen, andere Teile der Seite zu ändern. Es ist nicht sicher, wenn externer -Code Elemente ändert, die Blazor nicht als leer betrachtet.
  • Bei Verwendung dieses Ansatzes sollten Sie die Regeln im Hinterkopf behalten, wie Blazor DOM-Elemente beibehält oder löscht. Die Komponente verarbeitet Schaltflächen-Klickereignisse auf sichere weise und aktualisiert die vorhandene Karteninstanz, weil DOM-Elemente standardmäßig beibehalten werden, wenn dies möglich ist. Wenn Sie eine Liste von Kartenelementen innerhalb einer @foreach-Schleife rendern, sollten Sie @key verwenden, um für die Beibehaltung der Komponenteninstanzen zu sorgen. Andernfalls könnten Änderungen der Listendaten dazu führen, dass Komponenteninstanzen den Status vorheriger Instanzen auf nicht gewünschte Weise beibehalten. Weitere Informationen finden Sie unter using @key to preserve elements and components.
  • Das Beispiel kapselt JS-Logik und -Abhängigkeiten in einem ES6-Modul und lädt das Modul dynamisch mithilfe des import-Bezeichners. Weitere Informationen finden Sie unter JavaScript-Isolation in JavaScript-Modulen.

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

Dieser Abschnitt gilt nur für Blazor Server-Apps. In Blazor WebAssembly gibt das Framework keine Einschränkungen hinsichtlich der Größe von JavaScript-Interop-Eingaben und -Ausgaben (JS) vor.

In Blazor Server wird die Größe von JS-Interop-Aufrufen durch die maximale SignalR-Nachrichtengröße eingeschränkt, die für Hubmethoden zulässig sind. Dies wird von HubOptions.MaximumReceiveMessageSize erzwungen (Standardwert: 32 KB). SignalR-Nachrichten von JS an .NET, die größer als MaximumReceiveMessageSize sich, lösen einen Fehler aus. Das Framework beinhaltet keine Beschränkungen hinsichtlich der Größe einer SignalR-Nachricht vom Hub an einen Client.

Wenn die SignalR-Protokollierung nicht auf SignalR oder Überwachung festgelegt ist, wird ein Fehler zur Nachrichtengröße nur in der Konsole für Entwicklertools des Browsers angezeigt:

Fehler: Connection disconnected with error „Error: Server returned an error on close: Connection closed with an error.“ (Die Verbindung wurde durch den folgenden Fehler getrennt: „Der Server hat beim Schließen einen Fehler zurückgegeben: Die Verbindung wurde durch einen Fehler beendet.“)

Wenn die serverseitige Protokollierung in auf Debuggen oder Überwachung festgelegt ist, tritt bei der serverseitigen Protokollierung eine -Ausnahme für einen Fehler in Bezug auf die Nachrichtengröße auf.

appsettings.Development.json:

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "Microsoft.AspNetCore.SignalR": "Debug"
    }
  }
}

Error:

System.IO.InvalidDataException: The maximum message size of 32768B was exceeded. The message size can be configured in AddHubOptions. (System.IO.InvalidDataException: Die maximale Nachrichtengröße von 32.768 Byte wurde überschritten. Die Nachrichtengröße kann unter AddHubOptions konfiguriert werden.)

Erhöhen Sie den Grenzwert, indem Sie MaximumReceiveMessageSize in Startup.ConfigureServices festlegen.

services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

Wenn Sie den Grenzwert für die Größe eingehender Nachrichten von SignalR erhöhen, verbrauchen Sie auch mehr Serverressourcen und setzen den Server einem erhöhten Risiko durch böswillige Benutzer aus. Darüber hinaus kann das Lesen sehr großer Inhalte in den Arbeitsspeicher als Zeichenfolgen oder Bytearrays zu Zuordnungen führen, die vom Garbage Collector nur schlecht verarbeitet werden können. Dies kann zu zusätzlichen Leistungseinbußen führen.

Eine Möglichkeit zum Lesen großer Mengen an Nutzdaten besteht darin, den Inhalt in kleineren Blöcken zu senden und die Nutzdaten als Stream zu verarbeiten. Diesen Ansatz können Sie verwenden, wenn große Mengen von JSON-Nutzdaten gelesen werden müssen oder Daten in JS als unformatierte Bytes verfügbar sind. Ein Beispiel für das Senden großer binärer Payloads in Blazor Server, bei dem ähnliche Techniken wie die Blazor Server verwendet werden, finden Sie unter der InputFile.

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)).

Beachten Sie die folgenden Anleitungen, wenn Sie Code zum Übertragen großer Datenmengen zwischen JS und Blazor in Blazor Server-Apps entwickeln:

  • Segmentieren Sie die Daten in kleinere Teile, und senden Sie die Datensegmente sequenziell, bis alle Daten vom Server empfangen wurden.
  • Ordnen Sie in JS- und C#-Code keine großen Objekte zu.
  • Blockieren Sie den hauptsächlichen Benutzeroberflächenthread nicht für lange Zeiträume, wenn Sie Daten senden oder empfangen.
  • Geben Sie belegten Arbeitsspeicher frei, wenn der Prozess beendet oder abgebrochen wird.
  • Erzwingen Sie die folgenden zusätzlichen Anforderungen aus Sicherheitsgründen:
    • Deklarieren Sie die maximale Datei- oder Datengröße, die übermittelt werden kann.
    • Deklarieren Sie die minimale Uploadrate vom Client an den Server.
  • Nachdem die Daten vom Server empfangen wurden, ist mit den Daten Folgendes möglich:
    • Sie können temporär in einem Speicherpuffer gespeichert werden, bis alle Segmente gesammelt wurden.
    • Sie können sofort verarbeitet werden. Beispielsweise können die Daten sofort in einer Datenbank gespeichert oder auf den Datenträger geschrieben werden, wenn die einzelnen Segmente empfangen werden.

JavaScript-Interop-Aufrufe mit rückgängig gemachtem Marshallen

Bei Blazor WebAssembly-Komponenten kann es zu Leistungseinbußen kommen, wenn .NET-Objekte für JavaScript-Interop-Aufrufe (JS) serialisiert werden und eine der folgenden Bedingungen zutrifft:

  • Eine große Menge von .NET-Objekten wird schnell serialisiert. Es kann beispielsweise zu Leistungseinbußen kommen, wenn JS-Interop-Aufrufe auf Grundlage der Bewegung eines Eingabegeräts ausgeführt werden, z. B. beim Drehen eines Mausrads.
  • Große .NET-Objekte bzw. große Mengen von .NET-Objekten müssen für JS-Interop-Aufrufe serialisiert werden. Beispielsweise können Leistungseinbußen auftreten, wenn JS-Interop-Aufrufe die Serialisierung vieler Dateien erfordern.

IJSUnmarshalledObjectReference steht für einen Verweis auf ein JS-Objekt, dessen Funktionen aufgerufen werden können, ohne dass .NET-Daten serialisiert werden müssen.

Im folgenden Beispiel:

  • Eine Struktur, die eine Zeichenfolge und einen Integerwert enthält, wird unserialisiert an übergegeben.
  • JS-Funktionen verarbeiten die Daten und geben entweder einen booleschen Wert oder eine Zeichenfolge an den Aufrufer zurück.
  • Eine JS-Zeichenfolge ist nicht direkt in ein .NET-string-Objekt konvertierbar. Mit der unmarshalledFunctionReturnString-Funktion wird BINDING.js_string_to_mono_string aufgerufen, um die Konvertierung einer JS-Zeichenfolge zu verwalten.

Hinweis

Die folgenden Beispiele stellen keine typischen Anwendungsfälle für dieses Szenario dar, da die Struktur, die an übergeben wird, nicht zu einer schlechten Komponentenleistung führt. Im Beispiel wird ein kleines Objekt verwendet, um nur die Konzepte für das Übergeben von unserialisierten .NET-Daten zu veranschaulichen.

Platzieren Sie den folgenden <script>-Block in wwwroot/index.html (Blazor WebAssembly) oder Pages/_Host.cshtml (Blazor Server). Alternativ können Sie den JS-Block in eine externe JS-Datei platzieren, auf die im schließenden </body>-Tag mit <script src="{SCRIPT PATH AND FILE NAME (.js)}></script> verwiesen wird. Dabei entspricht der Platzhalter {SCRIPT PATH AND FILE NAME (.js)} dem Pfad und Dateinamen des Skripts.

<script>
  window.returnObjectReference = () => {
    return {
      unmarshalledFunctionReturnBoolean: function (fields) {
        const name = Blazor.platform.readStringField(fields, 0);
        const year = Blazor.platform.readInt32Field(fields, 8);

        return name === "Brigadier Alistair Gordon Lethbridge-Stewart" &&
            year === 1968;
      },
      unmarshalledFunctionReturnString: function (fields) {
        const name = Blazor.platform.readStringField(fields, 0);
        const year = Blazor.platform.readInt32Field(fields, 8);

        return BINDING.js_string_to_mono_string(`Hello, ${name} (${year})!`);
      }
    };
  }
</script>

Warnung

Der Name, das Verhalten und das Vorhandensein der js_string_to_mono_string-Funktion kann sich in einer zukünftigen Version von .NET ändern. Beispiel:

  • Die Funktion wird wahrscheinlich umbenannt.
  • Die Funktion selbst kann zugunsten automatischer Konvertierung von Zeichenfolgen durch das Framework entfernt werden.

Pages/CallJsExample10.razor:

@page "/call-js-example-10"
@using System.Runtime.InteropServices
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Call JS Example 10</h1>

@if (callResultForBoolean)
{
    <p>JS interop was successful!</p>
}

@if (!string.IsNullOrEmpty(callResultForString))
{
    <p>@callResultForString</p>
}

<p>
    <button @onclick="CallJSUnmarshalledForBoolean">
        Call Unmarshalled JS & Return Boolean
    </button>
    <button @onclick="CallJSUnmarshalledForString">
        Call Unmarshalled JS & Return String
    </button>
</p>

<p>
    <a href="https://www.doctorwho.tv">Doctor Who</a>
    is a registered trademark of the <a href="https://www.bbc.com/">BBC</a>.
</p>

@code {
    private bool callResultForBoolean;
    private string callResultForString;

    private void CallJSUnmarshalledForBoolean()
    {
        var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;

        var jsUnmarshalledReference = unmarshalledRuntime
            .InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
                "returnObjectReference");

        callResultForBoolean = 
            jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, bool>(
                "unmarshalledFunctionReturnBoolean", GetStruct());
    }

    private void CallJSUnmarshalledForString()
    {
        var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;

        var jsUnmarshalledReference = unmarshalledRuntime
            .InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
                "returnObjectReference");

        callResultForString = 
            jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, string>(
                "unmarshalledFunctionReturnString", GetStruct());
    }

    private InteropStruct GetStruct()
    {
        return new InteropStruct
        {
            Name = "Brigadier Alistair Gordon Lethbridge-Stewart",
            Year = 1968,
        };
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct InteropStruct
    {
        [FieldOffset(0)]
        public string Name;

        [FieldOffset(8)]
        public int Year;
    }
}

Wenn eine IJSUnmarshalledObjectReference-Instanz nicht im C#-Code verworfen wird, kann diese in JS verworfen werden. Die folgende dispose-Funktion verwirft den Objektverweis, wenn er über JS aufgerufen wird:

window.exampleJSObjectReferenceNotDisposedInCSharp = () => {
  return {
    dispose: function () {
      DotNet.disposeJSObjectReference(this);
    },

    ...
  };
}

Arraytypen können mithilfe von js_typed_array_to_array aus JS-Objekten in .NET-Objekte konvertiert werden, aber das JS-Array muss ein typisiertes Array sein. Arrays aus JS können in C#-Code als .NET-Objektarray (object[]) gelesen werden.

Andere Datentypen, z. B. Zeichenfolgenarrays, können konvertiert werden, erfordern jedoch das Erstellen eines neuen Mono-Arrayobjekts (mono_obj_array_new) und das Festlegen seines Werts (mono_obj_array_set).

Warnung

JS-Funktionen, die vom Blazor-Framework bereitgestellt werden (z. B. js_typed_array_to_array, mono_obj_array_new und mono_obj_array_set), unterliegen Namensänderungen, Verhaltensänderungen oder der Entfernung in zukünftigen Releases von .NET.

Abfangen von JavaScript-Ausnahmen

Zum Abfangen von JS-Ausnahmen schließen Sie JS-Interop in einen JS ein, und fangen Sie eine JSException ab.

Im folgenden Beispiel ist die JS-Funktion nonFunction nicht vorhanden. Wenn die Funktion nicht gefunden wird, wird die JSException mit einer Message abgefangen, die den folgenden Fehler angibt:

Could not find 'nonFunction' ('nonFunction' was undefined).

Pages/CallJsExample11.razor:

@page "/call-js-example-11"
@inject IJSRuntime JS

<h1>Call JS Example 11</h1>

<p>
    <button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>

<p>
    @result
</p>

<p>
    @errorMessage
</p>

@code {
    private string errorMessage;
    private string result;

    private async Task CatchUndefinedJSFunction()
    {
        try
        {
            result = await JS.InvokeAsync<string>("nonFunction");
        }
        catch (JSException e)
        {
            errorMessage = $"Error Message: {e.Message}";
        }
    }
}

DOM-Bereinigungsvorgänge (Document Object Model, Dokumentobjektmodell) 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 das MutationObserver-Muster in JavaScript auf dem Client:

  • Die Komponente wurde möglicherweise nach Ausführung des Bereinigungscodes in Dispose{Async} aus dem DOM entfernt.
  • In einer Blazor Server-App wurde der Blazor-Renderer möglicherweise 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.

JavaScript-Interopaufrufe ohne Verbindung

Dieser Abschnitt gilt nur für Blazor Server-Apps.

JavaScript-Interopaufrufe (JS) können nicht ausgegeben werden, nachdem eine SignalR-Verbindung getrennt wurde. Ohne eine Verbindung während der Komponentenbereinigung oder zu jedem anderen Zeitpunkt, an dem keine Verbindung vorhanden ist, schlagen die folgenden Methodenaufrufe fehl und protokollieren eine Meldung, dass die Verbindung als JSDisconnectedException getrennt wurde:

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 für Komponbereinigung gilt:

  • Die Komponente implementiert IAsyncDisposable.
  • objInstance ist eine IJSObjectReference.
  • 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 (objInstance is not null)
        {
            await objInstance.DisposeAsync();
        }
    }
    catch (JSDisconnectedException)
    {
    }
}

Wenn Sie ihre eigenen JS-Objekte bereinigen oder anderen JS-Code auf dem Client ausführen müssen, nachdem eine Verbindung verloren gegangen ist, verwenden Sie das MutationObserver-Muster in JS auf 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:

Zusätzliche Ressourcen

Informationen zum Aufrufen von .NET-Methoden aus JS finden Sie unter JS.

Fügen Sie für einen Aufruf an JS über .NET die IJSRuntime-Abstraktion ein, und rufen Sie eine der folgenden Methoden auf:

Für die oben genannten .NET-Methoden, die JS-Funktionen aufrufen, gilt Folgendes:

  • Der Funktionsbezeichner (String) ist relativ zum globalen Bereich (window). Der Bezeichner zum Aufrufen von window.someScope.someFunction lautet someScope.someFunction. Die Funktion muss nicht registriert werden, bevor sie aufgerufen wird.
  • Übergeben Sie eine beliebige Anzahl von serialisierbaren JSON-Argumenten in Object[] an eine JS-Funktion.
  • Das Abbruchstoken (CancellationToken) leitet eine Benachrichtigung weiter, dass Vorgänge abgebrochen werden sollen.
  • TimeSpan stellt ein Zeitlimit für einen JS-Vorgang dar.
  • Der Rückgabetyp TValue muss ebenfalls mit JSON serialisierbar sein. TValue sollte mit dem .NET-Typ übereinstimmen, der dem zurückgegebenen JSON-Typ am ehesten entspricht.
  • Ein JS Promise-Objekt wird für InvokeAsync-Methoden zurückgegeben. InvokeAsync entpackt das Promise-Objekt und gibt den Wert zurück, der vom Promise-Objekt erwartet wird.

Für Blazor-Apps mit aktiviertem Voarabrendering sind Aufrufe von JS während des Voarabrenderings nicht möglich. Weitere Informationen finden Sie im Abschnitt Voarabrendering.

Das folgende Beispiel basiert auf TextDecoder, dabei handelt es sich um einen auf JS basierenden Decoder. Im Beispiel wird veranschaulicht, wie eine JS-Funktion aus einer C#-Methode aufgerufen wird, die eine Anforderung aus Entwicklercode in eine vorhandene JS-API auslagert. Die JS-Funktion akzeptiert ein Bytearray von einer C#-Methode, decodiert das Array und gibt den Text zum Anzeigen an die Komponente zurück.

Fügen Sie den folgenden JS-Code in das schließende </body>-Tag von wwwroot/index.html (Blazor WebAssembly) oder Pages/_Host.cshtml (Blazor Server) ein:

<script>
  window.convertArray = (win1251Array) => {
    var win1251decoder = new TextDecoder('windows-1251');
    var bytes = new Uint8Array(win1251Array);
    var decodedArray = win1251decoder.decode(bytes);
    console.log(decodedArray);
    return decodedArray;
  };
</script>

Die folgende CallJsExample1-Komponente:

  • ruft die JS-Funktion convertArray mit InvokeAsync auf, wenn auf eine Schaltfläche geklickt wird (Convert Array).
  • Nachdem die JS-Funktion aufgerufen wurde, wird das übergebene Array in eine Zeichenfolge konvertiert. Die Zeichenfolge wird zur Anzeige an die Komponente zurückgegeben (text).

Pages/CallJsExample1.razor:

@page "/call-js-example-1"
@inject IJSRuntime JS

<h1>Call JS <code>convertArray</code> Function</h1>

<p>
    <button @onclick="ConvertArray">Convert Array</button>
</p>

<p>
    @text
</p>

<p>
    Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>: 
    <a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
    <a href="https://www.imdb.com/name/nm0472710/">David Krumholtz on IMDB</a>
</p>

@code {
    private MarkupString text;

    private uint[] quoteArray = 
        new uint[]
        {
            60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
            116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
            108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
            105, 118, 101, 114, 115, 101, 10, 10,
        };

    private async Task ConvertArray()
    {
        text = new MarkupString(await JS.InvokeAsync<string>("convertArray", 
            quoteArray));
    }
}

JavaScript-API auf Benutzergesten beschränkt

Dieser Abschnitt gilt nur für -Apps.

Einige JavaScript-APIs (JS) für Browser können nur im Zusammenhang mit einer Benutzergeste ausgeführt werden, z. B. mit JS. Diese APIs können nicht über den JS-Interop-Mechanismus in Blazor Server-Apps aufgerufen werden, da die Behandlung von Ereignissen auf der Benutzeroberfläche asynchron und im Allgemeinen nicht mehr im Kontext der Benutzergeste erfolgt. Die App muss das Ereignis auf der Benutzeroberfläche vollständig in JavaScript verarbeiten. Verwenden Sie daher onclick anstelle des Attributs Blazor der @onclick-Anweisung.

Aufrufen von JavaScript-Funktionen ohne Lesen eines zurückgegebenen Werts (InvokeVoidAsync)

Verwenden Sie InvokeVoidAsync in folgenden Fällen:

  • .NET ist nicht erforderlich, um das Ergebnis eines JS-Aufrufs zu lesen.
  • JS-Funktionen geben JS oder undefined zurück.

Geben Sie im schließenden </body>-Tag von wwwroot/index.html (Blazor WebAssembly) oder Pages/_Host.cshtml (Blazor Server) eine displayTickerAlert1-Funktion von JS an. Die Funktion wird mit InvokeVoidAsync aufgerufen und gibt keinen Wert zurück:

<script>
  window.displayTickerAlert1 = (symbol, price) => {
    alert(`${symbol}: $${price}!`);
  };
</script>

Beispiel (InvokeVoidAsync) für die .razor-Komponente

TickerChanged ruft die handleTickerChanged1-Methode in der folgenden CallJsExample2-Komponente auf.

Pages/CallJsExample2.razor:

@page "/call-js-example-2"
@inject IJSRuntime JS

<h1>Call JS Example 2</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol != null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new Random();
    private string stockSymbol;
    private decimal price;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
    }
}

Beispiel (InvokeVoidAsync) für die .cs-Klasse

JsInteropClasses1.cs:

using System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses1 : IDisposable
{
    private readonly IJSRuntime js;

    public JsInteropClasses1(IJSRuntime js)
    {
        this.js = js;
    }

    public async ValueTask TickerChanged(string symbol, decimal price)
    {
        await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
    }

    public void Dispose()
    {
    }
}

TickerChanged ruft die handleTickerChanged1-Methode in der folgenden CallJsExample3-Komponente auf.

Pages/CallJsExample3.razor:

@page "/call-js-example-3"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call JS Example 3</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol != null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new Random();
    private string stockSymbol;
    private decimal price;
    private JsInteropClasses1 jsClass;

    protected override void OnInitialized()
    {
        jsClass = new JsInteropClasses1(JS);
    }

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        await jsClass.TickerChanged(stockSymbol, price);
    }

    public void Dispose() => jsClass?.Dispose();
}

Aufrufen von JavaScript-Funktionen und Lesen eines zurückgegebenen Werts (InvokeAsync)

Verwenden Sie InvokeAsync, wenn .NET das Ergebnis eines JS-Aufrufs lesen soll.

Geben Sie im schließenden </body>-Tag von wwwroot/index.html (Blazor WebAssembly) oder Pages/_Host.cshtml (Blazor Server) eine displayTickerAlert2-Funktion von JS an. Im folgenden Beispiel wird eine anzuzeigende Zeichenfolge vom Aufrufer zurückgegeben:

<script>
  window.displayTickerAlert2 = (symbol, price) => {
    if (price < 20) {
      alert(`${symbol}: $${price}!`);
      return "User alerted in the browser.";
    } else {
      return "User NOT alerted.";
    }
  };
</script>

Beispiel (InvokeAsync) für die .razor-Komponente

TickerChanged ruft die handleTickerChanged2-Methode auf und zeigt die zurückgegebene Zeichenfolge in der folgenden CallJsExample4-Komponente an.

Pages/CallJsExample4.razor:

@page "/call-js-example-4"
@inject IJSRuntime JS

<h1>Call JS Example 4</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol != null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result != null)
{
    <p>@result</p>
}

@code {
    private Random r = new Random();
    private string stockSymbol;
    private decimal price;
    private string result;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        var interopResult = 
            await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
        result = $"Result of TickerChanged call for {stockSymbol} at " +
            $"{price.ToString("c")}: {interopResult}";
    }
}

Beispiel (InvokeAsync) für die .cs-Klasse

JsInteropClasses2.cs:

using System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses2 : IDisposable
{
    private readonly IJSRuntime js;

    public JsInteropClasses2(IJSRuntime js)
    {
        this.js = js;
    }

    public async ValueTask<string> TickerChanged(string symbol, decimal price)
    {
        return await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
    }

    public void Dispose()
    {
    }
}

TickerChanged ruft die handleTickerChanged2-Methode auf und zeigt die zurückgegebene Zeichenfolge in der folgenden CallJsExample5-Komponente an.

Pages/CallJsExample5.razor:

@page "/call-js-example-5"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call JS Example 5</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol != null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result != null)
{
    <p>@result</p>
}

@code {
    private Random r = new Random();
    private string stockSymbol;
    private decimal price;
    private JsInteropClasses2 jsClass;
    private string result;

    protected override void OnInitialized()
    {
        jsClass = new JsInteropClasses2(JS);
    }

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        var interopResult = await jsClass.TickerChanged(stockSymbol, price);
        result = $"Result of TickerChanged call for {stockSymbol} at " +
            $"{price.ToString("c")}: {interopResult}";
    }

    public void Dispose() => jsClass?.Dispose();
}

Szenarios der dynamischen Inhaltsgenerierung

Verwenden Sie das -Attribut für die dynamische Inhaltsgenerierung mit BuildRenderTree:

[Inject]
IJSRuntime JS { get; set; }

Voarabrendering

Dieser Abschnitt gilt für Blazor Server und gehostete Blazor WebAssembly-Apps, die Razor-Komponenten vorab rendern. Informationen zum Prerendering finden Sie in Prerendering und Integrieren von -Komponenten in ASP.NET Core.

Während eine App vorab gerendert wird, sind bestimmte Aktionen nicht möglich, z. B. Aufrufe in JavaScript (JS).

Im folgenden Beispiel wird die Funktion setElementText1 im Element <head> platziert. Die Funktion wird mit JSRuntimeExtensions.InvokeVoidAsync aufgerufen und gibt keinen Wert zurück.

Hinweis

Allgemeine Anleitungen zum Speicherort von JS und unsere Empfehlungen für Produktions-Apps finden Sie unter ASP.NET Core Blazor JavaScript-Interoperabilität (JS-Interop).

<script>
  window.setElementText1 = (element, text) => element.innerText = text;
</script>

Warnung

Im vorangehenden Beispiel wird das Dokumentobjektmodell (DOM) direkt zu Demonstrationszwecken geändert. Das direkte Ändern des DOM mit JS wird in den meisten Szenarios nicht empfohlen, da JS die Änderungsnachverfolgung von Blazor beeinträchtigen kann. Weitere Informationen finden Sie unter JavaScript-Interoperabilität von in ASP.NET Core (JS-Interoperabilität).

Das OnAfterRender{Async}-Lebenszyklusereignis wird beim Voarabrendering auf dem Server nicht aufgerufen. Überschreiben Sie die OnAfterRender{Async}-Methode, um JS-Interopaufrufe zu verzögern, bis die Komponente nach dem Vorabendering gerendert und auf dem Client interaktiv ist.

Pages/PrerenderedInterop1.razor:

@page "/prerendered-interop-1"
@using Microsoft.JSInterop
@inject IJSRuntime JS

<div @ref="divElement">Text during render</div>

@code {
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JS.InvokeVoidAsync(
                "setElementText1", divElement, "Text after render");
        }
    }
}

Hinweis

Das Beispiel oben „verschmutzt“ den Client mit globalen Methoden. Einen besseren Ansatz für Produktions-Apps finden Sie unter JavaScript-Isolation in JavaScript-Modulen.

Beispiel:

export setElementText1 = (element, text) => element.innerText = text;

Die folgende Komponente veranschaulicht, wie JS-Interop als Teil der Initialisierungslogik einer Komponente auf eine Weise verwendet werden kann, die mit dem Prerendering kompatibel ist. Die Komponente zeigt, dass es möglich ist, in OnAfterRenderAsync ein Renderingupdate zu initiieren. Der Entwickler muss in diesem Szenario unbedingt vermeiden, eine Endlosschleife zu erstellen.

Im folgenden Beispiel wird die Funktion setElementText2 im Element <head> platziert. Die Funktion wird mit IJSRuntime.InvokeAsync aufgerufen und gibt einen Wert zurück.

Hinweis

Allgemeine Anleitungen zum Speicherort von JS und unsere Empfehlungen für Produktions-Apps finden Sie unter ASP.NET Core Blazor JavaScript-Interoperabilität (JS-Interop).

<script>
  window.setElementText2 = (element, text) => {
    element.innerText = text;
    return text;
  };
</script>

Warnung

Im vorangehenden Beispiel wird das Dokumentobjektmodell (DOM) direkt zu Demonstrationszwecken geändert. Das direkte Ändern des DOM mit JS wird in den meisten Szenarios nicht empfohlen, da JS die Änderungsnachverfolgung von Blazor beeinträchtigen kann. Weitere Informationen finden Sie unter JavaScript-Interoperabilität von in ASP.NET Core (JS-Interoperabilität).

Wenn JSRuntime.InvokeAsync aufgerufen wird, wird ElementReference nur in OnAfterRenderAsync und nicht in einer früheren Lebenszyklusmethode verwendet, da es kein JS-Element gibt, bis die Komponente gerendert wird.

StateHasChanged wird aufgerufen, um die Komponente mit dem neuen Zustand, der vom JS-Interop-Aufruf abgerufen wurde, erneut zu rendern. Weitere Informationen erhalten Sie unter Razor-Komponentenrendering in ASP.NET Core. Der Code erstellt keine Endlosschleife, da StateHasChanged nur aufgerufen wird, wenn datanull ist.

Pages/PrerenderedInterop2.razor:

@page "/prerendered-interop-2"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<p>
    Get value via JS interop call:
    <strong id="val-get-by-interop">@(data ?? "No value yet")</strong>
</p>

<p>
    Set value via JS interop call:
</p>

<div id="val-set-by-interop" @ref="divElement"></div>

@code {
    private string? data;
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && data == null)
        {
            data = await JS.InvokeAsync<string>(
                "setElementText2", divElement, "Hello from interop call!");

            StateHasChanged();
        }
    }
}

Hinweis

Das Beispiel oben „verschmutzt“ den Client mit globalen Methoden. Einen besseren Ansatz für Produktions-Apps finden Sie unter JavaScript-Isolation in JavaScript-Modulen.

Beispiel:

export setElementText2 = (element, text) => {
  element.innerText = text;
  return text;
};

Synchrones JS-Interop in Blazor WebAssembly-Apps

Dieser Abschnitt gilt nur für Blazor WebAssembly-Apps.

JS-Interop-Aufrufe sind standardmäßig asynchron, und zwar unabhängig davon, ob der aufgerufene Code synchron oder asynchron ist. Aufrufe sind standardmäßig asynchron, um sicherzustellen, dass Komponenten mit beiden Blazor-Hostingmodellen (Blazor Serverund Blazor WebAssembly) kompatibel sind. Für Blazor Server müssen alle JS-Interopaufrufe asynchron sein, weil sie über eine Netzwerkverbindung gesendet werden.

Wenn Sie sicher sind, dass Ihre App nur mit Blazor WebAssembly ausgeführt wird, können Sie synchrone JS-Interopaufrufe durchführen. Dies ist mit etwas weniger Mehraufwand verbunden als asynchrone Aufrufe und kann zu weniger Renderingzyklen führen, da es keinen Zwischenzustand gibt, während auf die Ergebnisse gewartet wird.

Um einen synchronen Aufruf von .NET zu JavaScript in einer Blazor WebAssembly-App auszuführen, wandeln Sie IJSRuntime in IJSInProcessRuntime um, um den JS-Interopaufruf auszuführen:

@inject IJSRuntime JS

...

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

Wenn Sie in Blazor WebAssembly-Apps mit ASP.NET Core 5.0 oder höher mit IJSObjectReference arbeiten, können Sie IJSInProcessObjectReference stattdessen synchron verwenden. IJSInProcessObjectReference implementiert IAsyncDisposable/IDisposable und sollte für die Garbage Collection verworfen werden, um Arbeitsspeicherverlust zu verhindern, wie das folgende Beispiel veranschaulicht:

...

@inject IJSRuntime JS
@implements IAsyncDisposable

...

@code {
    ...
    private IJSInProcessObjectReference? module;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSInProcessObjectReference>("import", 
            "./scripts.js");
        }
    }

    ...

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

Speicherort von JavaScipt

Laden Sie JavaScript-Code (JS) mithilfe einer der Vorgehensweisen, die im Artikel JS beschrieben werden:

Warnung

Platzieren Sie kein <script>-Tag in einer Komponentendatei (.razor), weil das <script>-Tag nicht dynamisch aktualisiert werden kann.

Erfassen von Verweisen auf Elemente

Einige JavaScript-Interop-Szenarios (JS) erfordern Verweise auf HTML-Elemente. Beispielsweise erfordert eine Benutzeroberflächenbibliothek möglicherweise einen Elementverweis für die Initialisierung, oder Sie müssen befehlsähnliche APIs für ein Element aufrufen, z. B. click oder play.

Erfassen Sie mithilfe des folgenden Ansatzes Verweise auf HTML-Elemente in einer Komponente:

  • Fügen Sie ein @ref-Attribut zum HTML-Element hinzu.
  • Definieren Sie ein Feld vom Typ ElementReference, dessen Name mit dem Wert des @ref-Attributs übereinstimmt.

Im folgenden Beispiel wird das Erfassen eines Verweises auf das <input>-Element username veranschaulicht:

<input @ref="username" ... />

@code {
    private ElementReference username;
}

Warnung

Verwenden Sie Elementverweise nur, um die Inhalte eines leeren Elements zu ändern, das nicht mit Blazor interagiert. Dieses Szenario ist nützlich, wenn eine Drittanbieter-API Inhalt für das Element bereitstellt. Da Blazor nicht mit dem Element interagiert, kann kein Konflikt zwischen der Darstellung von Blazor des Elements und dem DOM (Dokumentobjektmodell) auftreten.

Im folgenden Beispiel ist es riskant, die Inhalte der unsortierten Liste () zu ändern, weil Blazor mit dem DOM interagiert, um die Listenelemente (<li>) dieses Elements anhand des Todos-Objekts aufzufüllen:

<ul @ref="MyList">
    @foreach (var item in Todos)
    {
        <li>@item.Text</li>
    }
</ul>

Wenn ein JS-Interop-Aufruf die Inhalte des MyList-Elements ändert und Blazor versucht, Diff-Algorithmen auf das Element anzuwenden, stimmen diese nicht mit dem DOM überein.

Weitere Informationen finden Sie unter JavaScript-Interoperabilität von in ASP.NET Core (JS-Interoperabilität).

Eine ElementReference-Struktur wird über einen JS-Interop-Aufruf an den JS-Code übergeben. Der JS-Code empfängt eine HTMLElement-Instanz, die er mit normalen DOM-APIs verwenden kann. Im folgenden Code wird beispielsweise eine .NET-Erweiterungsmethode (TriggerClickEvent) definiert, die das Senden eines Mausklicks an ein Element ermöglicht.

Die JS-Funktion clickElement erstellt ein click-Ereignis an das übergebene HTML-Element (element):

window.interopFunctions = {
  clickElement : function (element) {
    element.click();
  }
}

Verwenden Sie JSRuntimeExtensions.InvokeVoidAsync, um eine JS-Funktion aufzurufen, die keinen Wert zurückgibt. Der folgende Code löst ein clientseitiges click-Ereignis aus, indem die vorherige JS-Funktion mit der erfassten ElementReference-Struktur aufgerufen wird:

@inject IJSRuntime JS

<button @ref="exampleButton">Example Button</button>

<button @onclick="TriggerClick">
    Trigger click event on <code>Example Button</code>
</button>

@code {
    private ElementReference exampleButton;

    public async Task TriggerClick()
    {
        await JS.InvokeVoidAsync(
            "interopFunctions.clickElement", exampleButton);
    }
}

Erstellen Sie eine statische Erweiterungsmethode, die die IJSRuntime-Instanz empfängt, um eine Erweiterungsmethode zu verwenden:

public static async Task TriggerClickEvent(this ElementReference elementRef, 
    IJSRuntime js)
{
    await js.InvokeVoidAsync("interopFunctions.clickElement", elementRef);
}

Die clickElement-Methode wird direkt für das Objekt aufgerufen. Im folgenden Beispiel wird davon ausgegangen, dass die TriggerClickEvent-Methode vom JsInteropClasses-Namespace verfügbar ist:

@inject IJSRuntime JS
@using JsInteropClasses

<button @ref="exampleButton">Example Button</button>

<button @onclick="TriggerClick">
    Trigger click event on <code>Example Button</code>
</button>

@code {
    private ElementReference exampleButton;

    public async Task TriggerClick()
    {
        await exampleButton.TriggerClickEvent(JS);
    }
}

Wichtig

Die exampleButton-Variable wird erst aufgefüllt, nachdem die Komponente gerendert wurde. Wenn eine aufgefüllte ElementReference-Struktur an JS-Code übergeben wird, empfängt der JS-Code einen null-Wert. Sie können Elementverweise bearbeiten, nachdem die Komponente das Rendering abgeschlossen hat, indem Sie die Lebenszyklusmethoden oder OnAfterRender verwenden.

Verwenden Sie ValueTask<TResult>, wenn Sie mit generischen Typen arbeiten und einen Wert zurückgeben:

public static ValueTask<T> GenericMethod<T>(this ElementReference elementRef, 
    IJSRuntime js)
{
    return js.InvokeAsync<T>("{JAVASCRIPT FUNCTION}", elementRef);
}

Der Platzhalter {JAVASCRIPT FUNCTION} ist der Bezeichner für die JS-Funktion.

GenericMethod wird direkt für das Objekt mit einem Typ aufgerufen. Im folgenden Beispiel wird davon ausgegangen, dass die GenericMethod-Methode vom JsInteropClasses-Namespace verfügbar ist:

@inject IJSRuntime JS
@using JsInteropClasses

<input @ref="username" />

<button @onclick="OnClickMethod">Do something generic</button>

<p>
    returnValue: @returnValue
</p>

@code {
    private ElementReference username;
    private string returnValue;

    private async Task OnClickMethod()
    {
        returnValue = await username.GenericMethod<string>(JS);
    }
}

Komponentenübergreifende Verweiselemente

Ein ElementReference kann aus folgenden Gründen nicht zwischen Komponenten übermittelt werden:

Damit eine übergeordnete Komponente einen Elementverweis anderen Komponenten zur Verfügung stellen kann, kann die übergeordnete Komponente:

  • zulassen, dass untergeordnete Komponenten Rückrufe registrieren.
  • die registrierten Rückrufe während des OnAfterRender-Ereignisses mit dem übergebenen Elementverweis aufrufen. Dieser Ansatz ermöglicht untergeordneten Komponenten indirekt die Interaktion mit dem Elementverweis des übergeordneten Elements.

Fügen Sie die folgende Formatvorlage zum Abschnitt <head> der wwwroot/index.html-Datei (Blazor WebAssembly) oder der Pages/_Host.cshtml-Datei (Blazor Server) hinzu:

<style>
    .red { color: red }
</style>

Fügen Sie das folgende Skript innerhalb des schließenden </body>-Tags von wwwroot/index.html (Blazor WebAssembly) oder Pages/_Host.cshtml (Blazor Server) hinzu:

<script>
  function setElementClass(element, className) {
    var myElement = element;
    myElement.classList.add(className);
  }
</script>

Pages/CallJsExample7.razor (übergeordnete Komponente):

@page "/call-js-example-7"

<h1>Call JS Example 7</h1>

<h2 @ref="title">Hello, world!</h2>

Welcome to your new app.

<SurveyPrompt Parent="@this" Title="How is Blazor working for you?" />

Pages/CallJsExample7.razor.cs:

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;

namespace BlazorSample.Pages
{
    public partial class CallJsExample7 : 
        ComponentBase, IObservable<ElementReference>, IDisposable
    {
        private bool disposing;
        private IList<IObserver<ElementReference>> subscriptions = 
            new List<IObserver<ElementReference>>();
        private ElementReference title;

        protected override void OnAfterRender(bool firstRender)
        {
            base.OnAfterRender(firstRender);

            foreach (var subscription in subscriptions)
            {
                try
                {
                    subscription.OnNext(title);
                }
                catch (Exception)
                {
                    throw;
                }
            }
        }

        public void Dispose()
        {
            disposing = true;

            foreach (var subscription in subscriptions)
            {
                try
                {
                    subscription.OnCompleted();
                }
                catch (Exception)
                {
                }
            }

            subscriptions.Clear();
        }

        public IDisposable Subscribe(IObserver<ElementReference> observer)
        {
            if (disposing)
            {
                throw new InvalidOperationException("Parent being disposed");
            }

            subscriptions.Add(observer);

            return new Subscription(observer, this);
        }

        private class Subscription : IDisposable
        {
            public Subscription(IObserver<ElementReference> observer, 
                CallJsExample7 self)
            {
                Observer = observer;
                Self = self;
            }

            public IObserver<ElementReference> Observer { get; }
            public CallJsExample7 Self { get; }

            public void Dispose()
            {
                Self.subscriptions.Remove(Observer);
            }
        }
    }
}

Im vorherigen Beispiel ist BlazorSample der Namespace der App mit Komponenten im Ordner Pages. Wenn Sie den Code lokal testen möchten, müssen Sie den Namespace ändern.

Shared/SurveyPrompt.razor (untergeordnete Komponente):

@inject IJSRuntime JS

<div class="alert alert-secondary mt-4" role="alert">
    <span class="oi oi-pencil mr-2" aria-hidden="true"></span>
    <strong>@Title</strong>

    <span class="text-nowrap">
        Please take our
        <a target="_blank" class="font-weight-bold" 
            href="https://go.microsoft.com/fwlink/?linkid=2109206">brief survey</a>
    </span>
    and tell us what you think.
</div>

@code {
    [Parameter]
    public string Title { get; set; }
}

Shared/SurveyPrompt.razor.cs:

using System;
using Microsoft.AspNetCore.Components;

namespace BlazorSample.Shared
{
    public partial class SurveyPrompt : 
        ComponentBase, IObserver<ElementReference>, IDisposable
    {
        private IDisposable subscription = null;

        [Parameter]
        public IObservable<ElementReference> Parent { get; set; }

        protected override void OnParametersSet()
        {
            base.OnParametersSet();

            subscription?.Dispose();
            subscription = Parent.Subscribe(this);
        }

        public void OnCompleted()
        {
            subscription = null;
        }

        public void OnError(Exception error)
        {
            subscription = null;
        }

        public void OnNext(ElementReference value)
        {
            JS.InvokeAsync<object>(
                "setElementClass", new object[] { value, "red" });
        }

        public void Dispose()
        {
            subscription?.Dispose();
        }
    }
}

Im vorherigen Beispiel ist BlazorSample der Namespace der App mit gemeinsamen Komponenten im Ordner Shared. Wenn Sie den Code lokal testen möchten, müssen Sie den Namespace ändern.

Festschreiben von JavaScript-Interop-Aufrufen

Dieser Abschnitt gilt hauptsächlich für -Apps, jedoch können Blazor WebAssembly-Apps auch JS-Interop-Timeouts festlegen, wenn die Bedingungen dies rechtfertigen.

In Blazor Server-Apps können JavaScript-Interop-Aufrufe (JS) aufgrund von Netzwerkfehlern fehlschlagen, weshalb sie als unzuverlässig betrachtet werden sollten. Blazor Server-Apps verwenden standardmäßig einen einminütigen Timeout für JS-Interop-Aufrufe. Wenn eine App ein engeres Timeout tolerieren kann, legen Sie das Timeout mit einem der folgenden Ansätze fest:

Legen Sie ein globales Timeout in der Startup.ConfigureServices-Methode von Startup.cs mit CircuitOptions.JSInteropDefaultCallTimeout fest:

services.AddServerSideBlazor(
    options => options.JSInteropDefaultCallTimeout = {TIMEOUT});

Der Platzhalter {TIMEOUT} ist ein TimeSpan (z. B. TimeSpan.FromSeconds(80)).

Legen Sie im Komponentencode ein Timeout pro Aufruf fest. Das festgelegte Timeout überschreibt das globale Timeout, das von JSInteropDefaultCallTimeout festgelegt wird:

var result = await JS.InvokeAsync<string>("{ID}", {TIMEOUT}, new[] { "Arg1" });

Im vorherigen Beispiel:

  • Der Platzhalter {TIMEOUT} ist ein TimeSpan (z. B. TimeSpan.FromSeconds(80)).
  • Der Platzhalter {ID} ist der Bezeichner für die aufzurufende Funktion. Der Wert someScope.someFunction ruft beispielsweise die Funktion window.someScope.someFunction auf.

Obwohl Netzwerkfehler in Blazor Server-Apps eine gängige Ursache von JS-Interop-Fehlern sind, können Timeouts pro Aufruf für JS-Interop-Aufrufe in Blazor WebAssembly-Apps festgelegt werden. Obwohl keine SignalR-Leitung in einer Blazor WebAssembly-App vorhanden ist, können JS-Interop-Aufrufe aus anderen Gründen fehlschlagen, die sich auf Blazor WebAssembly-Apps beziehen.

Weitere Informationen zur Ressourcenerschöpfung finden Sie unter Leitfaden zur Bedrohungsabwehr für in ASP.NET Core.

Vermeiden von Objektzirkelbezügen

Objekte, die Zirkelbezüge enthalten, können auf dem Client für folgende Vorgänge nicht serialisiert werden:

  • .NET-Methodenaufrufe.
  • JavaScript-Methodenaufrufe von C#, wenn der Rückgabetyp Zirkelbezüge enthält.

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

Dieser Abschnitt gilt nur für Blazor Server-Apps. In Blazor WebAssembly gibt das Framework keine Einschränkungen hinsichtlich der Größe von JavaScript-Interop-Eingaben und -Ausgaben (JS) vor.

In Blazor Server wird die Größe von JS-Interop-Aufrufen durch die maximale SignalR-Nachrichtengröße eingeschränkt, die für Hubmethoden zulässig sind. Dies wird von HubOptions.MaximumReceiveMessageSize erzwungen (Standardwert: 32 KB). SignalR-Nachrichten von JS an .NET, die größer als MaximumReceiveMessageSize sich, lösen einen Fehler aus. Das Framework beinhaltet keine Beschränkungen hinsichtlich der Größe einer SignalR-Nachricht vom Hub an einen Client.

Wenn die SignalR-Protokollierung nicht auf SignalR oder Überwachung festgelegt ist, wird ein Fehler zur Nachrichtengröße nur in der Konsole für Entwicklertools des Browsers angezeigt:

Fehler: Connection disconnected with error „Error: Server returned an error on close: Connection closed with an error.“ (Die Verbindung wurde durch den folgenden Fehler getrennt: „Der Server hat beim Schließen einen Fehler zurückgegeben: Die Verbindung wurde durch einen Fehler beendet.“)

Wenn die serverseitige Protokollierung in auf Debuggen oder Überwachung festgelegt ist, tritt bei der serverseitigen Protokollierung eine -Ausnahme für einen Fehler in Bezug auf die Nachrichtengröße auf.

appsettings.Development.json:

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "Microsoft.AspNetCore.SignalR": "Debug"
    }
  }
}

Error:

System.IO.InvalidDataException: The maximum message size of 32768B was exceeded. The message size can be configured in AddHubOptions. (System.IO.InvalidDataException: Die maximale Nachrichtengröße von 32.768 Byte wurde überschritten. Die Nachrichtengröße kann unter AddHubOptions konfiguriert werden.)

Erhöhen Sie den Grenzwert, indem Sie MaximumReceiveMessageSize in Startup.ConfigureServices festlegen.

services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

Wenn Sie den Grenzwert für die Größe eingehender Nachrichten von SignalR erhöhen, verbrauchen Sie auch mehr Serverressourcen und setzen den Server einem erhöhten Risiko durch böswillige Benutzer aus. Darüber hinaus kann das Lesen sehr großer Inhalte in den Arbeitsspeicher als Zeichenfolgen oder Bytearrays zu Zuordnungen führen, die vom Garbage Collector nur schlecht verarbeitet werden können. Dies kann zu zusätzlichen Leistungseinbußen führen.

Eine Möglichkeit zum Lesen großer Mengen an Nutzdaten besteht darin, den Inhalt in kleineren Blöcken zu senden und die Nutzdaten als Stream zu verarbeiten. Diesen Ansatz können Sie verwenden, wenn große Mengen von JSON-Nutzdaten gelesen werden müssen oder Daten in JS als unformatierte Bytes verfügbar sind. Ein Beispiel für das Senden großer binärer Payloads in Blazor Server, bei dem ähnliche Techniken wie die Blazor Server verwendet werden, finden Sie unter der InputFile.

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)).

Beachten Sie die folgenden Anleitungen, wenn Sie Code zum Übertragen großer Datenmengen zwischen JS und Blazor in Blazor Server-Apps entwickeln:

  • Segmentieren Sie die Daten in kleinere Teile, und senden Sie die Datensegmente sequenziell, bis alle Daten vom Server empfangen wurden.
  • Ordnen Sie in JS- und C#-Code keine großen Objekte zu.
  • Blockieren Sie den hauptsächlichen Benutzeroberflächenthread nicht für lange Zeiträume, wenn Sie Daten senden oder empfangen.
  • Geben Sie belegten Arbeitsspeicher frei, wenn der Prozess beendet oder abgebrochen wird.
  • Erzwingen Sie die folgenden zusätzlichen Anforderungen aus Sicherheitsgründen:
    • Deklarieren Sie die maximale Datei- oder Datengröße, die übermittelt werden kann.
    • Deklarieren Sie die minimale Uploadrate vom Client an den Server.
  • Nachdem die Daten vom Server empfangen wurden, ist mit den Daten Folgendes möglich:
    • Sie können temporär in einem Speicherpuffer gespeichert werden, bis alle Segmente gesammelt wurden.
    • Sie können sofort verarbeitet werden. Beispielsweise können die Daten sofort in einer Datenbank gespeichert oder auf den Datenträger geschrieben werden, wenn die einzelnen Segmente empfangen werden.

Abfangen von JavaScript-Ausnahmen

Zum Abfangen von JS-Ausnahmen schließen Sie JS-Interop in einen JS ein, und fangen Sie eine JSException ab.

Im folgenden Beispiel ist die JS-Funktion nonFunction nicht vorhanden. Wenn die Funktion nicht gefunden wird, wird die JSException mit einer Message abgefangen, die den folgenden Fehler angibt:

Could not find 'nonFunction' ('nonFunction' was undefined).

Pages/CallJsExample11.razor:

@page "/call-js-example-11"
@inject IJSRuntime JS

<h1>Call JS Example 11</h1>

<p>
    <button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>

<p>
    @result
</p>

<p>
    @errorMessage
</p>

@code {
    private string errorMessage;
    private string result;

    private async Task CatchUndefinedJSFunction()
    {
        try
        {
            result = await JS.InvokeAsync<string>("nonFunction");
        }
        catch (JSException e)
        {
            errorMessage = $"Error Message: {e.Message}";
        }
    }
}

DOM-Bereinigungsvorgänge (Document Object Model, Dokumentobjektmodell) 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 das MutationObserver-Muster in JavaScript auf dem Client:

  • Die Komponente wurde möglicherweise nach Ausführung des Bereinigungscodes in Dispose{Async} aus dem DOM entfernt.
  • In einer Blazor Server-App wurde der Blazor-Renderer möglicherweise 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.

JavaScript-Interopaufrufe ohne Verbindung

Dieser Abschnitt gilt nur für Blazor Server-Apps.

JavaScript-Interopaufrufe (JS) können nicht ausgegeben werden, nachdem eine SignalR-Verbindung getrennt wurde. Ohne eine Verbindung während der Komponentenbereinigung oder zu jedem anderen Zeitpunkt, an dem keine Verbindung vorhanden ist, schlagen die folgenden Methodenaufrufe fehl und protokollieren eine Meldung, dass die Verbindung als JSDisconnectedException getrennt wurde:

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 für Komponbereinigung gilt:

  • Die Komponente implementiert IAsyncDisposable.
  • objInstance ist eine IJSObjectReference.
  • 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 (objInstance is not null)
        {
            await objInstance.DisposeAsync();
        }
    }
    catch (JSDisconnectedException)
    {
    }
}

Wenn Sie ihre eigenen JS-Objekte bereinigen oder anderen JS-Code auf dem Client ausführen müssen, nachdem eine Verbindung verloren gegangen ist, verwenden Sie das MutationObserver-Muster in JS auf 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:

Zusätzliche Ressourcen