Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Пользовательский интерфейс многоплатформенного приложения .NET (.NET MAUI) HybridWebView позволяет размещать произвольное содержимое HTML/JS/CSS в веб-представлении и обеспечивает обмен данными между кодом в веб-представлении (JavaScript) и кодом, на котором размещено веб-представление (C#/.NET). Например, если у вас есть существующее приложение React JS, его можно разместить в кроссплатформенной машинном приложении .NET MAUI и создать серверную часть приложения с помощью C# и .NET.
HybridWebView определяет следующие свойства:
-
DefaultFile, типа
string?
, который указывает файл внутри HybridRoot, который должен служить файлом по умолчанию. Значение по умолчанию — index.html. -
HybridRoot, типа
string?
, который представляет собой путь в необработанных ресурсах приложения, содержащий содержимое веб-приложения. Значение по умолчанию — wwwroot, которое сопоставляется с Resources/Raw/wwwroot.
Кроме того, HybridWebView определяет событие, которое возникает RawMessageReceived при получении необработанного сообщения. Объект HybridWebViewRawMessageReceivedEventArgs, который сопровождает событие, определяет свойство Message, содержащее сообщение.
Код C# вашего приложения может вызывать синхронные и асинхронные методы JavaScript в HybridWebView с помощью методов InvokeJavaScriptAsync и EvaluateJavaScriptAsync. Код JavaScript приложения также может вызывать синхронные и асинхронные методы C#. Дополнительные сведения см. в разделе "Вызов JavaScript" из C# и вызов C# из JavaScript.
Чтобы создать приложение .NET MAUI с помощью HybridWebView, вам необходимо:
- Веб-содержимое приложения, состоящее из статического HTML, JavaScript, CSS, изображений и других файлов.
- Элемент HybridWebView управления в составе пользовательского интерфейса приложения. Это можно сделать, ссылаясь на него в XAML приложения.
- Код в веб-содержимом и в C#/.NET, который использует HybridWebView API для отправки сообщений между двумя компонентами.
Все приложение, включая веб-содержимое, упаковано и выполняется локально на устройстве и может быть опубликовано в применимых магазинах приложений. Веб-содержимое размещается в нативном элементе управления веб-просмотром и выполняется в контексте приложения. Любая часть приложения может получить доступ к внешним веб-службам, но это не обязательно.
Внимание
По умолчанию элемент HybridWebView управления не будет доступен, если включен полный тримминг или Native AOT. Сведения об изменении этого поведения см. в разделе "Параметры функции обрезки".
Создание приложения .NET MAUI HybridWebView
Создание приложения .NET MAUI с помощью HybridWebView:
Откройте существующий проект приложения .NET MAUI или создайте проект приложения .NET MAUI.
Добавьте веб-содержимое в проект приложения .NET MAUI.
Веб-содержимое приложения должно быть включено в проект .NET MAUI в качестве необработанных ресурсов. Сырой ресурс — это любой файл в папке приложения Resources\Raw и включает вложенные папки. По умолчанию HybridWebViewвеб-содержимое должно быть помещено в папку Resources\Raw\wwwroot с основным файлом с именем index.html.
Простое приложение может содержать следующие файлы и содержимое:
Resources\Raw\wwwroot\index.html с содержимым для основного пользовательского интерфейса:
<!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); } async function InvokeDoAsyncWork() { LogMessage("Invoking DoAsyncWork"); await window.HybridWebView.InvokeDotNet('DoAsyncWork'); LogMessage("Invoked DoAsyncWork"); } async function InvokeDoAsyncWorkParams() { LogMessage("Invoking DoAsyncWorkParams"); await window.HybridWebView.InvokeDotNet('DoAsyncWorkParams', [123, 'hello']); LogMessage("Invoked DoAsyncWorkParams"); } async function InvokeDoAsyncWorkReturn() { LogMessage("Invoking DoAsyncWorkReturn"); const retValue = await window.HybridWebView.InvokeDotNet('DoAsyncWorkReturn'); LogMessage("Invoked DoAsyncWorkReturn, return value: " + retValue); } async function InvokeDoAsyncWorkParamsReturn() { LogMessage("Invoking DoAsyncWorkParamsReturn"); const retValue = await window.HybridWebView.InvokeDotNet('DoAsyncWorkParamsReturn', [123, 'hello']); LogMessage("Invoked DoAsyncWorkParamsReturn, 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> <button onclick="InvokeDoAsyncWork()">Call C# async method (no params)</button> <button onclick="InvokeDoAsyncWorkParams()">Call C# async method (params)</button> <button onclick="InvokeDoAsyncWorkReturn()">Call C# async method (no params) and get simple return value</button> <button onclick="InvokeDoAsyncWorkParamsReturn()">Call C# async 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 со стандартной HybridWebView библиотекой 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": async function __InvokeJavaScript(taskId, methodName, args) { try { var result = null; if (methodName[Symbol.toStringTag] === 'AsyncFunction') { result = await methodName(...args); } else { result = methodName(...args); } window.HybridWebView.__TriggerAsyncCallback(taskId, result); } catch (ex) { console.error(ex); window.HybridWebView.__TriggerAsyncFailedCallback(taskId, ex); } }, "__TriggerAsyncCallback": function __TriggerAsyncCallback(taskId, result) { const json = JSON.stringify(result); window.HybridWebView.__SendMessageInternal('__InvokeJavaScriptCompleted', taskId + '|' + json); }, "__TriggerAsyncFailedCallback": function __TriggerAsyncCallback(taskId, error) { if (!error) { json = { Message: "Unknown error", StackTrace: Error().stack }; } else if (error instanceof Error) { json = { Name: error.name, Message: error.message, StackTrace: error.stack }; } else if (typeof (error) === 'string') { json = { Message: error, StackTrace: Error().stack }; } else { json = { Message: JSON.stringify(error), StackTrace: Error().stack }; } json = JSON.stringify(json); window.HybridWebView.__SendMessageInternal('__InvokeJavaScriptFailed', taskId + '|' + json); } } window.HybridWebView.Init();
Затем добавьте в проект любое дополнительное веб-содержимое.
Предупреждение
В некоторых случаях Visual Studio может добавлять некорректные записи в файл .csproj проекта. При использовании расположения по умолчанию для необработанных ресурсов не должно быть никаких записей для этих файлов или папок в CSPROJ-файле .
Добавьте элемент управления HybridWebView в ваше приложение.
<Grid RowDefinitions="Auto,*" ColumnDefinitions="*"> <Button Text="Send message to JavaScript" Clicked="OnSendMessageButtonClicked" /> <HybridWebView x:Name="hybridWebView" RawMessageReceived="OnHybridWebViewRawMessageReceived" Grid.Row="1" /> </Grid>
Измените метод
CreateMauiApp
классаMauiProgram
, чтобы включить средства разработчика в элементах управления WebView, когда приложение выполняется в конфигурации отладки. Для этого вызовите AddHybridWebViewDeveloperTools метод объекта IServiceCollection :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 Используйте API для отправки сообщений между кодом JavaScript и 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"); }
private void OnSendMessageButtonClicked(object sender, EventArgs e) { hybridWebView.SendRawMessage($"Hello from C#!"); } private async void OnHybridWebViewRawMessageReceived(object sender, HybridWebViewRawMessageReceivedEventArgs e) { await DisplayAlertAsync("Raw Message Received", e.Message, "OK"); }
Приведенные выше сообщения классируются как необработанные, так как дополнительная обработка не выполняется. Вы также можете закодировать данные в сообщении для более расширенного обмена сообщениями.
Вызов JavaScript из C#
Код C# вашего приложения может синхронно и асинхронно вызывать методы JavaScript внутри HybridWebView, с необязательными параметрами и необязательным возвращаемым значением. Это можно сделать с помощью методов InvokeJavaScriptAsync и EvaluateJavaScriptAsync.
- Метод EvaluateJavaScriptAsync запускает код JavaScript, предоставленный с помощью параметра, и возвращает результат в виде строки.
- Метод InvokeJavaScriptAsync вызывает указанный метод JavaScript, при необходимости передавая значения параметров, и задает универсальный аргумент, указывающий тип возвращаемого значения. Он возвращает объект универсального типа аргумента, который содержит возвращаемое значение вызываемого метода JavaScript. Внутренние параметры и возвращаемые значения кодируются в формате JSON.
Примечание.
.NET 10 включает перегрузку InvokeJavaScriptAsync, которая вызывает указанный метод JavaScript без указания каких-либо сведений о типе возврата. Дополнительные сведения см. в статье Вызов методов JavaScript, которые не возвращают значение.
Вызов синхронного JavaScript
Синхронные методы JavaScript можно вызывать с помощью методов EvaluateJavaScriptAsync и InvokeJavaScriptAsync. В следующем примере InvokeJavaScriptAsync метод используется для демонстрации вызова JavaScript, внедренного в веб-содержимое приложения. Например, простой метод Javascript для добавления двух чисел можно определить в веб-содержимом:
function AddNumbers(a, b) {
return a + b;
}
Метод AddNumbers
JavaScript можно вызвать из C# с InvokeJavaScriptAsync помощью метода:
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
Вызов метода требует указания JsonTypeInfo
объектов, включающих сведения о сериализации для типов, используемых в операции. Эти объекты создаются автоматически, включая следующий partial
класс в проекте:
[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.
}
Внимание
Класс HybridSampleJsContext
должен быть partial
таким образом, чтобы создание кода обеспечивало реализацию при компиляции проекта. Если тип вложен в другой тип, этот тип также должен быть partial
.
Вызов асинхронного JavaScript
Асинхронные методы JavaScript можно вызывать с помощью EvaluateJavaScriptAsync методов и InvokeJavaScriptAsync методов. В следующем примере InvokeJavaScriptAsync метод используется для демонстрации вызова JavaScript, внедренного в веб-содержимое приложения. Например, метод Javascript, который асинхронно извлекает данные, можно определить в веб-содержимом:
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
JavaScript можно вызвать из C# с InvokeJavaScriptAsync помощью метода:
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
В этом примере asyncResult
является Dictionary<string, string>
, содержащим данные JSON из веб-запроса.
Вызов метода требует указания JsonTypeInfo
объектов, включающих сведения о сериализации для типов, используемых в операции. Эти объекты создаются автоматически, включая следующий partial
класс в проекте:
[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.
}
Внимание
Класс HybridSampleJsContext
должен быть partial
, чтобы генерация кода могла обеспечить его реализацию при компиляции проекта. Если тип вложен в другой тип, этот тип также должен быть partial
.
Вызов методов JavaScript, которые не возвращают значение
Метод InvokeJavaScriptAsync также можно использовать для вызова методов JavaScript, которые не возвращают значение. Существуют альтернативные подходы к этому:
Вызовите InvokeJavaScriptAsync, указав имя метода JavaScript и любые необязательные параметры:
await hybridWebView.InvokeJavaScriptAsync("javaScriptWithVoidReturn"); // JavaScript method name
В этом примере указывается только имя метода JavaScript.
Вызов метода InvokeJavaScriptAsync без указания универсального аргумента:
await hybridWebView.InvokeJavaScriptAsync( "javaScriptWithParamsAndVoidReturn", // 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
В этом примере, хотя универсальный аргумент не требуется, необходимо предоставить сведения о сериализации JSON для возвращаемого типа, даже если он не используется.
Вызовите метод InvokeJavaScriptAsync при указании универсального аргумента:
await hybridWebView.InvokeJavaScriptAsync<double>( "javaScriptWithParamsAndVoidReturn", // JavaScript method name null, // JSON serialization info for return type [x, y], // Parameter values [HybridSampleJSContext.Default.Double, HybridSampleJSContext.Default.Double]); // JSON serialization info for each parameter
В этом примере универсальный аргумент является обязательным, и
null
можно передать в качестве значения сведений сериализации JSON для возвращаемого типа.
Отправка исключений JavaScript в .NET
По умолчанию вызов методов JavaScript в HybridWebView может скрывать исключения, создаваемые кодом JavaScript. Чтобы включить отправку исключений JavaScript в .NET, где они перебрасываются как исключения .NET, добавьте следующий код в класс MauiProgram
:
static MauiProgram()
{
AppContext.SetSwitch("HybridWebView.InvokeJavaScriptThrowsExceptions", true);
}
По умолчанию все исключения, вызываемые кодом JavaScript, будут отправляться в .NET, где они повторно выбрасываются как исключения .NET.
Это позволяет создавать сценарии, в которых если ваш код C# вызывает код JavaScript, и код JavaScript завершится сбоем, то ошибка выполнения JavaScript будет отправлена в .NET, где она будет повторно выброшена как исключение .NET, которое может быть перехвачено и обработано.
Вызов C# из JavaScript
Код JavaScript приложения в HybridWebView может синхронно и асинхронно вызывать методы C# с необязательными параметрами и необязательным возвращаемым значением. Это выполняется посредством следующих настроек.
- Определение общедоступных методов C#, которые будут вызываться из JavaScript.
- Вызов метода SetInvokeJavaScriptTarget для задания объекта, который будет целевым объектом вызовов JavaScript из HybridWebView.
- Вызов методов C# из JavaScript.
В следующем примере определяются общедоступные синхронные и асинхронные методы для вызова из 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 async Task DoAsyncWork()
{
Debug.WriteLine("DoAsyncWork");
await Task.Delay(1000);
}
public async Task DoAsyncWorkParams(int i, string s)
{
Debug.WriteLine($"DoAsyncWorkParams: {i}, {s}");
await Task.Delay(1000);
}
public async Task<String> DoAsyncWorkReturn()
{
Debug.WriteLine("DoAsyncWorkReturn");
await Task.Delay(1000);
return "Hello from C#!";
}
public async Task<SyncReturn> DoAsyncWorkParamsReturn(int i, string s)
{
Debug.WriteLine($"DoAsyncWorkParamsReturn: {i}, {s}");
await Task.Delay(1000);
return new SyncReturn
{
Message = "Hello from C#!" + s,
Value = i
};
}
public class SyncReturn
{
public string? Message { get; set; }
public int Value { get; set; }
}
}
Затем необходимо вызвать SetInvokeJavaScriptTarget метод, чтобы задать объект, который будет целевым объектом вызовов JavaScript из HybridWebView:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
hybridWebView.SetInvokeJavaScriptTarget(this);
}
...
}
Методы, доступные в объекте, установленном с помощью метода SetInvokeJavaScriptTarget, можно вызвать из JavaScript с использованием функции window.HybridWebView.InvokeDotNet
.
// Synchronous methods
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']);
// Asynchronous methods
await window.HybridWebView.InvokeDotNet('DoAsyncWork');
await window.HybridWebView.InvokeDotNet('DoAsyncWorkParams', [123, 'hello']);
const retValue = await window.HybridWebView.InvokeDotNet('DoAsyncWorkReturn');
const retValue = await window.HybridWebView.InvokeDotNet('DoAsyncWorkParamsReturn', [123, 'hello']);
Функция window.HybridWebView.InvokeDotNet
JavaScript вызывает указанный метод C# с необязательными параметрами и необязательным возвращаемым значением.
Примечание.
window.HybridWebView.InvokeDotNet
Вызов функции JavaScript требует, чтобы приложение включало библиотеку JavaScript HybridWebView.js, указанную ранее в этой статье.