HybridWebView
Die HybridWebView der .NET Multi-Platform App UI (.NET MAUI) ermöglicht das Hosten beliebiger HTML/JS/CSS-Inhalte in einer Webansicht und erlaubt die Kommunikation zwischen dem Code in der Webansicht (JavaScript) und dem Code, der die Webansicht hostet (C#/.NET). Wenn Sie beispielsweise eine React-JS-App haben, können Sie diese in einer plattformübergreifenden nativen .NET-MAUI-App hosten und das Back-End der App mit C# und .NET erstellen.
HybridWebView definiert die folgenden Eigenschaften:
- DefaultFile, vom Typ
string?
, der die Datei innerhalb von HybridRoot angibt, die als Standarddatei verwendet werden soll. Der Standardwert ist index.html. - HybridRoot, vom Typ
string?
, bei dem es sich um den Pfad innerhalb der Rohressourcen der App handelt, die den Inhalt der Web-App enthalten. Der Standardwert ist wwwroot, der auf Resources/Raw/wwwroot abgebildet wird.
Zusätzlich definiert HybridWebView ein RawMessageReceived Ereignis, das ausgelöst wird, wenn eine Rohnachricht empfangen wird. Das HybridWebViewRawMessageReceivedEventArgs-Objekt, das die Veranstaltung begleitet, definiert eine Message-Eigenschaft, die die Nachricht enthält.
Der C#-Code Ihrer App kann synchrone und asynchrone JavaScript-Methoden innerhalb von HybridWebView mit den Methoden InvokeJavaScriptAsync und EvaluateJavaScriptAsync aufrufen. Der JavaScript-Code Ihrer App kann auch C#-Methoden synchron aufrufen. Weitere Informationen finden Sie unter Aufrufen von JavaScript aus C# und Aufrufen von C# aus JavaScript.
Um eine .NET MAUI-App mit HybridWebView zu erstellen, benötigen Sie:
- Den Webinhalt der App, der aus statischem HTML, JavaScript, CSS, Bildern und anderen Dateien besteht.
- Ein HybridWebView-Steuerelement als Teil der Benutzeroberfläche der App. Dies kann erreicht werden, indem es im XAML-Code der App referenziert wird.
- Code im Webinhalt und in C#/.NET, der die HybridWebView-APIs verwendet, um Nachrichten zwischen den beiden Komponenten zu senden.
Die gesamte App, einschließlich der Webinhalte, ist verpackt und läuft lokal auf einem Gerät und kann in den entsprechenden App-Stores veröffentlicht werden. Der Webinhalt wird in einer nativen Webansicht gehostet und läuft im Kontext der App. Jeder Teil der App kann auf externe Webdienste zugreifen, muss dies aber nicht.
Wichtig
Standardmäßig ist das Steuerelement nicht verfügbar, wenn die HybridWebView vollständige Kürzung oder native AOT aktiviert ist. Informationen zum Ändern dieses Verhaltens finden Sie unter "Trimming feature switches".
Erstellen einer .NET MAUI HybridWebView-App
So erstellen Sie eine .NET MAUI-App mit einer HybridWebView:
Öffnen Sie ein vorhandenes .NET MAUI-App-Projekt, oder erstellen Sie ein neues .NET MAUI-App-Projekt.
Fügen Sie Ihren Webinhalt zum .NET MAUI-App-Projekt hinzu.
Der Webinhalt Ihrer App sollte als Teil eines .NET MAUI-Projekts als Rohressource eingebunden werden. Eine Rohressource ist jede Datei im Ordner Resources\Raw der App, einschließlich Unterordner. Bei einer Standard-HybridWebView sollten Webinhalte im Ordner Resources\Raw\wwwroot abgelegt werden, wobei die Hauptdatei index.html heißen sollte.
Eine einfache App kann die folgenden Dateien und Inhalte enthalten:
Resources\Raw\wwwroot\index.html mit Inhalten für die Hauptbenutzeroberfläche:
<!DOCTYPE html> <html lang="en" xmlns="http://www.w3.org/1999/xhtml"> <head> <meta charset="utf-8" /> <title></title> <link rel="icon" href="data:,"> <link rel="stylesheet" href="styles/app.css"> <script src="scripts/HybridWebView.js"></script> <script> function LogMessage(msg) { var messageLog = document.getElementById("messageLog"); messageLog.value += '\r\n' + msg; } window.addEventListener( "HybridWebViewMessageReceived", function (e) { LogMessage("Raw message: " + e.detail.message); }); function AddNumbers(a, b) { var result = { "result": a + b, "operationName": "Addition" }; return result; } var count = 0; async function EvaluateMeWithParamsAndAsyncReturn(s1, s2) { const response = await fetch("/asyncdata.txt"); if (!response.ok) { throw new Error(`HTTP error: ${response.status}`); } var jsonData = await response.json(); jsonData[s1] = s2; const msg = 'JSON data is available: ' + JSON.stringify(jsonData); window.HybridWebView.SendRawMessage(msg) return jsonData; } async function InvokeDoSyncWork() { LogMessage("Invoking DoSyncWork"); await window.HybridWebView.InvokeDotNet('DoSyncWork'); LogMessage("Invoked DoSyncWork"); } async function InvokeDoSyncWorkParams() { LogMessage("Invoking DoSyncWorkParams"); await window.HybridWebView.InvokeDotNet('DoSyncWorkParams', [123, 'hello']); LogMessage("Invoked DoSyncWorkParams"); } async function InvokeDoSyncWorkReturn() { LogMessage("Invoking DoSyncWorkReturn"); const retValue = await window.HybridWebView.InvokeDotNet('DoSyncWorkReturn'); LogMessage("Invoked DoSyncWorkReturn, return value: " + retValue); } async function InvokeDoSyncWorkParamsReturn() { LogMessage("Invoking DoSyncWorkParamsReturn"); const retValue = await window.HybridWebView.InvokeDotNet('DoSyncWorkParamsReturn', [123, 'hello']); LogMessage("Invoked DoSyncWorkParamsReturn, return value: message=" + retValue.Message + ", value=" + retValue.Value); } </script> </head> <body> <div> Hybrid sample! </div> <div> <button onclick="window.HybridWebView.SendRawMessage('Message from JS! ' + (count++))">Send message to C#</button> </div> <div> <button onclick="InvokeDoSyncWork()">Call C# sync method (no params)</button> <button onclick="InvokeDoSyncWorkParams()">Call C# sync method (params)</button> <button onclick="InvokeDoSyncWorkReturn()">Call C# method (no params) and get simple return value</button> <button onclick="InvokeDoSyncWorkParamsReturn()">Call C# method (params) and get complex return value</button> </div> <div> Log: <textarea readonly id="messageLog" style="width: 80%; height: 10em;"></textarea> </div> <div> Consider checking out this PDF: <a href="docs/sample.pdf">sample.pdf</a> </div> </body> </html>
Resources\Raw\wwwroot\scripts\HybridWebView.js mit der standardmäßigen HybridWebView JavaScript-Bibliothek:
window.HybridWebView = { "Init": function Init() { function DispatchHybridWebViewMessage(message) { const event = new CustomEvent("HybridWebViewMessageReceived", { detail: { message: message } }); window.dispatchEvent(event); } if (window.chrome && window.chrome.webview) { // Windows WebView2 window.chrome.webview.addEventListener('message', arg => { DispatchHybridWebViewMessage(arg.data); }); } else if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.webwindowinterop) { // iOS and MacCatalyst WKWebView window.external = { "receiveMessage": message => { DispatchHybridWebViewMessage(message); } }; } else { // Android WebView window.addEventListener('message', arg => { DispatchHybridWebViewMessage(arg.data); }); } }, "SendRawMessage": function SendRawMessage(message) { window.HybridWebView.__SendMessageInternal('__RawMessage', message); }, "InvokeDotNet": async function InvokeDotNetAsync(methodName, paramValues) { const body = { MethodName: methodName }; if (typeof paramValues !== 'undefined') { if (!Array.isArray(paramValues)) { paramValues = [paramValues]; } for (var i = 0; i < paramValues.length; i++) { paramValues[i] = JSON.stringify(paramValues[i]); } if (paramValues.length > 0) { body.ParamValues = paramValues; } } const message = JSON.stringify(body); var requestUrl = `${window.location.origin}/__hwvInvokeDotNet?data=${encodeURIComponent(message)}`; const rawResponse = await fetch(requestUrl, { method: 'GET', headers: { 'Accept': 'application/json' } }); const response = await rawResponse.json(); if (response) { if (response.IsJson) { return JSON.parse(response.Result); } return response.Result; } return null; }, "__SendMessageInternal": function __SendMessageInternal(type, message) { const messageToSend = type + '|' + message; if (window.chrome && window.chrome.webview) { // Windows WebView2 window.chrome.webview.postMessage(messageToSend); } else if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.webwindowinterop) { // iOS and MacCatalyst WKWebView window.webkit.messageHandlers.webwindowinterop.postMessage(messageToSend); } else { // Android WebView hybridWebViewHost.sendMessage(messageToSend); } }, "__InvokeJavaScript": function __InvokeJavaScript(taskId, methodName, args) { if (methodName[Symbol.toStringTag] === 'AsyncFunction') { // For async methods, we need to call the method and then trigger the callback when it's done const asyncPromise = methodName(...args); asyncPromise .then(asyncResult => { window.HybridWebView.__TriggerAsyncCallback(taskId, asyncResult); }) .catch(error => console.error(error)); } else { // For sync methods, we can call the method and trigger the callback immediately const syncResult = methodName(...args); window.HybridWebView.__TriggerAsyncCallback(taskId, syncResult); } }, "__TriggerAsyncCallback": function __TriggerAsyncCallback(taskId, result) { // Make sure the result is a string if (result && typeof (result) !== 'string') { result = JSON.stringify(result); } window.HybridWebView.__SendMessageInternal('__InvokeJavaScriptCompleted', taskId + '|' + result); } } window.HybridWebView.Init();
Fügen Sie dann weitere Webinhalte zu Ihrem Projekt hinzu.
Warnung
In einigen Fällen fügt Visual Studio möglicherweise falsche Einträge zur .csproj-Datei des Projekts hinzu. Wenn Sie den Standardpfad für Rohdressourcen verwenden, sollten in der Datei .csproj keine Einträge für diese Dateien oder Ordner vorhanden sein.
Fügen Sie das HybridWebView-Steuerelement zu Ihrer App hinzu:
<Grid RowDefinitions="Auto,*" ColumnDefinitions="*"> <Button Text="Send message to JavaScript" Clicked="OnSendMessageButtonClicked" /> <HybridWebView x:Name="hybridWebView" RawMessageReceived="OnHybridWebViewRawMessageReceived" Grid.Row="1" /> </Grid>
Ändern Sie die
CreateMauiApp
Methode IhrerMauiProgram
Klasse, um Entwicklertools für die zugrunde liegenden WebView-Steuerelemente zu aktivieren, wenn Ihre App in der Debugkonfiguration ausgeführt wird. Rufen Sie dazu die AddHybridWebViewDeveloperTools Methode für das IServiceCollection Objekt auf:using Microsoft.Extensions.Logging; public static class MauiProgram { public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder .UseMauiApp<App>() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); }); #if DEBUG builder.Services.AddHybridWebViewDeveloperTools(); builder.Logging.AddDebug(); #endif // Register any app services on the IServiceCollection object return builder.Build(); } }
Verwenden Sie die HybridWebView-APIs, um Nachrichten zwischen dem JavaScript- und dem C#-Code zu senden:
private void OnSendMessageButtonClicked(object sender, EventArgs e) { hybridWebView.SendRawMessage($"Hello from C#!"); } private async void OnHybridWebViewRawMessageReceived(object sender, HybridWebViewRawMessageReceivedEventArgs e) { await DisplayAlert("Raw Message Received", e.Message, "OK"); }
Die oben genannten Nachrichten werden als „roh“ eingestuft, da keine zusätzliche Verarbeitung erfolgt. Sie können auch Daten in der Nachricht verschlüsseln, um eine fortgeschrittenere Nachrichtenübermittlung durchzuführen.
Aufrufen von JavaScript aus C#
Der C#-Code Ihrer App kann JavaScript-Methoden synchron und asynchron innerhalb von HybridWebView mit optionalen Parametern und einem optionalen Rückgabewert aufrufen. Dies kann mit den Methoden InvokeJavaScriptAsync und EvaluateJavaScriptAsync erreicht werden:
- Die Methode EvaluateJavaScriptAsync führt den über einen Parameter bereitgestellten JavaScript-Code aus und gibt das Ergebnis als Zeichenfolge zurück.
- Die InvokeJavaScriptAsync Methode ruft eine angegebene JavaScript-Methode auf, gibt optional Parameterwerte an und gibt ein generisches Argument an, das den Typ des Rückgabewerts angibt. Es gibt ein Objekt des generischen Argumenttyps zurück, der den Rückgabewert der aufgerufenen JavaScript-Methode enthält. Intern werden Parameter und Rückgabewerte JSON-codiert.
Aufrufen von synchronem JavaScript
Synchrone JavaScript-Methoden können mit den Methoden EvaluateJavaScriptAsync und InvokeJavaScriptAsync aufgerufen werden. Im folgenden Beispiel wird die Methode InvokeJavaScriptAsync verwendet, um das Aufrufen von JavaScript zu veranschaulichen, das in den Webinhalt einer App eingebettet ist. Beispielsweise könnte eine einfache Javascript-Methode zum Addieren von zwei Zahlen in Ihrem Webinhalt definiert werden:
function AddNumbers(a, b) {
return a + b;
}
Die JavaScript-Methode AddNumbers
kann von C# aus mit der InvokeJavaScriptAsync-Methode aufgerufen
double x = 123d;
double y = 321d;
double result = await hybridWebView.InvokeJavaScriptAsync<double>(
"AddNumbers", // JavaScript method name
HybridSampleJSContext.Default.Double, // JSON serialization info for return type
[x, y], // Parameter values
[HybridSampleJSContext.Default.Double, HybridSampleJSContext.Default.Double]); // JSON serialization info for each parameter
Für den Methodenaufruf müssen JsonTypeInfo
-Objekte angegeben werden, die Serialisierungsinformationen für die in dem Vorgang verwendeten Typen enthalten. Diese Objekte werden automatisch erstellt, indem Sie die folgende partial
-Klasse in Ihr Projekt einfügen:
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(double))]
internal partial class HybridSampleJsContext : JsonSerializerContext
{
// This type's attributes specify JSON serialization info to preserve type structure
// for trimmed builds.
}
Wichtig
Die HybridSampleJsContext
-Klasse muss partial
sein, damit die Codegenerierung die Implementierung bereitstellen kann, wenn das Projekt kompiliert wird. Wenn der Typ in einen anderen Typ geschachtelt ist, muss dieser Typ ebenfalls partial
sein.
Aufrufen von asynchronem JavaScript-Code
Asynchrone JavaScript-Methoden können mit den Methoden EvaluateJavaScriptAsync und InvokeJavaScriptAsync aufgerufen werden. Im folgenden Beispiel wird die InvokeJavaScriptAsync-Methode verwendet, um das Aufrufen von JavaScript zu veranschaulichen, das in den Webinhalt einer App eingebettet ist. Beispielsweise könnte eine Javascript-Methode, die Daten asynchron abruft, in Ihrem Webinhalt definiert werden:
async function EvaluateMeWithParamsAndAsyncReturn(s1, s2) {
const response = await fetch("/asyncdata.txt");
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
var jsonData = await response.json();
jsonData[s1] = s2;
return jsonData;
}
Die JavaScript-Methode EvaluateMeWithParamsAndAsyncReturn
kann von C# aus mit der InvokeJavaScriptAsync-Methode aufgerufen werden:
Dictionary<string, string> asyncResult = await hybridWebView.InvokeJavaScriptAsync<Dictionary<string, string>>(
"EvaluateMeWithParamsAndAsyncReturn", // JavaScript method name
HybridSampleJSContext.Default.DictionaryStringString, // JSON serialization info for return type
["new_key", "new_value"], // Parameter values
[HybridSampleJSContext.Default.String, HybridSampleJSContext.Default.String]); // JSON serialization info for each parameter
In diesem Beispiel handelt es sich um eine Dictionary<string, string>
Datei, asyncResult
die die JSON-Daten aus der Webanforderung enthält.
Für den Methodenaufruf müssen JsonTypeInfo
-Objekte angegeben werden, die Serialisierungsinformationen für die in dem Vorgang verwendeten Typen enthalten. Diese Objekte werden automatisch erstellt, indem Sie die folgende partial
-Klasse in Ihr Projekt einfügen:
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(Dictionary<string, string>))]
[JsonSerializable(typeof(string))]
internal partial class HybridSampleJSContext : JsonSerializerContext
{
// This type's attributes specify JSON serialization info to preserve type structure
// for trimmed builds.
}
Wichtig
Die HybridSampleJsContext
-Klasse muss partial
sein, damit die Codegenerierung die Implementierung bereitstellen kann, wenn das Projekt kompiliert wird. Wenn der Typ in einen anderen Typ geschachtelt ist, muss dieser Typ ebenfalls partial
sein.
Aufrufen von C#-Code über JavaScript
Der JavaScript-Code Ihrer App innerhalb der HybridWebView C#-Methoden kann synchron mit optionalen Parametern und einem optionalen Rückgabewert aufgerufen werden. Dies lässt sich wie folgt erreichen:
- Definieren von öffentlichen C#-Methoden, die aus JavaScript aufgerufen werden.
- Aufrufen der SetInvokeJavaScriptTarget Methode zum Festlegen des Objekts, das das Ziel von JavaScript-Aufrufen von der HybridWebView.
- Aufrufen der C#-Methoden aus JavaScript.
Wichtig
Das asynchrone Aufrufen von C#-Methoden aus JavaScript wird derzeit nicht unterstützt.
Im folgenden Beispiel werden vier öffentliche Methoden zum Aufrufen aus JavaScript definiert:
public partial class MainPage : ContentPage
{
...
public void DoSyncWork()
{
Debug.WriteLine("DoSyncWork");
}
public void DoSyncWorkParams(int i, string s)
{
Debug.WriteLine($"DoSyncWorkParams: {i}, {s}");
}
public string DoSyncWorkReturn()
{
Debug.WriteLine("DoSyncWorkReturn");
return "Hello from C#!";
}
public SyncReturn DoSyncWorkParamsReturn(int i, string s)
{
Debug.WriteLine($"DoSyncWorkParamReturn: {i}, {s}");
return new SyncReturn
{
Message = "Hello from C#!" + s,
Value = i
};
}
public class SyncReturn
{
public string? Message { get; set; }
public int Value { get; set; }
}
}
Anschließend müssen Sie die SetInvokeJavaScriptTarget Methode aufrufen, um das Objekt festzulegen, das das Ziel von JavaScript-Aufrufen aus dem HybridWebView:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
hybridWebView.SetInvokeJavaScriptTarget(this);
}
...
}
Die öffentlichen Methoden für den Objektsatz über die SetInvokeJavaScriptTarget Methode können dann über JavaScript mit der window.HybridWebView.InvokeDotNet
Funktion aufgerufen werden:
await window.HybridWebView.InvokeDotNet('DoSyncWork');
await window.HybridWebView.InvokeDotNet('DoSyncWorkParams', [123, 'hello']);
const retValue = await window.HybridWebView.InvokeDotNet('DoSyncWorkReturn');
const retValue = await window.HybridWebView.InvokeDotNet('DoSyncWorkParamsReturn', [123, 'hello']);
Die window.HybridWebView.InvokeDotNet
JavaScript-Funktion ruft eine angegebene C#-Methode mit optionalen Parametern und einem optionalen Rückgabewert auf.
Hinweis
Zum Aufrufen der window.HybridWebView.InvokeDotNet
JavaScript-Funktion muss Ihre App die weiter oben in diesem Artikel aufgeführte HybridWebView.js JavaScript-Bibliothek einschließen.