Aufrufen von JavaScript-Funktionen über .NET-Methoden in ASP.NET Core BlazorCall JavaScript functions from .NET methods in ASP.NET Core Blazor

Von Javier Calvarro Nelson, Daniel Roth und Luke LathamBy Javier Calvarro Nelson, Daniel Roth, and Luke Latham

Eine Blazor-App kann JavaScript-Funktionen über .NET-Methoden und .NET-Methoden über JavaScript-Funktionen aufrufen.A Blazor app can invoke JavaScript functions from .NET methods and .NET methods from JavaScript functions. Diese Szenarios werden als JavaScript-Interoperabilität (JS Interop) bezeichnet.These scenarios are called JavaScript interoperability (JS interop).

In diesem Artikel wird das Aufrufen von JavaScript-Funktionen über .NET behandelt.This article covers invoking JavaScript functions from .NET. Weitere Informationen zum Aufrufen von .NET-Methoden über JavaScript finden Sie unter Aufrufen von .NET-Methoden von JavaScript-Funktionen in ASP.NET Core Blazor.For information on how to call .NET methods from JavaScript, see Aufrufen von .NET-Methoden von JavaScript-Funktionen in ASP.NET Core Blazor.

Anzeigen oder Herunterladen von Beispielcode (Vorgehensweise zum Herunterladen)View or download sample code (how to download)

Verwenden Sie die IJSRuntime-Abstraktion, um JavaScript über .NET aufzurufen.To call into JavaScript from .NET, use the IJSRuntime abstraction. Fügen Sie die IJSRuntime-Abstraktion in Ihre Komponente ein, um JS Interop-Aufrufe auszugeben.To issue JS interop calls, inject the IJSRuntime abstraction in your component. InvokeAsync akzeptiert einen Bezeichner für die JavaScript-Funktion, die Sie aufrufen möchten, sowie eine beliebige Anzahl JSON-serialisierbarer Argumente.InvokeAsync takes an identifier for the JavaScript function that you wish to invoke along with any number of JSON-serializable arguments. Der Funktionsbezeichner ist relativ zum globalen Bereich (window).The function identifier is relative to the global scope (window). Wenn Sie window.someScope.someFunction aufrufen möchten, lautet der Bezeichner someScope.someFunction.If you wish to call window.someScope.someFunction, the identifier is someScope.someFunction. Die Funktion muss nicht registriert werden, bevor sie aufgerufen wird.There's no need to register the function before it's called. Der Rückgabetyp T muss ebenfalls JSON-serialisierbar sein.The return type T must also be JSON serializable. T sollte mit dem .NET-Typ übereinstimmen, der dem zurückgegebenen JSON-Typ am ehesten entspricht.T should match the .NET type that best maps to the JSON type returned.

Bei Blazor Server-Apps mit aktiviertem Prerendering ist das Aufrufen von JavaScript beim ersten Prerendering nicht möglich.For Blazor Server apps with prerendering enabled, calling into JavaScript isn't possible during the initial prerendering. JavaScript-Interoperabilitätsaufrufe müssen verzögert werden, bis die Verbindung mit dem Browser hergestellt ist.JavaScript interop calls must be deferred until after the connection with the browser is established. Weitere Informationen finden Sie im Abschnitt Erkennen, wenn für eine Blazor Server-App ein Prerendering durchgeführt wird.For more information, see the Detect when a Blazor Server app is prerendering section.

Das folgende Beispiel basiert auf TextDecoder, einem auf JavaScript basierenden Decoder.The following example is based on TextDecoder, a JavaScript-based decoder. Im Beispiel wird veranschaulicht, wie eine JavaScript-Funktion aus einer C#-Methode aufgerufen wird, die eine Anforderung aus Entwicklercode in eine vorhandene JavaScript-API auslagert.The example demonstrates how to invoke a JavaScript function from a C# method that offloads a requirement from developer code to an existing JavaScript API. Die JavaScript-Funktion akzeptiert ein Bytearray von einer C#-Methode, decodiert das Array und gibt den Text zum Anzeigen an die Komponente zurück.The JavaScript function accepts a byte array from a C# method, decodes the array, and returns the text to the component for display.

Geben Sie im <head>-Element von wwwroot/index.html (Blazor WebAssembly) oder Pages/_Host.cshtml (Blazor Server) eine JavaScript-Funktion an, die TextDecoder verwendet, um ein übermitteltes Array zu decodieren und den decodierten Wert zurückzugeben:Inside the <head> element of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server), provide a JavaScript function that uses TextDecoder to decode a passed array and return the decoded value:

<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>

JavaScript-Code, z. B. der Code im vorherigen Beispiel, kann auch über eine JavaScript-Datei (.js) mit einem Verweis auf die Skriptdatei geladen werden:JavaScript code, such as the code shown in the preceding example, can also be loaded from a JavaScript file (.js) with a reference to the script file:

<script src="exampleJsInterop.js"></script>

Die folgende Komponente führt folgende Aktionen aus:The following component:

  • Sie ruft die JavaScript-Funktion convertArray mit JSRuntime auf, wenn eine Komponentenschaltfläche ( Convert Array ) ausgewählt wird.Invokes the convertArray JavaScript function using JSRuntime when a component button (Convert Array) is selected.
  • Nachdem die JavaScript-Funktion aufgerufen wurde, wird das übergebene Array in eine Zeichenfolge konvertiert.After the JavaScript function is called, the passed array is converted into a string. Die Zeichenfolge wird zur Anzeige an die Komponente zurückgegeben.The string is returned to the component for display.
@page "/call-js-example"
@inject IJSRuntime JSRuntime;

<h1>Call JavaScript Function Example</h1>

<button type="button" class="btn btn-primary" @onclick="ConvertArray">
    Convert Array
</button>

<p class="mt-2" style="font-size:1.6em">
    <span class="badge badge-success">
        @convertedText
    </span>
</p>

@code {
    // Quote (c)2005 Universal Pictures: Serenity
    // https://www.uphe.com/movies/serenity
    // David Krumholtz on IMDB: https://www.imdb.com/name/nm0472710/

    private MarkupString convertedText =
        new MarkupString("Select the <b>Convert Array</b> button.");

    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()
    {
        var text =
            await JSRuntime.InvokeAsync<string>("convertArray", quoteArray);

        convertedText = new MarkupString(text);
    }
}

IJSRuntimeIJSRuntime

Verwenden Sie einen der folgenden Ansätze, um die IJSRuntime-Abstraktion zu verwenden:To use the IJSRuntime abstraction, adopt any of the following approaches:

  • Fügen Sie die IJSRuntime-Abstraktion in die Razor-Komponente (.razor) ein:Inject the IJSRuntime abstraction into the Razor component (.razor):

    @inject IJSRuntime JSRuntime
    
    @code {
        protected override void OnInitialized()
        {
            StocksService.OnStockTickerUpdated += stockUpdate =>
            {
                JSRuntime.InvokeVoidAsync("handleTickerChanged",
                    stockUpdate.symbol, stockUpdate.price);
            };
        }
    }
    

    Stellen Sie innerhalb des <head>-Elements von wwwroot/index.html (Blazor WebAssembly) oder Pages/_Host.cshtml (Blazor Server) eine handleTickerChanged-JavaScript-Funktion bereit.Inside the <head> element of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server), provide a handleTickerChanged JavaScript function. Die Funktion wird mit JSRuntimeExtensions.InvokeVoidAsync aufgerufen und gibt keinen Wert zurück:The function is called with JSRuntimeExtensions.InvokeVoidAsync and doesn't return a value:

    <script>
      window.handleTickerChanged = (symbol, price) => {
        // ... client-side processing/display code ...
      };
    </script>
    
  • Fügen Sie die IJSRuntime-Abstraktion in eine Klasse (.cs) ein:Inject the IJSRuntime abstraction into a class (.cs):

    public class JsInteropClasses
    {
        private readonly IJSRuntime jsRuntime;
    
        public JsInteropClasses(IJSRuntime jsRuntime)
        {
            this.jsRuntime = jsRuntime;
        }
    
        public ValueTask<string> TickerChanged(string data)
        {
            return jsRuntime.InvokeAsync<string>(
                "handleTickerChanged",
                stockUpdate.symbol,
                stockUpdate.price);
        }
    }
    

    Stellen Sie innerhalb des <head>-Elements von wwwroot/index.html (Blazor WebAssembly) oder Pages/_Host.cshtml (Blazor Server) eine handleTickerChanged-JavaScript-Funktion bereit.Inside the <head> element of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server), provide a handleTickerChanged JavaScript function. Die Funktion wird mit JSRuntime.InvokeAsync aufgerufen und gibt einen Wert zurück:The function is called with JSRuntime.InvokeAsync and returns a value:

    <script>
      window.handleTickerChanged = (symbol, price) => {
        // ... client-side processing/display code ...
        return 'Done!';
      };
    </script>
    
  • Verwenden Sie das [Inject]-Attribut für die dynamische Inhaltsgenerierung mit BuildRenderTree:For dynamic content generation with BuildRenderTree, use the [Inject] attribute:

    [Inject]
    IJSRuntime JSRuntime { get; set; }
    

In der clientseitigen Beispiel-App zu diesem Artikel stehen der App zwei JavaScript-Funktionen zur Verfügung, die mit dem DOM (Dokumentobjektmodell) interagieren, um Benutzereingaben zu empfangen und eine Begrüßungsnachricht anzuzeigen:In the client-side sample app that accompanies this topic, two JavaScript functions are available to the app that interact with the DOM to receive user input and display a welcome message:

  • showPrompt: Generiert eine Eingabeaufforderung zum Akzeptieren der Benutzereingaben (dem Namen des Benutzers) und gibt den Namen an den Aufrufer zurück.showPrompt: Produces a prompt to accept user input (the user's name) and returns the name to the caller.
  • displayWelcome: Weist einem DOM-Objekt eine Begrüßungsnachricht vom Aufrufer mit einer id mit dem Wert welcome zu.displayWelcome: Assigns a welcome message from the caller to a DOM object with an id of welcome.

wwwroot/exampleJsInterop.js:wwwroot/exampleJsInterop.js:

window.exampleJsFunctions = {
  showPrompt: function (text) {
    return prompt(text, 'Type your name here');
  },
  displayWelcome: function (welcomeMessage) {
    document.getElementById('welcome').innerText = welcomeMessage;
  },
  returnArrayAsyncJs: function () {
    DotNet.invokeMethodAsync('BlazorSample', 'ReturnArrayAsync')
      .then(data => {
        data.push(4);
          console.log(data);
      });
  },
  sayHello: function (dotnetHelper) {
    return dotnetHelper.invokeMethodAsync('SayHello')
      .then(r => console.log(r));
  }
};

Platzieren Sie das <script>-Tag, das auf die JavaScript-Datei verweist, in der wwwroot/index.html-Datei (Blazor WebAssembly) oder der Pages/_Host.cshtml-Datei (Blazor Server).Place the <script> tag that references the JavaScript file in the wwwroot/index.html file (Blazor WebAssembly) or Pages/_Host.cshtml file (Blazor Server).

wwwroot/index.html (Blazor WebAssembly):wwwroot/index.html (Blazor WebAssembly):

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
    <title>Blazor WebAssembly Sample</title>
    <base href="/" />
    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    <link href="css/app.css" rel="stylesheet" />
</head>

<body>
    <app>Loading...</app>

    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>
    <script src="_framework/blazor.webassembly.js"></script>
    <script src="exampleJsInterop.js"></script>
</body>

</html>

Pages/_Host.cshtml (Blazor Server):Pages/_Host.cshtml (Blazor Server):

@page "/"
@namespace BlazorSample.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
    Layout = null;
}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Blazor Server Sample</title>
    <base href="~/" />
    <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
    <link href="css/site.css" rel="stylesheet" />
</head>
<body>
    <app>
        <component type="typeof(App)" render-mode="ServerPrerendered" />
    </app>

    <div id="blazor-error-ui">
        <environment include="Staging,Production">
            An error has occurred. This application may no longer respond until reloaded.
        </environment>
        <environment include="Development">
            An unhandled exception has occurred. See browser dev tools for details.
        </environment>
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>

    <script src="_framework/blazor.server.js"></script>
    <script src="exampleJsInterop.js"></script>
</body>
</html>

Platzieren Sie kein <script>-Tag in einer Komponentendatei, weil das <script>-Tag nicht dynamisch aktualisiert werden kann.Don't place a <script> tag in a component file because the <script> tag can't be updated dynamically.

Die Interoperabilität von .NET-Methoden mit den JavaScript-Funktionen in der Datei exampleJsInterop.js erfolgt, indem IJSRuntime.InvokeAsync aufgerufen wird..NET methods interop with the JavaScript functions in the exampleJsInterop.js file by calling IJSRuntime.InvokeAsync.

Die IJSRuntime-Abstraktion ist asynchron, um Blazor Server-Szenarios zu ermöglichen.The IJSRuntime abstraction is asynchronous to allow for Blazor Server scenarios. Wenn es sich bei der App um eine Blazor WebAssembly-App handelt, sollten Sie die JavaScript-Funktion synchron aufrufen, eine Typumwandlung nach unten in IJSInProcessRuntime durchführen und stattdessen Invoke aufrufen.If the app is a Blazor WebAssembly app and you want to invoke a JavaScript function synchronously, downcast to IJSInProcessRuntime and call Invoke instead. Es wird empfohlen, für die meisten JS Interop-Bibliotheken asynchrone APIs zu verwenden, um sicherzustellen, dass die Bibliotheken in allen Szenarios verfügbar sind.We recommend that most JS interop libraries use the async APIs to ensure that the libraries are available in all scenarios.

Die Beispiel-App enthält eine Komponente zur Veranschaulichung der JavaScript-Interoperabilität.The sample app includes a component to demonstrate JS interop. Die Komponente:The component:

  • empfängt Benutzereingaben über eine JavaScript-Eingabeaufforderung.Receives user input via a JavaScript prompt.
  • gibt den Text zur Verarbeitung an die Komponente zurück.Returns the text to the component for processing.
  • ruft eine zweite JavaScript-Funktion auf, die mit dem DOM interagiert, um eine Begrüßungsnachricht anzuzeigen.Calls a second JavaScript function that interacts with the DOM to display a welcome message.

Pages/JsInterop.razor:Pages/JsInterop.razor:

@page "/JSInterop"
@using {APP ASSEMBLY}.JsInteropClasses
@inject IJSRuntime JSRuntime

<h1>JavaScript Interop</h1>

<h2>Invoke JavaScript functions from .NET methods</h2>

<button type="button" class="btn btn-primary" @onclick="TriggerJsPrompt">
    Trigger JavaScript Prompt
</button>

<h3 id="welcome" style="color:green;font-style:italic"></h3>

@code {
    public async Task TriggerJsPrompt()
    {
        var name = await JSRuntime.InvokeAsync<string>(
                "exampleJsFunctions.showPrompt",
                "What's your name?");

        await JSRuntime.InvokeVoidAsync(
                "exampleJsFunctions.displayWelcome",
                $"Hello {name}! Welcome to Blazor!");
    }
}

