Condividi tramite


HybridWebView

Sfogliare l'esempio. Esplorare l'esempio

L'interfaccia utente dell'app multipiattaforma .NET (.NET MAUI) consente di ospitare contenuto HTML/JS/CSS arbitrario in una visualizzazione Web e consente la comunicazione tra il codice nella visualizzazione Web (JavaScript) HybridWebView e il codice che ospita la visualizzazione Web (C#/.NET). Ad esempio, se si dispone di un'app React JS esistente, è possibile ospitarla in un'app nativa .NET MAUI multipiattaforma e compilare il back-end dell'app usando C# e .NET.

HybridWebView definisce le proprietà seguenti:

  • DefaultFile, di tipo string?, che specifica il file all'interno di HybridRoot che deve essere gestito come file predefinito. Il valore predefinito è index.html.
  • HybridRoot, di tipo string?, ovvero il percorso all'interno delle risorse di asset non elaborati dell'app che contengono il contenuto dell'app Web. Il valore predefinito è wwwroot, che esegue il mapping a Resources/Raw/wwwroot.

Definisce inoltre HybridWebView un RawMessageReceived evento generato quando viene ricevuto un messaggio non elaborato. L'oggetto HybridWebViewRawMessageReceivedEventArgs che accompagna l'evento definisce una Message proprietà che contiene il messaggio.

Il codice C# dell'app può richiamare metodi JavaScript sincroni e asincroni all'interno di HybridWebView con i InvokeJavaScriptAsync metodi e EvaluateJavaScriptAsync . Il codice JavaScript dell'app può anche richiamare in modo sincrono i metodi C#. Per altre informazioni, vedere Richiamare JavaScript da C# e Richiamare C# da JavaScript.

Per creare un'app MAUI .NET con HybridWebView è necessario:

  • Contenuto Web dell'app, costituito da HTML statico, JavaScript, CSS, immagini e altri file.
  • Controllo HybridWebView come parte dell'interfaccia utente dell'app. A questo scopo, è possibile fare riferimento a esso nel codice XAML dell'app.
  • Codice nel contenuto Web e in C#/.NET che usa le HybridWebView API per inviare messaggi tra i due componenti.

L'intera app, incluso il contenuto Web, viene inserita in un pacchetto ed eseguita in locale in un dispositivo e può essere pubblicata negli App Store applicabili. Il contenuto Web è ospitato all'interno di un controllo visualizzazione Web nativo e viene eseguito nel contesto dell'app. Qualsiasi parte dell'app può accedere a servizi Web esterni, ma non è necessaria.

Importante

Per impostazione predefinita, il HybridWebView controllo non sarà disponibile quando è abilitato il taglio completo o Native AOT. Per modificare questo comportamento, vedere Opzioni delle funzionalità di taglio.

Creare un'app .NET MAUI HybridWebView

Per creare un'app MAUI .NET con :HybridWebView

  1. Aprire un progetto di app .NET MAUI esistente o creare un nuovo progetto di app MAUI .NET.

  2. Aggiungere il contenuto Web al progetto di app MAUI .NET.

    Il contenuto Web dell'app deve essere incluso come parte di un progetto MAUI .NET come asset non elaborati. Un asset non elaborato è qualsiasi file nella cartella Resources\Raw dell'app e include sottocartelle. Per un valore predefinito HybridWebView, il contenuto Web deve essere inserito nella cartella Resources\Raw\wwwroot , con il file principale denominato index.html.

    Un'app semplice potrebbe avere i file e il contenuto seguenti:

    • Resources\Raw\wwwroot\index.html con contenuto per l'interfaccia utente principale:

      <!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 libreria JavaScript standard HybridWebView :

      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();
      

    Aggiungere quindi qualsiasi contenuto Web aggiuntivo al progetto.

    Avviso

    In alcuni casi Visual Studio potrebbe aggiungere voci al file con estensione csproj del progetto non corretto. Quando si usa il percorso predefinito per gli asset non elaborati, non devono essere presenti voci per questi file o cartelle nel file con estensione csproj .

  3. Aggiungere il HybridWebView controllo all'app:

    <Grid RowDefinitions="Auto,*"
          ColumnDefinitions="*">
        <Button Text="Send message to JavaScript"
                Clicked="OnSendMessageButtonClicked" />
        <HybridWebView x:Name="hybridWebView"
                       RawMessageReceived="OnHybridWebViewRawMessageReceived"
                       Grid.Row="1" />
    </Grid>
    
  4. Modificare il CreateMauiApp metodo della MauiProgram classe per abilitare gli strumenti di sviluppo nei controlli WebView sottostanti quando l'app è in esecuzione nella configurazione di debug. A tale scopo, chiamare il AddHybridWebViewDeveloperTools metodo sull'oggetto 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();
        }
    }
    
  5. Usare le HybridWebView API per inviare messaggi tra il codice JavaScript e 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");
    }
    

    I messaggi precedenti sono elencati come non elaborati perché non viene eseguita alcuna elaborazione aggiuntiva. È anche possibile codificare i dati all'interno del messaggio per eseguire messaggi più avanzati.

