HybridWebView
El elemento HybridWebView de .NET Multi-platform App UI (.NET MAUI) habilita el hospedaje de contenido HTML/JS/CSS arbitrario en una vista web y la comunicación entre el código de la vista web (JavaScript) y el código que hospeda la vista web (C#/.NET). Por ejemplo, si ya dispones de una aplicación de React JS, puedes hospedarla en una multiplataforma nativa de .NET MAUI y compilar el back-end de la aplicación mediante C# y .NET.
HybridWebView define las siguientes propiedades:
- DefaultFile, de tipo
string?
, que especifica el archivo dentro del HybridRoot que se debe servir como archivo predeterminado. El valor predeterminado es index.html. - HybridRoot, de tipo
string?
, que es la ruta de acceso dentro de los recursos de activos sin procesar de la aplicación que contienen el contenido de la aplicación web. El valor predeterminado es wwwroot, que se asigna a Resources/Raw/wwwroot.
Además, HybridWebView define un evento RawMessageReceived que se genera cuando se recibe un mensaje sin procesar. El objeto HybridWebViewRawMessageReceivedEventArgs que acompaña al evento define una propiedad Message que contiene el mensaje.
El código C# de la aplicación puede invocar métodos de JavaScript sincrónicos y asincrónicos dentro de HybridWebView con los métodos InvokeJavaScriptAsync y EvaluateJavaScriptAsync. El código JavaScript de la aplicación también puede invocar métodos de C# de forma sincrónica. Para obtener más información, vea Invocación de JavaScript desde C# e Invocación de C# desde JavaScript.
Para crear una aplicación .NET MAUI con HybridWebView necesitas:
- Contenido web de la aplicación, que consta de HTML estático, JavaScript, CSS, imágenes y otros archivos.
- Un control HybridWebView como parte de la interfaz de usuario de la aplicación. Puedes hacerlo mediante una referencia a él en el XAML de la aplicación.
- Código en el contenido web y en C#/.NET, que usa las API de la HybridWebView para enviar mensajes entre los dos componentes.
Toda la aplicación, incluido el contenido web, se empaqueta y se ejecuta localmente en un dispositivo y se puede publicar en las tiendas de aplicaciones correspondientes. El contenido web se hospeda en un control de vista web nativo y se ejecuta dentro del contexto de la aplicación. Cualquier parte de la aplicación puede acceder a servicios web externos, pero no es un requisito.
Importante
De forma predeterminada, el HybridWebView control no estará disponible cuando esté habilitado el recorte completo o el AOT nativo. Para cambiar este comportamiento, consulte Recorte de modificadores de características.
Creación de una aplicación HybridWebView de .NET MAUI
Para crear una aplicación .NET MAUI con HybridWebView:
Abre un proyecto de aplicación .NET MAUI existente o crea un nuevo proyecto de aplicación .NET MAUI.
Agrega el contenido web al proyecto de aplicación .NET MAUI.
El contenido web de la aplicación debe incluirse como parte de un proyecto .NET MAUI como recursos sin procesar. Un recurso sin procesar es cualquier archivo de la carpeta Resources\Raw de la aplicación e incluye subcarpetas. Para una HybridWebView predeterminada, el contenido web debe colocarse en la carpeta Resources\Raw\wwwroot, con el archivo principal denominado index.html.
Una aplicación sencilla puede tener los siguientes archivos y contenidos:
Resources\Raw\wwwroot\index.html con contenido para la interfaz de usuario principal:
<!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 con la biblioteca estándar de HybridWebViewJavaScript:
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();
A continuación, agrega cualquier contenido web adicional al proyecto.
Advertencia
En algunos casos, Visual Studio podría agregar entradas al archivo .csproj del proyecto que no sean correctas. Cuando se usa la ubicación predeterminada para los recursos sin procesar, no debe haber ninguna entrada para estos archivos o carpetas en el archivo .csproj .
Agrega el control HybridWebView a la aplicación:
<Grid RowDefinitions="Auto,*" ColumnDefinitions="*"> <Button Text="Send message to JavaScript" Clicked="OnSendMessageButtonClicked" /> <HybridWebView x:Name="hybridWebView" RawMessageReceived="OnHybridWebViewRawMessageReceived" Grid.Row="1" /> </Grid>
Modifique el
CreateMauiApp
método de laMauiProgram
clase para habilitar las herramientas de desarrollo en los controles WebView subyacentes cuando la aplicación se ejecuta en la configuración de depuración. Para ello, llame al AddHybridWebViewDeveloperTools método en el IServiceCollection objeto :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(); } }
Usa las API de HybridWebView para enviar mensajes entre el código de JavaScript y 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"); }
Los mensajes anteriores se clasifican como sin procesar porque no se realiza ningún procesamiento adicional. También puedes codificar datos dentro del mensaje para realizar mensajería más avanzada.
Invocación de JavaScript a partir de C#
El código C# de la aplicación puede invocar de forma sincrónica y asincrónica los métodos de JavaScript dentro de HybridWebView, con parámetros opcionales y un valor devuelto opcional. Esto puede conseguirse con los métodos InvokeJavaScriptAsync y EvaluateJavaScriptAsync:
- El método EvaluateJavaScriptAsync ejecuta el código JavaScript proporcionado a través de un parámetro y devuelve el resultado como una cadena.
- El InvokeJavaScriptAsync método invoca un método de JavaScript especificado, opcionalmente pasando valores de parámetro y especifica un argumento genérico que indica el tipo del valor devuelto. Devuelve un objeto del tipo de argumento genérico que contiene el valor devuelto del método javaScript llamado . Internamente, los parámetros y los valores devueltos están codificados en JSON.
Invocación de JavaScript de forma sincrónica
Los métodos de JavaScript sincrónicos se pueden invocar con los métodos EvaluateJavaScriptAsync y InvokeJavaScriptAsync. En el ejemplo siguiente, el método InvokeJavaScriptAsync se usa para mostrar la invocación de JavaScript incrustado en el contenido web de una aplicación. Por ejemplo, un método simple de Javascript para agregar dos números podría definirse en el contenido web:
function AddNumbers(a, b) {
return a + b;
}
El método AddNumbers
de JavaScript se puede invocar a partir de C# con el método 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
La invocación del método requiere especificar objetos JsonTypeInfo
que incluyan información de serialización para los tipos usados en la operación. Estos objetos se crean automáticamente incluyendo la siguiente clase partial
en el proyecto:
[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.
}
Importante
La clase HybridSampleJsContext
debe ser partial
para que la generación de código pueda proporcionar la implementación cuando se compila el proyecto. Si el tipo está anidado en otro tipo, ese tipo también debe ser partial
.
Invocación de JavaScript de forma asincrónica
Los métodos de JavaScript asincrónicos se pueden invocar con los métodos EvaluateJavaScriptAsync y InvokeJavaScriptAsync. En el ejemplo siguiente, el método InvokeJavaScriptAsync se usa para mostrar la invocación de JavaScript incrustado en el contenido web de una aplicación. Por ejemplo, un método de JavaScript que recupera datos de forma asincrónica podría definirse en el contenido web:
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;
}
El método EvaluateMeWithParamsAndAsyncReturn
de JavaScript se puede invocar a partir de C# con el método 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
En este ejemplo, asyncResult
es un que Dictionary<string, string>
contiene los datos JSON de la solicitud web.
La invocación del método requiere especificar objetos JsonTypeInfo
que incluyan información de serialización para los tipos usados en la operación. Estos objetos se crean automáticamente incluyendo la siguiente clase partial
en el proyecto:
[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.
}
Importante
La clase HybridSampleJsContext
debe ser partial
para que la generación de código pueda proporcionar la implementación cuando se compila el proyecto. Si el tipo está anidado en otro tipo, ese tipo también debe ser partial
.
Invocación de C# desde JavaScript
El código JavaScript de la HybridWebView aplicación dentro de puede invocar de forma sincrónica métodos de C#, con parámetros opcionales y un valor devuelto opcional. Para lograr esto:
- Definición de métodos públicos de C# que se invocarán desde JavaScript.
- Llamar al SetInvokeJavaScriptTarget método para establecer el objeto que será el destino de las llamadas de JavaScript desde HybridWebView.
- Llamar a los métodos de C# desde JavaScript.
Importante
Actualmente no se admite la invocación asincrónica de métodos de C# desde JavaScript.
En el ejemplo siguiente se definen cuatro métodos públicos para invocar desde 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; }
}
}
A continuación, debe llamar al SetInvokeJavaScriptTarget método para establecer el objeto que será el destino de las llamadas de JavaScript desde HybridWebView:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
hybridWebView.SetInvokeJavaScriptTarget(this);
}
...
}
Los métodos públicos del conjunto de objetos mediante el SetInvokeJavaScriptTarget método se pueden invocar desde JavaScript con la window.HybridWebView.InvokeDotNet
función :
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']);
La window.HybridWebView.InvokeDotNet
función de JavaScript invoca un método de C# especificado, con parámetros opcionales y un valor devuelto opcional.
Nota:
La invocación de la función de JavaScript requiere que la window.HybridWebView.InvokeDotNet
aplicación incluya la biblioteca de JavaScript HybridWebView.js que se muestra anteriormente en este artículo.