Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
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, of type
string?
, which is the path within the app's raw asset resources that contain the web app's contents. 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# sincrónicos y asincrónicos. 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.
Important
De forma predeterminada, el HybridWebView control no estará disponible cuando esté habilitado el recorte completo o el AOT nativo. To change this behavior, see Trimming feature switches.
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. A raw asset is any file in the app's Resources\Raw folder, and includes sub-folders. 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); } 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 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": 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();
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 método
CreateMauiApp
de su claseMauiProgram
para habilitar las herramientas de desarrollo en los controles WebView subyacentes cuando su aplicación se ejecuta en 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"); }
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"); }
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.
Nota:
.NET 10 incluye una sobrecarga de InvokeJavaScriptAsync que invoca un método de JavaScript especificado sin especificar información sobre el tipo de valor devuelto. For more information, see Invoke JavaScript methods that don't return a value.
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.
}
Important
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 Dictionary<string, string>
que 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.
}
Important
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 métodos de JavaScript que no devuelven un valor
El método InvokeJavaScriptAsync también se puede usar para invocar métodos de JavaScript que no devuelven un valor. Existen enfoques alternativos para hacerlo:
Invoque el InvokeJavaScriptAsync, especificando el nombre del método javaScript y los parámetros opcionales:
await hybridWebView.InvokeJavaScriptAsync("javaScriptWithVoidReturn"); // JavaScript method name
En este ejemplo, solo se especifica el nombre del método JavaScript.
Invoque el método InvokeJavaScriptAsync sin especificar el argumento genérico:
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
En este ejemplo, aunque el argumento genérico no es necesario, sigue siendo necesario proporcionar información de serialización JSON para el tipo de valor devuelto aunque no se use.
Invoque el método InvokeJavaScriptAsync al especificar el argumento genérico:
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
En este ejemplo, se requiere el argumento genérico y se puede pasar
null
como valor de la información de serialización JSON para el tipo de valor devuelto.
Envío de excepciones de JavaScript a .NET
De forma predeterminada, la invocación de métodos de JavaScript en un HybridWebView puede ocultar las excepciones producidas por el código JavaScript. Para activar el envío de excepciones de JavaScript a .NET, donde se vuelven a lanzar como excepciones de .NET, agregue el siguiente código a la clase MauiProgram
:
static MauiProgram()
{
AppContext.SetSwitch("HybridWebView.InvokeJavaScriptThrowsExceptions", true);
}
By default, any exceptions that are thrown by your JavaScript code will be sent to .NET, where they're re-thrown as .NET exceptions.
Esto permite escenarios en los que si el código de C# llama al código javaScript y se produce un error en el código JavaScript, el error de JavaScript se enviará a .NET donde se volverá a producir como una excepción de .NET que se pueda detectar y controlar.
Invocación de C# desde JavaScript
El código JavaScript de la aplicación dentro de la HybridWebView puede invocar de forma sincrónica y asincró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.
- Calling the C# methods from JavaScript.
En el ejemplo siguiente se definen métodos sincrónicos y asincrónicos 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 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; }
}
}
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 :
// 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']);
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.