Richiamare JavaScript da C#

Il codice C# dell'app può richiamare in modo sincrono e asincrono i metodi JavaScript all'interno HybridWebViewdi , con parametri facoltativi e un valore restituito facoltativo. Questa operazione può essere ottenuta con i InvokeJavaScriptAsync metodi e EvaluateJavaScriptAsync :

  • Il EvaluateJavaScriptAsync metodo esegue il codice JavaScript fornito tramite un parametro e restituisce il risultato come stringa.
  • Il InvokeJavaScriptAsync metodo richiama un metodo JavaScript specificato, passando facoltativamente i valori dei parametri e specifica un argomento generico che indica il tipo del valore restituito. Restituisce un oggetto del tipo di argomento generico che contiene il valore restituito del metodo JavaScript denominato. Internamente, i parametri e i valori restituiti sono codificati in JSON.

Richiamare JavaScript sincrono

I metodi JavaScript sincroni possono essere richiamati con i EvaluateJavaScriptAsync metodi e InvokeJavaScriptAsync . Nell'esempio seguente viene usato il InvokeJavaScriptAsync metodo per dimostrare di richiamare JavaScript incorporato nel contenuto Web di un'app. Ad esempio, un semplice metodo Javascript per aggiungere due numeri può essere definito nel contenuto Web:

function AddNumbers(a, b) {
    return a + b;
}

Il AddNumbers metodo JavaScript può essere richiamato da C# con il InvokeJavaScriptAsync metodo :

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 chiamata al metodo richiede la specifica di JsonTypeInfo oggetti che includono informazioni di serializzazione per i tipi utilizzati nell'operazione. Questi oggetti vengono creati automaticamente includendo la classe seguente partial nel progetto:

[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 HybridSampleJsContext classe deve essere partial in modo che la generazione di codice possa fornire l'implementazione quando il progetto viene compilato. Se il tipo è annidato in un altro tipo, tale tipo deve anche essere partial.

Richiamare JavaScript asincrono

I metodi JavaScript asincroni possono essere richiamati con i EvaluateJavaScriptAsync metodi e InvokeJavaScriptAsync . Nell'esempio seguente viene usato il InvokeJavaScriptAsync metodo per dimostrare di richiamare JavaScript incorporato nel contenuto Web di un'app. Ad esempio, un metodo Javascript che recupera in modo asincrono i dati può essere definito nel contenuto 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;
}

Il EvaluateMeWithParamsAndAsyncReturn metodo JavaScript può essere richiamato da C# con il InvokeJavaScriptAsync metodo :

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 questo esempio, asyncResult è un oggetto Dictionary<string, string> che contiene i dati JSON della richiesta Web.

La chiamata al metodo richiede la specifica di JsonTypeInfo oggetti che includono informazioni di serializzazione per i tipi utilizzati nell'operazione. Questi oggetti vengono creati automaticamente includendo la classe seguente partial nel progetto:

[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 HybridSampleJsContext classe deve essere partial in modo che la generazione di codice possa fornire l'implementazione quando il progetto viene compilato. Se il tipo è annidato in un altro tipo, tale tipo deve anche essere partial.

Richiamare C# da JavaScript

Il codice JavaScript dell'app all'interno HybridWebView di può richiamare in modo sincrono i metodi C#, con parametri facoltativi e un valore restituito facoltativo. A tale scopo, è possibile:

  • Definizione di metodi C# pubblici che verranno richiamati da JavaScript.
  • Chiamata del SetInvokeJavaScriptTarget metodo per impostare l'oggetto che sarà la destinazione delle chiamate JavaScript da HybridWebView.
  • Chiamata dei metodi C# da JavaScript.

Importante

La chiamata asincrona di metodi C# da JavaScript non è attualmente supportata.

L'esempio seguente definisce quattro metodi pubblici per richiamare da 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; }
    }  
}

È quindi necessario chiamare il SetInvokeJavaScriptTarget metodo per impostare l'oggetto che sarà la destinazione delle chiamate JavaScript da HybridWebView:

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
        hybridWebView.SetInvokeJavaScriptTarget(this);
    }

    ...
}

I metodi pubblici sul set di oggetti tramite il SetInvokeJavaScriptTarget metodo possono quindi essere richiamati da JavaScript con la window.HybridWebView.InvokeDotNet funzione :

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 funzione JavaScript richiama un metodo C# specificato, con parametri facoltativi e un valore restituito facoltativo.

Nota

Per richiamare la funzione JavaScript è necessario che l'app window.HybridWebView.InvokeDotNet includa la libreria JavaScript HybridWebView.js elencata in precedenza in questo articolo.