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

Dieser Artikel befasst sich mit dem Aufrufen von JavaScript-Funktionen (JS) über .NET. Informationen zum Aufrufen von .NET-Methoden über JS finden Sie unter Aufrufen von .NET-Methoden von JavaScript-Funktionen in ASP.NET Core Blazor.

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 Server-Apps mit aktiviertem Vorabrendern sind Aufrufe von JS während des ersten Vorabrenderns nicht möglich. JS-Interop-Aufrufe müssen verzögert werden, bis die Verbindung mit dem Browser hergestellt wurde. Weitere Informationen finden Sie im Abschnitt Erkennen, wenn für eine Blazor Server-App ein Prerendering durchgeführt wird.

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 &copy;2005 <a href="https://www.uphe.com">Universal Pictures</a>: 
    <a href="https://www.uphe.com/movies/serenity">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));
    }
}
@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 &copy;2005 <a href="https://www.uphe.com">Universal Pictures</a>: 
    <a href="https://www.uphe.com/movies/serenity">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));
    }
}

Speicherort von JavaScipt

Laden Sie JavaScript-Code (JS) mithilfe einer der Vorgehensweisen, die im Artikel Übersicht über die Interoperabilität von JavaScript (JS) beschrieben werden:

Warnung

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

Ausführen von Aufrufen an JavaScript mit IJSRuntime

IJSRuntime stellt eine Instanz einer JavaScript-Runtime (JS) für die Durchführung von JS-Interop-Aufrufen mit JSRuntimeExtensions-Methoden dar.

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 void(0)/void 0 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);
    }
}
@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
{
    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()
    {
    }
}
using System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses1
{
    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();
}
@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 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}";
    }
}
@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
{
    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()
    {
    }
}
using System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses2
{
    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();
}
@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 [Inject]-Attribut für die dynamische Inhaltsgenerierung mit BuildRenderTree:

[Inject]
IJSRuntime JS { get; set; }

Erkennen, wenn für eine Blazor Server-App ein Prerendering durchgeführt wird

Dieser Abschnitt gilt für Blazor Server und gehostete Blazor WebAssembly-Apps, die Razor-Komponenten vorab rendern. Das Vorabrendern wird unter Vorabrendern und Integrieren von ASP.NET Core Razor-Komponenten behandelt.

Während eine App vorab gerendert wird, sind bestimmte Aktionen nicht möglich, z. B. Aufrufe in JavaScript. Komponenten müssen wahrscheinlich unterschiedlich rendern, wenn dafür ein Prerendering durchgeführt wurde.

Im folgenden Beispiel wird die setElementText1 -Funktion im <head> -Element von wwwroot/index.html (Blazor WebAssembly) oder Pages/_Host.cshtml (Blazor Server) platziert. Die Funktion wird mit JSRuntimeExtensions.InvokeVoidAsync aufgerufen und gibt keinen Wert zurück:

<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 JavaScript wird in den meisten Szenarios nicht empfohlen, da JavaScript die Änderungsnachverfolgung von Blazor beeinträchtigen kann.

Überschreiben Sie das OnAfterRender{Async}-Lebenszyklusereignis, um JavaScript-Interop-Aufrufe bis zu einem Punkt zu verzögern, an dem solche Aufrufe garantiert funktionieren. Dieses Ereignis wird erst aufgerufen, wenn die App vollständig gerendert wurde.

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");
        }
    }
}
@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 JavaScript-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 setElementText2 -Funktion im <head> -Element von wwwroot/index.html (Blazor WebAssembly) oder Pages/_Host.cshtml (Blazor Server) platziert. Die Funktion wird mit IJSRuntime.InvokeAsync aufgerufen und gibt einen Wert zurück:

<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 JavaScript wird in den meisten Szenarios nicht empfohlen, da JavaScript die Änderungsnachverfolgung von Blazor beeinträchtigen kann.

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

StateHasChanged wird aufgerufen, um die Komponente mit dem neuen Zustand, der vom JavaScript Interop-Aufruf abgerufen wurde, erneut zu rendern. Weitere Informationen erhalten Sie unter Blazor-Komponentenrendering in ASP.NET Core. Der Code erstellt keine Endlosschleife, da StateHasChanged nur aufgerufen wird, wenn infoFromJs null 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();
        }
    }
}
@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">@(infoFromJs ?? "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 infoFromJs;
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && infoFromJs == null)
        {
            infoFromJs = 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;
};

JavaScript-Isolation in JavaScript-Modulen

Blazor aktiviert die JavaScript-Isolation (JS) in JavaScript-Standardmodulen (ECMAScript-Spezifikation).

Die JS-Isolation bietet die folgenden Vorteile:

  • Importierter JS-Code verunreinigt nicht mehr den globalen Namespace.
  • Consumer einer Bibliothek und von Komponenten müssen nicht den zugehörigen JS-Code importieren.

Das folgende JS-Modul exportiert beispielsweise eine JS-Funktion zum Anzeigen eines Browser-Eingabeaufforderungsfensters. 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-Element, 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()
    {
        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 FILENAME (.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 FILENAME (.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 aufgerufen werden können.

Hinweis

Wenn die externe JS-Datei von einer Razor-Klassenbibliothek bereitgestellt wird, geben Sie die JS-Datei des Moduls mithilfe des stabilen Pfads des statischen Webobjekts an: ./_content/{LIBRARY NAME}/{SCRIPT PATH AND FILENAME (.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 {LIBRARY NAME} ist der Name der Bibliothek. Im folgenden Beispiel lautet der Name der Bibliothek ComponentLibrary.
  • Der Platzhalter {SCRIPT PATH AND FILENAME (.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-Razor-Komponenten aus Razor-Klassenbibliotheken.

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 username <input>-Element 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 (ul) 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.

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 OnAfterRenderAsync 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>("exampleJsFunctions.doSomethingGeneric", elementRef);
}

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?" />
@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);
            }
        }
    }
}
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; }
}
@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();

            if (subscription != null)
            {
                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();
        }
    }
}
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();

            if (subscription != null)
            {
                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 Blazor Server-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 Ressourcenauslastung finden Sie unter Leitfaden zur Bedrohungsabwehr für Blazor Server 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 (JS) von C#, wenn der Rückgabetyp Zirkelbezüge aufweist

Weitere Informationen finden Sie unter Circular references are not supported, take two (dotnet/aspnetcore #20525) (Zirkelbezüge werden nicht unterstützt, die Zweite [dotnet/aspnetcore #20525]).

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 Blazor component rendered at @DateTime.Now</h1>

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

@code {
    private HtmlElement 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 {ACCESS TOKEN} 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()
    {
        await mapInstance.DisposeAsync();
        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 Blazor-Komponenten einbetten, solange diese nicht versuchen, andere Teile der Seite zu ändern. Es ist nicht sicher, wenn externer JS-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 Verwenden von @key zur Beibehaltung von Elementen und Komponenten.
  • 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

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. Weitere Informationen finden Sie unter Aufrufen von .NET-Methoden von JavaScript-Funktionen in ASP.NET Core Blazor.

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 JS ü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 JS ü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.returnJSObjectReference = () => {
    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/CallJsExample9.razor:

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

<h1>Call JS Example 9</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.

Zusätzliche Ressourcen