HybridWebView
Wieloplatformowy interfejs użytkownika aplikacji platformy .NET (.NET MAUI) umożliwia hostowanie dowolnej zawartości HTML/JS/CSS w widoku internetowym i umożliwia komunikację między kodem w widoku internetowym (JavaScript) HybridWebView i kodem hostujący widok internetowy (C#/.NET). Jeśli na przykład masz istniejącą aplikację React JS, możesz ją hostować w wieloplatformowej aplikacji natywnej .NET MAUI i skompilować zaplecze aplikacji przy użyciu języków C# i .NET.
HybridWebView definiuje następujące właściwości:
- DefaultFile, typu
string?
, który określa plik w pliku HybridRoot , który powinien być obsługiwany jako plik domyślny. Wartość domyślna to index.html. - HybridRoot, typu
string?
, który jest ścieżką w nieprzetworzonych zasobach zasobów aplikacji, które zawierają zawartość aplikacji internetowej. Wartość domyślna to wwwroot, która mapuje na zasoby/Raw/wwwroot.
Ponadto HybridWebView definiuje RawMessageReceived zdarzenie, które jest zgłaszane po odebraniu nieprzetworzonego komunikatu. Obiekt HybridWebViewRawMessageReceivedEventArgs , który towarzyszy zdarzeniu, definiuje właściwość zawierającą Message komunikat.
Kod języka C# aplikacji może wywoływać metody synchroniczne i asynchroniczne języka JavaScript w HybridWebView ramach metod InvokeJavaScriptAsync i EvaluateJavaScriptAsync . Kod JavaScript aplikacji może również synchronicznie wywoływać metody języka C#. Aby uzyskać więcej informacji, zobacz Wywoływanie języka JavaScript z języka C# i wywoływanie języka C# z języka JavaScript.
Aby utworzyć aplikację HybridWebView MAUI platformy .NET, potrzebne są następujące elementy:
- Zawartość internetowa aplikacji, która składa się ze statycznych plików HTML, JavaScript, CSS, obrazów i innych plików.
- Kontrolka HybridWebView w ramach interfejsu użytkownika aplikacji. Można to osiągnąć, odwołując się do niego w języku XAML aplikacji.
- Kod w zawartości internetowej i w języku C#/.NET, który używa HybridWebView interfejsów API do wysyłania komunikatów między dwoma składnikami.
Cała aplikacja, w tym zawartość internetowa, jest pakowana i uruchamiana lokalnie na urządzeniu i może zostać opublikowana w odpowiednich sklepach z aplikacjami. Zawartość internetowa jest hostowana w natywnej kontrolce widoku sieci Web i jest uruchamiana w kontekście aplikacji. Każda część aplikacji może uzyskiwać dostęp do zewnętrznych usług internetowych, ale nie jest wymagana.
Ważne
Domyślnie kontrolka HybridWebView nie będzie dostępna, gdy jest włączone pełne przycinanie ani natywna funkcja AOT. Aby zmienić to zachowanie, zobacz Przycinanie przełączników funkcji.
Tworzenie aplikacji HybridWebView platformy .NET MAUI
Aby utworzyć aplikację .NET MAUI za pomocą polecenia HybridWebView:
Otwórz istniejący projekt aplikacji .NET MAUI lub utwórz nowy projekt aplikacji .NET MAUI.
Dodaj zawartość internetową do projektu aplikacji .NET MAUI.
Zawartość internetowa aplikacji powinna zostać dołączona jako część projektu MAUI platformy .NET jako nieprzetworzone zasoby. Nieprzetworzone zasoby to dowolny plik w folderze Resources\Raw aplikacji i zawiera podfoldery. W przypadku domyślnej HybridWebViewzawartości internetowej należy umieścić w folderze Resources\Raw\wwwroot z głównym plikiem o nazwie index.html.
Prosta aplikacja może mieć następujące pliki i zawartość:
Resources\Raw\wwwroot\index.html z zawartością głównego interfejsu użytkownika:
<!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 ze standardową HybridWebView biblioteką Języka JavaScript:
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();
Następnie dodaj do projektu dodatkową zawartość internetową.
Ostrzeżenie
W niektórych przypadkach program Visual Studio może dodać wpisy do pliku csproj projektu, które są niepoprawne. W przypadku używania domyślnej lokalizacji dla nieprzetworzonych zasobów nie powinny istnieć żadne wpisy dla tych plików ani folderów w pliku csproj .
Dodaj kontrolkę HybridWebView do aplikacji:
<Grid RowDefinitions="Auto,*" ColumnDefinitions="*"> <Button Text="Send message to JavaScript" Clicked="OnSendMessageButtonClicked" /> <HybridWebView x:Name="hybridWebView" RawMessageReceived="OnHybridWebViewRawMessageReceived" Grid.Row="1" /> </Grid>
Zmodyfikuj metodę
CreateMauiApp
MauiProgram
klasy , aby włączyć narzędzia deweloperskie w podstawowych kontrolkach WebView, gdy aplikacja jest uruchomiona w konfiguracji debugowania. W tym celu wywołaj metodę AddHybridWebViewDeveloperTools IServiceCollection w obiekcie :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(); } }
HybridWebView Użyj interfejsów API, aby wysyłać komunikaty między kodem JavaScript i C#:
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"); }
Powyższe komunikaty są klasowane jako nieprzetworzone, ponieważ nie jest wykonywane żadne dodatkowe przetwarzanie. Możesz również zakodować dane w wiadomości, aby wykonać bardziej zaawansowane komunikaty.
Wywoływanie kodu JavaScript z poziomu języka C#
Kod języka C# aplikacji może synchronicznie i asynchronicznie wywoływać metody Języka JavaScript w obiekcie HybridWebViewz opcjonalnymi parametrami i opcjonalną wartością zwracaną. Można to osiągnąć za InvokeJavaScriptAsync pomocą metod i :EvaluateJavaScriptAsync
- Metoda EvaluateJavaScriptAsync uruchamia kod JavaScript dostarczony za pomocą parametru i zwraca wynik jako ciąg.
- Metoda InvokeJavaScriptAsync wywołuje określoną metodę JavaScript, opcjonalnie przekazując wartości parametrów i określa ogólny argument wskazujący typ zwracanej wartości. Zwraca obiekt ogólnego typu argumentu, który zawiera wartość zwracaną przez wywołaną metodę JavaScript. Wewnętrznie parametry i wartości zwracane są kodowane w formacie JSON.
Wywoływanie synchronicznego kodu JavaScript
Metody synchroniczne języka JavaScript można wywołać za EvaluateJavaScriptAsync pomocą metod i InvokeJavaScriptAsync . W poniższym przykładzie InvokeJavaScriptAsync metoda jest używana do zademonstrowania wywoływania języka JavaScript osadzonego w zawartości internetowej aplikacji. Na przykład prosta metoda języka JavaScript umożliwiająca dodanie dwóch liczb można zdefiniować w zawartości internetowej:
function AddNumbers(a, b) {
return a + b;
}
AddNumbers
Metodę JavaScript można wywołać z języka C# przy użyciu InvokeJavaScriptAsync metody :
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
Wywołanie metody wymaga określenia JsonTypeInfo
obiektów, które zawierają informacje o serializacji dla typów używanych w operacji. Te obiekty są tworzone automatycznie przez dołączenie następującej partial
klasy w projekcie:
[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.
}
Ważne
Klasa HybridSampleJsContext
musi być partial
tak, aby generowanie kodu może zapewnić implementację podczas kompilowania projektu. Jeśli typ jest zagnieżdżony do innego typu, ten typ również musi mieć wartość partial
.
Wywoływanie asynchronicznego kodu JavaScript
Metody Asynchroniczne języka JavaScript można wywołać za EvaluateJavaScriptAsync pomocą metod i InvokeJavaScriptAsync . W poniższym przykładzie InvokeJavaScriptAsync metoda jest używana do zademonstrowania wywoływania języka JavaScript osadzonego w zawartości internetowej aplikacji. Na przykład metoda javascript, która asynchronicznie pobiera dane, może zostać zdefiniowana w zawartości internetowej:
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;
}
EvaluateMeWithParamsAndAsyncReturn
Metodę JavaScript można wywołać z języka C# przy użyciu InvokeJavaScriptAsync metody :
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
W tym przykładzie asyncResult
element zawiera Dictionary<string, string>
dane JSON z żądania internetowego.
Wywołanie metody wymaga określenia JsonTypeInfo
obiektów, które zawierają informacje o serializacji dla typów używanych w operacji. Te obiekty są tworzone automatycznie przez dołączenie następującej partial
klasy w projekcie:
[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.
}
Ważne
Klasa HybridSampleJsContext
musi być partial
tak, aby generowanie kodu może zapewnić implementację podczas kompilowania projektu. Jeśli typ jest zagnieżdżony do innego typu, ten typ również musi mieć wartość partial
.
Wywoływanie języka C# z poziomu języka JavaScript
Kod JavaScript aplikacji w programie HybridWebView może synchronicznie wywoływać metody języka C# z opcjonalnymi parametrami i opcjonalną wartością zwracaną. Można to osiągnąć, wykonując następujące czynności:
- Definiowanie publicznych metod języka C#, które będą wywoływane z języka JavaScript.
- SetInvokeJavaScriptTarget Wywołanie metody w celu ustawienia obiektu, który będzie obiektem docelowym wywołań języka JavaScript z obiektu HybridWebView.
- Wywoływanie metod języka C# z języka JavaScript.
Ważne
Asynchronicznie wywoływanie metod języka C# z języka JavaScript nie jest obecnie obsługiwane.
W poniższym przykładzie zdefiniowano cztery publiczne metody wywoływania z języka JavaScript:
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; }
}
}
Następnie należy wywołać metodę SetInvokeJavaScriptTarget , aby ustawić obiekt, który będzie obiektem docelowym wywołań języka JavaScript z elementu HybridWebView:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
hybridWebView.SetInvokeJavaScriptTarget(this);
}
...
}
Metody publiczne w zestawie obiektów za pośrednictwem SetInvokeJavaScriptTarget metody można następnie wywołać z języka JavaScript za pomocą window.HybridWebView.InvokeDotNet
funkcji :
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']);
Funkcja window.HybridWebView.InvokeDotNet
JavaScript wywołuje określoną metodę języka C# z opcjonalnymi parametrami i opcjonalną wartością zwracaną.
Uwaga
Wywołanie window.HybridWebView.InvokeDotNet
funkcji JavaScript wymaga, aby aplikacja zawierała bibliotekę języka JavaScript HybridWebView.js wymienioną wcześniej w tym artykule.