共用方式為


HybridWebView

流覽範例。 流覽範例

.NET 多平臺應用程式 UI (.NET MAUI) HybridWebView 可讓您在網頁檢視中裝載任意 HTML/JS/CSS 內容,並啟用 Web 檢視中程式代碼 (JavaScript) 與裝載 Web 檢視的程式代碼之間的通訊 (C#/.NET)。 例如,如果您有現有的 React JS 應用程式,您可以在跨平臺 .NET MAUI 原生應用程式中裝載它,並使用 C# 和 .NET 建置應用程式的後端。

HybridWebView 會定義下列屬性:

  • DefaultFilestring?別為 的 ,指定 內 HybridRoot 應做為預設檔案的 檔案。 預設值為 index.html
  • HybridRoot類型 string?為,這是應用程式原始資產資源內包含 Web 應用程式內容的路徑。 默認值為 wwwroot,對應至 Resources/Raw/wwwroot

此外, HybridWebView 定義 RawMessageReceived 收到原始訊息時所引發的事件。 事件 HybridWebViewRawMessageReceivedEventArgs 隨附的物件會 Message 定義包含訊息的屬性。

應用程式的 C# 程式代碼可以使用 和 EvaluateJavaScriptAsync 方法來叫用 InvokeJavaScriptAsync 中的HybridWebView同步和異步 JavaScript 方法。 您應用程式的 JavaScript 程式代碼也可以同步叫用 C# 方法。 如需詳細資訊,請參閱 從 C# 叫用 JavaScript 和 從 JavaScript 叫用 C# 。

若要建立 .NET MAUI 應用程式, HybridWebView 您需要:

  • 應用程式的 Web 內容,其中包含靜態 HTML、JavaScript、CSS、影像和其他檔案。
  • 控制件 HybridWebView 是應用程式 UI 的一部分。 這可藉由在應用程式的 XAML 中參考它來達成。
  • Web 內容和 C#/.NET 中的程式碼,會使用 HybridWebView API 在兩個元件之間傳送訊息。

整個應用程式,包括 Web 內容,會封裝並在裝置本機執行,並可發佈至適用的應用程式市集。 Web 內容裝載於原生 Web 檢視控件內,並在應用程式內容中執行。 應用程式的任何部分都可以存取外部 Web 服務,但不需要。

重要

根據預設, HybridWebView 啟用完整修剪或原生 AOT 時,將無法使用控件。 若要變更此行為,請參閱 修剪功能參數

建立 .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

    簡單的應用程式可能有下列檔案和內容:

    • 資源\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:,">
          <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 搭配標準 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": 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();
      

    然後,將任何其他 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. 修改 類別MauiProgram的 方法,CreateMauiApp以在應用程式在偵錯組態中執行時,在基礎 WebView 控件上啟用開發人員工具。 若要這樣做,請在 物件上IServiceCollection呼叫 AddHybridWebViewDeveloperTools 方法:

    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. 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 方法,並搭配選擇性參數和選擇性傳回值。 這可以使用 和 EvaluateJavaScriptAsync 方法來達成InvokeJavaScriptAsync

  • 方法會 EvaluateJavaScriptAsync 執行透過 參數提供的 JavaScript 程式代碼,並以字串傳回結果。
  • 方法 InvokeJavaScriptAsync 會叫用指定的 JavaScript 方法,選擇性地傳入參數值,並指定泛型自變數,指出傳回值的型別。 它會傳回泛型自變數類型的 物件,其中包含所呼叫 JavaScript 方法的傳回值。 在內部,參數和傳回值會經過 JSON 編碼。

叫用同步 JavaScript

您可以使用 和 InvokeJavaScriptAsync 方法來叫用EvaluateJavaScriptAsync同步 JavaScript 方法。 在下列範例中,方法 InvokeJavaScriptAsync 可用來示範叫用內嵌在應用程式Web內容中的JavaScript。 例如,在 Web 內容中可以定義新增兩個數位的簡單 Javascript 方法:

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

AddNumbers您可以使用 方法從 C# 叫用 InvokeJavaScriptAsync JavaScript 方法:

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

您可以使用 和 InvokeJavaScriptAsync 方法來叫用EvaluateJavaScriptAsync異步 JavaScript 方法。 在下列範例中,方法 InvokeJavaScriptAsync 可用來示範叫用內嵌在應用程式Web內容中的JavaScript。 例如,以異步方式擷取數據的 Javascript 方法可以在您的 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;
}

EvaluateMeWithParamsAndAsyncReturn您可以使用 方法從 C# 叫用 InvokeJavaScriptAsync 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

在這裡範例中, asyncResultDictionary<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

從 JavaScript 叫用 C#

應用程式中的 JavaScript 程式代碼 HybridWebView 可以同步叫用 C# 方法,並搭配選擇性參數和選擇性傳回值。 執行方式如下:

  • 定義將從 JavaScript 叫用的公用 C# 方法。
  • SetInvokeJavaScriptTarget呼叫 方法,以設定 對象,這個物件將是 來自HybridWebView的JavaScript呼叫目標。
  • 從 JavaScript 呼叫 C# 方法。

重要

目前不支援從 JavaScript 以異步方式叫用 C# 方法。

下列範例會定義四個公用方法,以從 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; }
    }  
}

然後,您必須呼叫 SetInvokeJavaScriptTarget 方法,以設定 將成為 JavaScript 呼叫 HybridWebView目標的物件:

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

    ...
}

然後,就可以使用 函式從 JavaScript 叫用window.HybridWebView.InvokeDotNet物件集SetInvokeJavaScriptTarget上的公用方法:

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']);

JavaScript 函 window.HybridWebView.InvokeDotNet 式會使用選擇性參數和選擇性傳回值叫用指定的 C# 方法。

注意

window.HybridWebView.InvokeDotNet 用 JavaScript 函式需要您的應用程式包含 本文稍早所列HybridWebView.js JavaScript 連結庫。