© Visual Generation/Shutterstock.com
Interoperabilität von C# und JavaScript im Browser

Blazor geht fremd


Mit Microsofts Web-Framework ASP.NET Core Blazor programmiert man moderne Webanwendungen in C# mit .NET-Klassen. In manchen Fällen braucht man aber immer noch JavaScript. Zum Glück gibt es zwischen C# und JavaScript eine Interoperabilität in beide Richtungen, wie dieser Artikel zeigt.

(Noch) nicht auf alle Funktionen eines Webbrowsers kann man in Blazor direkt per C#-Code zugreifen, zumindest ausgehend vom Standardlieferumfang von Blazor. Von diesem Standpunkt aus müssen Entwickler selbst für einige vermeintlich einfache Funktionen auf die Interoperabilität mit JavaScript zurückgreifen. Dazu gehören zum Beispiel:

  • Anzeige von Browserdialogen: alert(), confirm() und prompt()

  • Lesen und Setzen von Cookies und Zugriff auf andere lokale Speichertypen des Browsers

  • Ausgabe in die Browserkonsole in Blazor Server; in Blazor WebAssembly ist das über Console.WriteLine() möglich

  • Einfluss auf den HTML-Head, z. B. <Title> (das ist in Blazor 5.0 mit einer mitgelieferten Komponente gelöst)

  • Interaktion mit Standard-W3C-Web-Components (z. B. mit in Angular entwickelten Komponenten)

Bitte beachten Sie, dass es im Eingangssatz ausdrücklich „ausgehend vom Standardlieferumfang von Blazor“ heißt. Man findet im Internet zu vielen Aufgabenstellungen bereits Blazor-Komponenten aus der Open-Source-Community und/oder von kommerziellen Herstellern [1], die entsprechende Zugriffe auf JavaScript, das HTML-DOM und die Browser-APIs kapseln (in Wrapper-Komponenten). Da es aber vorkommen kann, dass Sie in Ihrem Projekt etwas nutzen möchten, für das es keinen Wrapper gibt, oder wenn Sie aus strategischen Gründen Drittanbieterkomponenten vermeiden wollen, bietet dieser Beitrag Ihnen das Wissen, um selbst beliebige Interaktionen zwischen C# und JavaScript zu entwickeln. Die hier vorgestellten Techniken sind die Basis für die vielen verfügbaren Wrapper-Komponenten.

Blazor hätte sicherlich ein Akzeptanzproblem, wenn es keine Möglichkeit gäbe, in Blazor auch mit JavaScript zu programmieren, um weitere Browser-APIs, JavaScript-Bibliotheken und bestehenden eigenen JavaScript-Code zu integrieren. Die Interoperabilität zwischen dem C#-Programmcode und JavaScript ist vorhanden – sowohl in Blazor WebAssembly, wo beide im Webbrowser laufen, als auch im Fall von Blazor Server, wo JavaScript im Webbrowser läuft und C# auf dem Webserver. Die Aufrufe mit Parametern und die Rückgabewerte werden in Blazor Server über ASP.NET SignalR serialisiert. Im Fall von Blazor WebAssembly laufen C# und JavaScript beide im Browserprozess, es gilt aber, die Grenze zwischen der JavaScript Engine und der WebAssembly VM zu überwinden.

Einschränkung der JavaScript-Interoperabilität in Blazor Server

In Blazor Server ist JavaScript-Interoperabilität in einigen Fällen nicht im Rahmen der sogenannten Pre-Rendering-Phase möglich, d. h. nicht in den Lebenszyklusereignisbehandlungsroutinen OnInitialized() und OnInitializedAsync(), sondern erst in OnAfterRenderAsync(). Pre-Rendering findet nur bei den Render Modes Static und ServerPrerendered statt. Im Render Mode Server gibt es die o. g. Einschränkung also nicht. Beim Modus ServerPrerendered gilt die Einschränkung nur für die erste aktivierte Razor Component einer Anwendung. Falls ein Softwareentwickler diese Einschränkung nicht beachtet, bricht die Blazor-Server-Anwendung zur Laufzeit mit diesem Fehler ab: „JavaScript interop calls cannot be issued at this time. This is because the component is being statically rendererd. When prerendering is enabled, JavaScript interop calls can only be performed during the OnAfterRenderAsync lifecycle method.”

Aufruf von C# zu JavaScript