Der Platzhalter {APP ASSEMBLY} ist der App-Assemblyname der App (z. B. BlazorSample).The placeholder {APP ASSEMBLY} is the app's app assembly name (for example, BlazorSample).

  1. Wenn TriggerJsPrompt ausgeführt wird, indem die Schaltfläche Trigger JavaScript Prompt der Komponente ausgewählt wird, wird die showPrompt-Funktion von JavaScript aufgerufen, die in der wwwroot/exampleJsInterop.js-Datei bereitgestellt wird.When TriggerJsPrompt is executed by selecting the component's Trigger JavaScript Prompt button, the JavaScript showPrompt function provided in the wwwroot/exampleJsInterop.js file is called.
  2. Die showPrompt-Funktion akzeptiert Benutzereingaben (den Namen des Benutzers), die HTML-codiert und an die Komponente zurückgegeben werden.The showPrompt function accepts user input (the user's name), which is HTML-encoded and returned to the component. Die Komponente speichert den Namen des Benutzers in der lokalen Variable name.The component stores the user's name in a local variable, name.
  3. Die in name gespeicherte Zeichenfolge wird in eine Begrüßungsnachricht eingebunden, die an die JavaScript-Funktion displayWelcome übergeben wird, die die Begrüßungsnachricht in ein Überschriftentag rendert.The string stored in name is incorporated into a welcome message, which is passed to a JavaScript function, displayWelcome, which renders the welcome message into a heading tag.

Aufrufen der JavaScript-Funktion „void“Call a void JavaScript function

JavaScript-Funktionen, die void(0)/void 0 oder undefined zurückgeben, werden mit JSRuntimeExtensions.InvokeVoidAsync aufgerufen.JavaScript functions that return void(0)/void 0 or undefined are called with JSRuntimeExtensions.InvokeVoidAsync.

Erkennen, wenn für eine Blazor Server-App ein Prerendering durchgeführt wirdDetect when a Blazor Server app is prerendering



Während für eine Blazor Server-App ein Prerendering erfolgt, sind bestimmte Aktionen (z. B. Aufrufe in JavaScript) nicht möglich, da noch keine Verbindung mit dem Browser hergestellt wurde.While a Blazor Server app is prerendering, certain actions, such as calling into JavaScript, aren't possible because a connection with the browser hasn't been established. Komponenten müssen wahrscheinlich unterschiedlich rendern, wenn dafür ein Prerendering durchgeführt wurde.Components may need to render differently when prerendered.

Wenn Sie JavaScript-Interop-Aufrufe bis nach Herstellung der Verbindung zum Browser verzögern möchten, können Sie das Lebenszyklusereignis der OnAfterRenderAsync-Komponente verwenden.To delay JavaScript interop calls until after the connection with the browser is established, you can use the OnAfterRenderAsync component lifecycle event. Dieses Ereignis wird nur aufgerufen, nachdem die App vollständig gerendert und die Clientverbindung hergestellt wurde.This event is only called after the app is fully rendered and the client connection is established.

@using Microsoft.JSInterop
@inject IJSRuntime JSRuntime

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

@code {
    private ElementReference divElement;

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

Stellen Sie für den obigen Beispielcode eine setElementText-JavaScript-Funktion im <head>-Element von wwwroot/index.html (Blazor WebAssembly) oder Pages/_Host.cshtml (Blazor Server) bereit.For the preceding example code, provide a setElementText JavaScript function inside the <head> element of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server). Die Funktion wird mit JSRuntimeExtensions.InvokeVoidAsync aufgerufen und gibt keinen Wert zurück:The function is called with JSRuntimeExtensions.InvokeVoidAsync and doesn't return a value:

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

Warnung

Im vorangehenden Beispiel wird das Dokumentobjektmodell (DOM) direkt zu Demonstrationszwecken geändert.The preceding example modifies the Document Object Model (DOM) directly for demonstration purposes only. Das direkte Ändern des DOM mit JavaScript wird in den meisten Szenarios nicht empfohlen, da JavaScript die Änderungsnachverfolgung von Blazor beeinträchtigen kann.Directly modifying the DOM with JavaScript isn't recommended in most scenarios because JavaScript can interfere with Blazor's change tracking.

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.The following component demonstrates how to use JavaScript interop as part of a component's initialization logic in a way that's compatible with prerendering. Die Komponente zeigt, dass es möglich ist, in OnAfterRenderAsync ein Renderingupdate zu initiieren.The component shows that it's possible to trigger a rendering update from inside OnAfterRenderAsync. Der Entwickler muss in diesem Szenario vermeiden, eine Endlosschleife zu erstellen.The developer must avoid creating an infinite loop in this scenario.

Wenn JSRuntime.InvokeAsync aufgerufen wird, wird ElementRef nur in OnAfterRenderAsync und nicht in einer früheren Lebenszyklusmethode verwendet, da es kein JavaScript-Element gibt, bis die Komponente gerendert wird.Where JSRuntime.InvokeAsync is called, ElementRef is only used in OnAfterRenderAsync and not in any earlier lifecycle method because there's no JavaScript element until after the component is rendered.

StateHasChanged wird aufgerufen, um die Komponente mit dem neuen Zustand, der vom JavaScript-Interop-Aufruf abgerufen wurde, erneut zu überarbeiten.StateHasChanged is called to rerender the component with the new state obtained from the JavaScript interop call. Der Code erstellt keine Endlosschleife, da StateHasChanged nur aufgerufen wird, wenn infoFromJs null ist.The code doesn't create an infinite loop because StateHasChanged is only called when infoFromJs is null.

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

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

Set value via JS interop call:
<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 JSRuntime.InvokeAsync<string>(
                "setElementText", divElement, "Hello from interop call!");

            StateHasChanged();
        }
    }
}

Stellen Sie für den obigen Beispielcode eine setElementText-JavaScript-Funktion im <head>-Element von wwwroot/index.html (Blazor WebAssembly) oder Pages/_Host.cshtml (Blazor Server) bereit.For the preceding example code, provide a setElementText JavaScript function inside the <head> element of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server). Die Funktion wird mit IJSRuntime.InvokeAsync aufgerufen und gibt einen Wert zurück:The function is called withIJSRuntime.InvokeAsync and returns a value:

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

Warnung

Im vorangehenden Beispiel wird das Dokumentobjektmodell (DOM) direkt zu Demonstrationszwecken geändert.The preceding example modifies the Document Object Model (DOM) directly for demonstration purposes only. Das direkte Ändern des DOM mit JavaScript wird in den meisten Szenarios nicht empfohlen, da JavaScript die Änderungsnachverfolgung von Blazor beeinträchtigen kann.Directly modifying the DOM with JavaScript isn't recommended in most scenarios because JavaScript can interfere with Blazor's change tracking.

Erfassen von Verweisen auf ElementeCapture references to elements

Einige JS Interop-Szenarios erfordern Verweise auf HTML-Elemente.Some JS interop scenarios require references to HTML elements. 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. focus oder play.For example, a UI library may require an element reference for initialization, or you might need to call command-like APIs on an element, such as focus or play.

Erfassen Sie mithilfe des folgenden Ansatzes Verweise auf HTML-Elemente in einer Komponente:Capture references to HTML elements in a component using the following approach:

  • Fügen Sie ein @ref-Attribut zum HTML-Element hinzu.Add an @ref attribute to the HTML element.
  • Definieren Sie ein Feld vom Typ ElementReference, dessen Name mit dem Wert des @ref-Attributs übereinstimmt.Define a field of type ElementReference whose name matches the value of the @ref attribute.

Im folgenden Beispiel wird das Erfassen eines Verweises auf das username <input>-Element veranschaulicht:The following example shows capturing a reference to the username <input> element:

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

@code {
    ElementReference username;
}

Warnung

Verwenden Sie Elementverweise nur, um die Inhalte eines leeren Elements zu ändern, das nicht mit Blazor interagiert.Only use an element reference to mutate the contents of an empty element that doesn't interact with Blazor. Dieses Szenario ist nützlich, wenn eine Drittanbieter-API Inhalt für das Element bereitstellt.This scenario is useful when a third-party API supplies content to the element. Da Blazor nicht mit dem Element interagiert, kann kein Konflikt zwischen der Darstellung von Blazor des Elements und dem DOM auftreten.Because Blazor doesn't interact with the element, there's no possibility of a conflict between Blazor's representation of the element and the DOM.

Im folgenden Beispiel ist es gefährlich, die Inhalte der unsortierten Liste (ul) zu ändern, weil Blazor mit dem DOM interagiert, um die Listenelemente (<li>) dieses Elements aufzufüllen:In the following example, it's dangerous to mutate the contents of the unordered list (ul) because Blazor interacts with the DOM to populate this element's list items (<li>):

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

Wenn die Inhalte des Elements MyList durch JS Interop geändert werden und Blazor versucht, diffs-Algorithmen auf das Element anzuwenden, entsprechen diese nicht dem DOM.If JS interop mutates the contents of element MyList and Blazor attempts to apply diffs to the element, the diffs won't match the DOM.

Bei .NET-Code handelt es sich bei ElementReference um einen nicht transparenten Handle.As far as .NET code is concerned, an ElementReference is an opaque handle. Das Einzige, was Sie mit ElementReference tun können, ist, es per JS Interop an JavaScript-Code zu übergeben.The only thing you can do with ElementReference is pass it through to JavaScript code via JS interop. Dadurch empfängt der JavaScript-seitige Code eine HTMLElement-Instanz, die er mit normalen DOM-APIs verwenden kann.When you do so, the JavaScript-side code receives an HTMLElement instance, which it can use with normal DOM APIs.

Im folgenden Code wird beispielsweise eine .NET-Erweiterungsmethode definiert, die das Festlegen des Fokus auf ein Element ermöglicht:For example, the following code defines a .NET extension method that enables setting the focus on an element:

exampleJsInterop.js:exampleJsInterop.js:

window.exampleJsFunctions = {
  focusElement : function (element) {
    element.focus();
  }
}

Verwenden Sie JSRuntimeExtensions.InvokeVoidAsync, um eine JavaScript-Funktion aufzurufen, die keinen Wert zurückgibt.To call a JavaScript function that doesn't return a value, use JSRuntimeExtensions.InvokeVoidAsync. Mit dem folgenden Code wird der Fokus auf die Eingabe des Benutzernamens festgelegt, indem die vorherige JavaScript-Funktion mit dem erfassten ElementReference-Wert aufgerufen wird:The following code sets the focus on the username input by calling the preceding JavaScript function with the captured ElementReference:

@inject IJSRuntime JSRuntime

<input @ref="username" />
<button @onclick="SetFocus">Set focus on username</button>

@code {
    private ElementReference username;

    public async Task SetFocus()
    {
        await JSRuntime.InvokeVoidAsync(
            "exampleJsFunctions.focusElement", username);
    }
}

Erstellen Sie eine statische Erweiterungsmethode, die die IJSRuntime-Instanz empfängt, um eine Erweiterungsmethode zu verwenden:To use an extension method, create a static extension method that receives the IJSRuntime instance:

public static async Task Focus(this ElementReference elementRef, IJSRuntime jsRuntime)
{
    await jsRuntime.InvokeVoidAsync(
        "exampleJsFunctions.focusElement", elementRef);
}

Die Focus-Methode wird direkt für das Objekt aufgerufen.The Focus method is called directly on the object. Im folgenden Beispiel wird davon ausgegangen, dass die Focus-Methode vom JsInteropClasses-Namespace verfügbar ist:The following example assumes that the Focus method is available from the JsInteropClasses namespace:

@inject IJSRuntime JSRuntime
@using JsInteropClasses

<input @ref="username" />
<button @onclick="SetFocus">Set focus on username</button>

@code {
    private ElementReference username;

    public async Task SetFocus()
    {
        await username.Focus(JSRuntime);
    }
}

Wichtig

Die username-Variable wird erst aufgefüllt, nachdem die Komponente gerendert wurde.The username variable is only populated after the component is rendered. Wenn ein nicht aufgefüllter ElementReference-Wert an JavaScript-Code übergeben wird, empfängt der JavaScript-Code den Wert null.If an unpopulated ElementReference is passed to JavaScript code, the JavaScript code receives a value of null. Um Elementverweise zu ändern, nachdem die Komponente gerendert wurde (um den anfänglichen Fokus für ein Element festzulegen), verwenden Sie die OnAfterRenderAsync- oder OnAfterRender-Komponentenlebenszyklus-Methoden .To manipulate element references after the component has finished rendering (to set the initial focus on an element) use the OnAfterRenderAsync or OnAfterRender component lifecycle methods.

Verwenden Sie ValueTask<TResult>, wenn Sie mit generischen Typen arbeiten und einen Wert zurückgeben:When working with generic types and returning a value, use ValueTask<TResult>:

public static ValueTask<T> GenericMethod<T>(this ElementReference elementRef, 
    IJSRuntime jsRuntime)
{
    return jsRuntime.InvokeAsync<T>(
        "exampleJsFunctions.doSomethingGeneric", elementRef);
}

GenericMethod wird direkt für das Objekt mit einem Typ aufgerufen.GenericMethod is called directly on the object with a type. Im folgenden Beispiel wird davon ausgegangen, dass die GenericMethod-Methode vom JsInteropClasses-Namespace verfügbar ist:The following example assumes that the GenericMethod is available from the JsInteropClasses namespace:

@inject IJSRuntime JSRuntime
@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>(JSRuntime);
    }
}

Komponentenübergreifende VerweiselementeReference elements across components

ElementReference ist nur in der OnAfterRender-Methode einer Komponente garantiert gültig (und ein Elementverweis ist ein struct), daher kann ein Elementverweis nicht zwischen Komponenten übergeben werden.An ElementReference is only guaranteed valid in a component's OnAfterRender method (and an element reference is a struct), so an element reference can't be passed between components.

Damit eine übergeordnete Komponente einen Elementverweis anderen Komponenten zur Verfügung stellen kann, kann die übergeordnete Komponente:For a parent component to make an element reference available to other components, the parent component can:

  • zulassen, dass untergeordnete Komponenten Rückrufe registrieren.Allow child components to register callbacks.
  • die registrierten Rückrufe während des OnAfterRender-Ereignisses mit dem übergebenen Elementverweis aufrufen.Invoke the registered callbacks during the OnAfterRender event with the passed element reference. Dieser Ansatz ermöglicht untergeordneten Komponenten indirekt die Interaktion mit dem Elementverweis des übergeordneten Elements.Indirectly, this approach allows child components to interact with the parent's element reference.

Dieser Ansatz wird anhand des folgenden Blazor WebAssembly-Beispiels veranschaulicht.The following Blazor WebAssembly example illustrates the approach.

Im <head> von wwwroot/index.html:In the <head> of wwwroot/index.html:

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

Im <body> von wwwroot/index.html:In the <body> of wwwroot/index.html:

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

Pages/Index.razor (übergeordnete Komponente):Pages/Index.razor (parent component):

@page "/"

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

Welcome to your new app.

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

Pages/Index.razor.cs:Pages/Index.razor.cs:

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

namespace {APP ASSEMBLY}.Pages
{
    public partial class Index : 
        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, Index self)
            {
                Observer = observer;
                Self = self;
            }

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

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

Der Platzhalter {APP ASSEMBLY} ist der App-Assemblyname der App (z. B. BlazorSample).The placeholder {APP ASSEMBLY} is the app's app assembly name (for example, BlazorSample).

Shared/SurveyPrompt.razor (untergeordnete Komponente):Shared/SurveyPrompt.razor (child component):

@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:Shared/SurveyPrompt.razor.cs:

using System;
using Microsoft.AspNetCore.Components;

namespace {APP ASSEMBLY}.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();
        }
    }
}

Der Platzhalter {APP ASSEMBLY} ist der App-Assemblyname der App (z. B. BlazorSample).The placeholder {APP ASSEMBLY} is the app's app assembly name (for example, BlazorSample).

Festschreiben von JS Interop-AufrufenHarden JS interop calls

JS Interop kann aufgrund von Netzwerkfehlern fehlschlagen und sollte als unzuverlässig behandelt werden.JS interop may fail due to networking errors and should be treated as unreliable. Standardmäßig weisen Blazor Server-Apps einen Timeout für JS Interop-Aufrufe an den Server nach einer Minute auf.By default, a Blazor Server app times out JS interop calls on the server after one minute. Wenn eine App ein engeres Timeout tolerieren kann, legen Sie das Timeout mit einem der folgenden Ansätze fest:If an app can tolerate a more aggressive timeout, set the timeout using one of the following approaches:

  • Legen Sie das Timeout global in Startup.ConfigureServices fest:Globally in Startup.ConfigureServices, specify the timeout:

    services.AddServerSideBlazor(
        options => options.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds({SECONDS}));
    
  • Pro Aufruf im Komponentencode kann ein einzelner Aufruf das Timeout festlegen:Per-invocation in component code, a single call can specify the timeout:

    var result = await JSRuntime.InvokeAsync<string>("MyJSOperation", 
        TimeSpan.FromSeconds({SECONDS}), new[] { "Arg1" });
    

Weitere Informationen zur Ressourcenauslastung finden Sie unter Leitfaden zur Bedrohungsabwehr für [Blazor Server in ASP.NET Core.For more information on resource exhaustion, see Leitfaden zur Bedrohungsabwehr für [Blazor Server in ASP.NET Core.

Freigeben von Interop-Code in einer KlassenbibliothekShare interop code in a class library

JavaScript-Interop-Code kann in einer Klassenbibliothek eingeschlossen werden, wodurch Sie den Code in einem NuGet-Paket freigeben können.JS interop code can be included in a class library, which allows you to share the code in a NuGet package.

Die Klassenbibliothek behandelt das Einbetten von JavaScript-Ressourcen in der erstellten Assembly.The class library handles embedding JavaScript resources in the built assembly. Die JavaScript-Dateien werden im Ordner wwwroot gespeichert.The JavaScript files are placed in the wwwroot folder. Die Tools kümmern sich um das Einbetten der Ressourcen, wenn die Bibliothek erstellt wird.The tooling takes care of embedding the resources when the library is built.

In der Projektdatei der App wird so auf das NuGet-Paket verwiesen, wie auf jedes andere NuGet-Paket verwiesen wird.The built NuGet package is referenced in the app's project file the same way that any NuGet package is referenced. Nachdem das Paket wiederhergestellt wurde, kann App-Code einen Aufruf in JavaScript wie auch unter C# durchführen.After the package is restored, app code can call into JavaScript as if it were C#.

Weitere Informationen finden Sie unter Klassenbibliotheken für ASP.NET Core-Razor-Komponenten.For more information, see Klassenbibliotheken für ASP.NET Core-Razor-Komponenten.

Vermeiden von ObjektzirkelbezügenAvoid circular object references

Objekte, die Zirkelbezüge enthalten, können auf dem Client für folgende Vorgänge nicht serialisiert werden:Objects that contain circular references can't be serialized on the client for either:

  • .NET-Methodenaufrufe..NET method calls.
  • JavaScript-Methodenaufrufe von C#, wenn der Rückgabetyp Zirkelbezüge enthält.JavaScript method calls from C# when the return type has circular references.

Weitere Informationen finden Sie unter den folgenden Problemen:For more information, see the following issues:

Zusätzliche RessourcenAdditional resources