HybridWebView

.NET Multi-platform App UI (.NET MAUI) HybridWebView 支持在 Web 视图中托管任意 HTML/JS/CSS 内容,并允许 Web 视图 (JavaScript) 中的代码与托管 Web 视图 (C#/.NET) 的代码之间进行通信。 例如,如果已有 React JS 应用,则可以将其托管在跨平台 .NET MAUI 本机应用中,并使用 C# 和 .NET 生成应用的后端。

HybridWebView 定义以下属性:

  • DefaultFile,类型为 string?,指定 HybridRoot 中应作为默认文件的文件。 默认值为 index.html
  • HybridRoot,类型为 string?,是包含 Web 应用内容的应用原始资产资源中的路径。 默认值为 wwwroot,映射到 Resources/Raw/wwwroot

此外,HybridWebView 定义在收到原始消息时引发的 RawMessageReceived 事件。 事件附带的 HybridWebViewRawMessageReceivedEventArgs 对象定义包含消息的 Message 属性。

应用的 C# 代码可以使用 InvokeJavaScriptAsyncEvaluateJavaScriptAsync 方法在 HybridWebView 中调用同步和异步 JavaScript 方法。 有关详细信息,请参阅从 C# 调用 JavaScript

若要使用 HybridWebView 生成 .NET MAUI 应用,需要:

  • 应用的 Web 内容,由静态 HTML、JavaScript、CSS、图像和其他文件组成。
  • 作为应用 UI 的一部分的 HybridWebView 控件。 这可以通过在应用的 XAML 中引用它来实现。
  • Web 内容和 C#/.NET 中的代码,使用 HybridWebView API 在两个组件之间发送消息。

整个应用(包括 Web 内容)已打包并在设备上本地运行,并可以发布到对应的应用商店。 Web 内容托管在本机 Web 视图控件中,并在应用的上下文中运行。 应用的任何部分都可以访问外部 Web 服务,但这不是必须的。

创建 .NET MAUI HybridWebView 应用

若要使用 HybridWebView 生成 .NET MAUI 应用,需要:

  1. 打开现有的 .NET MAUI 应用项目或新建 .NET MAUI 应用项目。

  2. 将 Web 内容添加到 .NET MAUI 应用项目。

    应用的 Web 内容应作为原始资产包含在 .NET MAUI 项目中。 原始资产是应用的 Resources\Raw 文件夹中的任何文件,包括子文件夹。 对于默认 HybridWebView,Web 内容应放置在 Resources\Raw\wwwroot 文件夹中,其主文件名为 index.html

    简单的应用可能包含以下文件和内容:

    • Resources\Raw\wwwroot\index.html,其中包含主 UI 内容:

      <!DOCTYPE html>
      
      <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
      <head>
          <meta charset="utf-8" />
          <title></title>
          <link rel="icon" href="data:,">
          <script src="scripts/HybridWebView.js"></script>
          <script>
              window.addEventListener(
                  "HybridWebViewMessageReceived",
                  function (e) {
                      var messageFromCSharp = document.getElementById("messageFromCSharp");
                      messageFromCSharp.value += '\r\n' + e.detail.message;
                  });
          </script>
      </head>
      <body>
          <h1>HybridWebView app!</h1>
          <div>
              <button onclick="window.HybridWebView.SendRawMessage('Message from JS!')">Send message to C#</button>
          </div>
          <div>
              Messages from C#: <textarea readonly id="messageFromCSharp" style="width: 80%; height: 300px;"></textarea>
          </div>
      </body>
      </html>
      
    • Resources\Raw\wwwroot\scripts\HybridWebView.js,其中包含标准 HybridWebView JavaScript 库:

      window.HybridWebView = {
          "Init": function () {
              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 (message) {
              window.HybridWebView.__SendMessageInternal('RawMessage', message);
          },
      
          "__SendMessageInternal": function (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);
              }
          },
      
          "InvokeMethod": function (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 (taskId, result) {
              // Make sure the result is a string
              if (result && typeof (result) !== 'string') {
                  result = JSON.stringify(result);
              }
      
              window.HybridWebView.__SendMessageInternal('InvokeMethodCompleted', taskId + '|' + result);
          }
      }
      
      window.HybridWebView.Init();
      

    然后,将任何其他 Web 内容添加到项目。

    警告

    在某些情况下,Visual Studio 可能会向项目的 .csproj 文件添加不正确的条目。 使用原始资产的默认位置时,.csproj 文件中不应有任何这些文件或文件夹的条目。

  3. HybridWebView 控件添加到应用:

    <Grid RowDefinitions="Auto,*"
          ColumnDefinitions="*">
        <Button Text="Send message to JavaScript"
                Clicked="OnSendMessageButtonClicked" />
        <HybridWebView x:Name="hybridWebView"
                       RawMessageReceived="OnHybridWebViewRawMessageReceived"
                       Grid.Row="1" />
    </Grid>
    
  4. 使用 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");
    }
    

    因为没有执行其他处理,所以上述消息被归类为原始消息。 还可以对消息中的数据进行编码,以执行更高级的消息传递。

从 C# 调用 JavaScript

应用的 C# 代码可以使用可选参数和可选返回值在 HybridWebView 中同步和异步调用 JavaScript 方法。 这可以通过 InvokeJavaScriptAsyncEvaluateJavaScriptAsync 方法实现:

  • EvaluateJavaScriptAsync 方法运行通过参数提供的 JavaScript 代码,并将结果作为字符串返回。
  • 该方法 InvokeJavaScriptAsync 调用指定的 JavaScript 方法,可以选择传入参数值,并指定指示返回值的类型的泛型参数。 它返回泛型参数类型的对象,该对象包含调用 JavaScript 方法的返回值。 在内部,参数和返回值已经过 JSON 编码。

调用同步 JavaScript

可以使用 EvaluateJavaScriptAsyncInvokeJavaScriptAsync 方法调用同步 JavaScript 方法。 在以下示例中,InvokeJavaScriptAsync 方法用于演示如何调用嵌入在应用 Web 内容中的 JavaScript。 例如,可以在 Web 内容中定义用于添加两个数字的简单 Javascript 方法:

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

可以使用 InvokeJavaScriptAsync 方法从 C# 中调用 AddNumbers 方法:

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

可以使用 EvaluateJavaScriptAsyncInvokeJavaScriptAsync 方法调用异步 JavaScript 方法。 在以下示例中,InvokeJavaScriptAsync 方法用于演示如何调用嵌入在应用 Web 内容中的 JavaScript。 例如,可以在 Web 内容中定义异步检索数据的 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;
}

可以使用 InvokeJavaScriptAsync 方法从 C# 中调用 EvaluateMeWithParamsAndAsyncReturn JavaScript 方法:

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> 来自 Web 请求的 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