Zentrale Anlaufstelle für die Interoperabilität von .NET zu JavaScript ist die .NET-Schnittstelle Microsoft.JSInterop.IJSRuntime mit den Methoden InvokeAsync() und InvokeVoidAsync() in jeweils drei Überladungen, die eine Parameterliste und optional entweder ein TimeSpan-Objekt für den Timeout oder ein CancellationToken erwarten.

Eine Klasseninstanz für die Schnittstelle IJSRuntime lässt man sich per Dependency Injection von der Blazor-Infrastruktur liefern. Im Razor-Template geht das mit:

@inject IJSRuntime jsRuntime

Im C#-Code geht es so:

[Inject] IJSRuntime jsRuntime { get; set; }

Dabei verwundert es nicht, dass die beiden Blazor-Formen für die Schnittstelle IJSRuntime verschiedene Implementierungen bereitstellen. Blazor Server liefert eine Instanz von Microsoft.AspNetCore.Components.Server.Circuits.RemoteJSRuntime. Blazor WebAssembly liefert hingegen Microsoft.AspNetCore.Components.WebAssembly.Services.DefaultWebAssemblyJSRuntime. Die verschiedenen Implementierungen ergeben Sinn, denn bei Blazor WebAssembly muss lediglich zwischen der WebAssembly VM und der JavaScript-Laufzeitumgebung innerhalb des Webbrowserprozesses vermittelt werden. Bei Blazor Server liegt das Netzwerk dazwischen; die Aufrufe mit Parametern und die Rückgabewerte werden über ASP.NET SignalR serialisiert.

Nach der Injizierung kann man auf der injizierten Instanz die Methoden InvokeVoidAsync() und InvokeAsync<Rückgabetyp>() aufrufen und dabei eine beliebige Liste von Parametern übergeben. Der folgende Aufruf geht von C# an die in JavaScript eingebaute Funktion confirm(), die einen Nachfragedialog mit zwei Schaltflächen anzeigt. Die Funktion confirm() erwartet einen Parameter mit Text und liefert einen Boolean-Wert zurück.

if (!await js.InvokeAsync<bool>("confirm", $"Remove Task #{t.TaskID}: {t.Title}?")) return;

Mit InvokeAsync() werden JavaScript-Funktionen aufgerufen, die einen Wert zurückliefern. InvokeAsync<T>() erwartet einen Typparameter (T) und liefert ein Objekt dieses Typs zurück. In beiden Fällen ist der erste Methodenparameter der Name der JavaScript-Funktion, ab dem zweiten Parameter in der Liste werden die Werte an die JavaScript-Funktion als Parameter durchgereicht. Die Parameter und der Rückgabewert können wahlweise als typisiertes .NET-Objekt (Klasse, Struct oder Record) oder anonymes .NET-Objekt verwaltet werden. Wichtig ist nur, dass sie in JSON serialisierbar sind und der .NET-Typ mit dem JavaScript-Typ korrespondiert. Wenn es nicht möglich ist, den JavaScript-Typ in den .NET-Typ umzuwandeln, kommt es zum Laufzeitfehler „Microsoft.JSInterop.JSException: An exception occurred executing JS interop: The JSON value could not be converted to (Typname)“. Abbildung 1 zeigt das Ergebnis des Aufrufs.

schwichtenberg_blazor_js_1.tif_fmt1.jpgAbb. 1: Modale Nachfrage beim Löschen

Eigene JavaScript-Dateien verwenden

Man kann in Blazor nicht nur eingebaute JavaScript-Funktionen, sondern auch JavaScript-Funktionen in eigenen Skriptdateien oder eigenen Skriptblöcken aufrufen. Eine Skriptdatei legt man im wwwRoot-Verzeichnis (oder einem Unterverzeichnis davon) ab. In Blazor Server 3.1 und Blazor WebAssembly 3.2 musste man JavaScript-Dateien noch immer global per <script>-Tag in die Startseite der Blazor-Anwendung einbinden. Seit der Blazor-Version 5.0 können JavaScript-Dateien bei Bedarf auch in einzelne Komponenten geladen werden (JavaScript-Isolation).

Globale Skripteinbindung

Zu beachten ist, dass man grundsätzlich kein eigenes JavaScript (weder über eine JavaScript-Datei noch einen Skriptblock) in eine einzelne Razor Component einbinden darf, weil Komponenten dynamische Bausteine sind, die man wieder entfernen kann. Einmal in den Browser geladenes JavaScript kann man aber nicht mehr aus dem Browser entfernen. Microsoft hat sich daher entschie...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Gewinner-Teams

Wir bieten Lizenz-Lösungen für Teams jeder Größe: Finden Sie heraus, welche Lösung am besten zu Ihnen passt.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang