次の方法で共有


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# コードは、HybridWebView メソッドと InvokeJavaScriptAsync メソッドを使用して、EvaluateJavaScriptAsync 内で同期および非同期の JavaScript メソッドを呼び出すことができます。 アプリの JavaScript コードでは、同期および非同期の C# メソッドを呼び出すこともできます。 詳細については、「C#Invoke JavaScript」と JavaScript の「Invoke C#」を参照

HybridWebView を使用して .NET MAUI アプリを作成するには、次のものが必要です。

  • 静的 HTML、JavaScript、CSS、画像、およびその他のファイルで構成されるアプリの Web コンテンツ。
  • アプリの UI の一部としての HybridWebView コントロール。 これは、アプリの XAML で参照することで実現できます。
  • HybridWebView API を使用して 2 つのコンポーネント間でメッセージを送信する Web コンテンツと C#/.NET のコード。

Web コンテンツを含むアプリ全体がパッケージ化され、デバイス上でローカルに実行され、適用可能なアプリ ストアに発行できます。 Web コンテンツはネイティブ Web ビュー コントロール内でホストされ、アプリのコンテキスト内で実行されます。 アプリのどの部分からも外部 Web サービスにアクセスできますが、その必要はありません。

重要

既定では、フル トリミングまたはネイティブ AOT が有効になっている場合、 HybridWebView コントロールは使用できません。 この動作を変更するには、「トリミング機能の切り替え」を参照してください。

警告

Windows では、 Program Files ディレクトリにインストールされている WebView2 ベースのコントロールを使用するアプリは、コンテンツを正しくレンダリングできないことがあります。 これは、WebView2 がキャッシュ ファイルとユーザー データ ファイルを、 Program Filesの書き込みアクセス許可が制限されているアプリのインストール ディレクトリに書き込もうとしたために発生します。 この問題を解決するには、WebView コントロールが初期化される前に、 WEBVIEW2_USER_DATA_FOLDER 環境変数を設定します。

#if WINDOWS
var userDataFolder = Path.Combine(FileSystem.AppDataDirectory, "WebView2");
Environment.SetEnvironmentVariable("WEBVIEW2_USER_DATA_FOLDER", userDataFolder);
#endif

WebView コントロールが作成される前に、このコードを App.xaml.cs コンストラクターまたは Platforms\Windows\App.xaml.cs に配置します。 これにより、WebView2 は、制限されたプログラム ファイルの場所ではなく、ユーザーの AppData ディレクトリ内の書き込み可能な場所を使用するように指示されます。

ブラウザー エンジン

WebView では、各プラットフォーム上の異なるブラウザー エンジンを使用して Web コンテンツをレンダリングします。

  • Windows: Microsoft Edge (Chromium) ブラウザー エンジンに基づく WebView2 を使用します。 これにより、最新の Web 標準のサポートと、Edge ブラウザーとの一貫した動作が提供されます。
  • Android: Chromium ブラウザー エンジンに基づく android.webkit.WebViewを使用します。 特定のバージョンは、デバイスにインストールされている Android WebView システム コンポーネントによって異なります。
  • iOS および Mac Catalyst: Safari WebKit ブラウザー エンジンに基づく WKWebViewを使用します。 これは、iOS と macOS の Safari ブラウザーで使用されるのと同じエンジンです。

これらのプラットフォーム固有の実装は、Web コンテンツがプラットフォーム間で異なるレンダリングを行う可能性があることを意味し、一部のプラットフォーム固有の Web API は特定のプラットフォームでのみ使用できます。 クロスプラットフォーム アプリを開発する場合は、すべてのターゲット プラットフォームで 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:,">
          <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>
      
      <!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="_framework/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 と標準の 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": 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();
      

    次に、追加の 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. CreateMauiApp クラスのMauiProgram メソッドを変更して、アプリがデバッグ構成で実行されているときに、基になる WebView コントロールの開発者ツールを有効にします。 これを行うには、AddHybridWebViewDeveloperTools オブジェクトで 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. 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");
    }
    
    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");
    }
    

    上記のメッセージは、追加の処理が実行されないため、生として分類されます。 メッセージ内のデータをエンコードして、より高度なメッセージングを実行することもできます。

C# から JavaScript を呼び出す

アプリの C# コードは、省略可能なパラメーターと省略可能な戻り値を使用して、HybridWebView 内で JavaScript メソッドを同期または非同期で呼び出すことができます。 これは、InvokeJavaScriptAsync メソッドと EvaluateJavaScriptAsync メソッドを使用して実現できます。

  • EvaluateJavaScriptAsync メソッドは、パラメーターを使用して指定された JavaScript コードを実行し、結果を文字列として返します。
  • InvokeJavaScriptAsync メソッドは、指定した JavaScript メソッドを呼び出し、必要に応じてパラメーター値を渡し、戻り値の型を示すジェネリック引数を指定します。 呼び出された JavaScript メソッドの戻り値を含むジェネリック引数型のオブジェクトを返します。 内部的には、パラメーターと戻り値は JSON 形式にエンコードされます。

.NET 10 には、戻り値の型に関する情報を指定せずに、指定した JavaScript メソッドを呼び出す InvokeJavaScriptAsync オーバーロードが含まれています。 詳細については、「返さない JavaScript メソッドを呼び出す」を参照してください。 x

同期 JavaScript 呼び出し

同期 JavaScript メソッドは、EvaluateJavaScriptAsync メソッドと InvokeJavaScriptAsync メソッドを使用して呼び出すことができます。 次の例では、 InvokeJavaScriptAsync メソッドを使用して、アプリの Web コンテンツに埋め込まれている JavaScript の呼び出しを示しています。 たとえば、2 つの数値を追加する単純な Javascript メソッドを Web コンテンツで定義できます。

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

AddNumbers JavaScript メソッドは、InvokeJavaScriptAsync メソッドを使用して C# から呼び出すことができます。

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 呼び出し

非同期 JavaScript メソッドは、EvaluateJavaScriptAsync メソッドと InvokeJavaScriptAsync メソッドを使用して呼び出すことができます。 次の例では、 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 JavaScript メソッドは、InvokeJavaScriptAsync メソッドを使用して C# から呼び出すことができます。

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 は Web 要求からの JSON データを含む Dictionary<string, string> です。

メソッドの呼び出しでは、操作で使用される型のシリアル化情報を含む 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 メソッドを呼び出す

InvokeJavaScriptAsync メソッドを使用して、値を返さない JavaScript メソッドを呼び出すこともできます。 これを行うには、別の方法があります。

  • JavaScript メソッド名と任意の省略可能なパラメーターを指定して、InvokeJavaScriptAsyncを呼び出します。

    await hybridWebView.InvokeJavaScriptAsync("javaScriptWithVoidReturn"); // JavaScript method name
    

    この例では、JavaScript メソッド名のみが指定されています。

  • ジェネリック引数を指定せずに、InvokeJavaScriptAsync メソッドを呼び出します。

    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
    

    この例では、ジェネリック引数は必要ありませんが、戻り値の型に対して JSON シリアル化情報を指定する必要はありますが、使用されていません。

  • ジェネリック引数を指定しながら、InvokeJavaScriptAsync メソッドを呼び出します。

    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
    

    この例では、ジェネリック引数が必要であり、戻り値の型の JSON シリアル化情報の値として null を渡すことができます。

JavaScript 例外を .NET に送信する

既定では、HybridWebView で JavaScript メソッドを呼び出すことで、JavaScript コードによってスローされる例外を非表示にすることができます。 JavaScript 例外を .NET に送信して、.NET 例外として再スローされるようにするには、次のコードを MauiProgram クラスに追加してください。

static MauiProgram()
{
    AppContext.SetSwitch("HybridWebView.InvokeJavaScriptThrowsExceptions", true);
}

デフォルトでは、JavaScript コードによってスローされるすべての例外が .NET に送信され、.NET 例外として再スローされます。

これにより、C# コードが JavaScript コードを呼び出し、JavaScript コードが失敗した場合、JavaScript エラーが .NET に送信され、キャッチして処理できる .NET 例外として再スローされるシナリオが可能になります。

JavaScript から C# を呼び出す

HybridWebView 内のアプリの JavaScript コードは、省略可能なパラメーターと省略可能な戻り値を使用して、C# メソッドを同期的および非同期的に呼び出すことができます。 これは、次のようにして実現できます。

  • JavaScript から呼び出されるパブリック C# メソッドの定義。
  • SetInvokeJavaScriptTarget メソッドを呼び出して、HybridWebViewからの JavaScript 呼び出しのターゲットとなるオブジェクトを設定します。
  • 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 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; }
    }  
}

その後、 SetInvokeJavaScriptTarget メソッドを呼び出して、 HybridWebViewからの JavaScript 呼び出しのターゲットとなるオブジェクトを設定する必要があります。

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

その後、 SetInvokeJavaScriptTarget メソッドを介してオブジェクト セットのパブリック メソッドを、 window.HybridWebView.InvokeDotNet 関数を使用して JavaScript から呼び出すことができます。

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

window.HybridWebView.InvokeDotNet JavaScript 関数は、省略可能なパラメーターと省略可能な戻り値を使用して、指定された C# メソッドを呼び出します。

window.HybridWebView.InvokeDotNet JavaScript 関数を呼び出すには、この記事で前述した HybridWebView.js JavaScript ライブラリをアプリに含める必要があります。

JavaScript から C に複合型を渡す#

JavaScript から C# メソッドを呼び出すときは、複合型 (プリミティブだけでなくオブジェクト) をパラメーターとして渡し、複合型を戻り値として受け取ることができます。 C# から JavaScript を呼び出す場合とは異なり、JavaScript から C# への呼び出しのJsonSerializerContextを定義する必要はありません。 HybridWebViewでは、リフレクション ベースの JSON 逆シリアル化を使用して、パラメーターが自動的に逆シリアル化されます。

JavaScript から C# に複合型を渡すには:

  1. パラメーターまたは戻り値の型として使用される C# クラスを定義します。

    public class Person
    {
        public string? Name { get; set; }
        public int Age { get; set; }
        public Address? Address { get; set; }
    }
    
    public class Address
    {
        public string? Street { get; set; }
        public string? City { get; set; }
    }
    
  2. 複合型を受け入れる C# メソッドを定義します。

    public string ProcessPerson(Person person)
    {
        return $"{person.Name} is {person.Age} years old and lives in {person.Address?.City}";
    }
    
  3. JavaScript からメソッドを呼び出し、C# クラス構造に一致する JavaScript オブジェクトを渡します。

    const person = {
        Name: "Alice",
        Age: 30,
        Address: {
            Street: "123 Main St",
            City: "Seattle"
        }
    };
    
    const result = await window.HybridWebView.InvokeDotNet('ProcessPerson', [person]);
    console.log(result); // "Alice is 30 years old and lives in Seattle"
    

JavaScript オブジェクトは、C# に送信されると JSON に自動的にシリアル化され、 HybridWebView によって予期される C# 型に自動的に逆シリアル化されます。 JavaScript オブジェクト構造が C# クラス構造と一致する限り、入れ子になったオブジェクト、配列、およびその他の複雑な構造体がサポートされます。

重要

JavaScript オブジェクトのプロパティ名は、C# クラスのプロパティ名と一致する必要があります。 JSON シリアル化では既定で大文字と小文字が区別されるため、JavaScript の Name は C# の Name と一致する必要があります。 [JsonPropertyName]属性を使用して、必要に応じて別名を指定できます。

初期化とアクセス プラットフォームの Web ビューをカスタマイズする

.NET 10 には、初期化の開始 (WebViewInitializing) と初期化終了 (WebViewInitialized) を直接処理するイベントが含まれています。

HybridWebViewでは、BlazorWebViewなどのアプリ向けの初期化/初期化イベントは公開されませんが、基になるプラットフォーム Web ビューをカスタマイズし、準備ができたらコードを実行できます。

  • Windows (WebView2): プラットフォーム ビューが HybridWebViewされ、 WebView2 が継承され、 RunAfterInitialize(Action) が追加されるため、準備ができたら CoreWebView2 に安全にアクセスできます。
  • Android (android.webkit.WebView): ハンドラーを使用してプラットフォーム WebView にアクセスして構成します。
  • iOS/Mac Catalyst (WKWebView): 作成後にプラットフォーム WKWebView にアクセスして構成します。 一部のオプション (特定の WKWebViewConfiguration 設定など) は作成時に設定する必要があります。.NET MAUI では、これらの設定に適切な既定値が設定されます。

ハンドラーの作成後にプラットフォーム ビューにアクセスする

プラットフォームごとに HandlerChanged (またはカスタム コントロールの OnHandlerChanged をオーバーライド) および分岐を処理します。

using Microsoft.Maui.Platform; // For MauiHybridWebView on Windows

void HybridWebView_HandlerChanged(object? sender, EventArgs e)
{
    if (sender is not HybridWebView hv || hv.Handler?.PlatformView is null)
        return;

#if WINDOWS
    if (hv.Handler.PlatformView is MauiHybridWebView winView)
    {
        winView.RunAfterInitialize(() =>
        {
            // CoreWebView2 is guaranteed to be initialized here
            winView.CoreWebView2.Settings.IsZoomControlEnabled = false;
            winView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false;
        });
    }
#elif ANDROID
    if (hv.Handler.PlatformView is Android.Webkit.WebView androidView)
    {
        // Safe to tweak most settings after creation
        androidView.Settings.BuiltInZoomControls = false;
        androidView.Settings.DisplayZoomControls = false;
    }
#elif IOS || MACCATALYST
    if (hv.Handler.PlatformView is WebKit.WKWebView wk)
    {
        wk.AllowsBackForwardNavigationGestures = true;
        // Many WKWebViewConfiguration options can’t be changed now – see note below
    }
#endif
}

これを 1 回、たとえば XAML 分離コードで結び付けます。

public MainPage()
{
    InitializeComponent();
    hybridWebView.HandlerChanged += HybridWebView_HandlerChanged;
}

重要

iOS/Mac Catalyst では、ビューを作成する前にいくつかの WKWebViewConfiguration オプションを設定する必要があります。 .NET MAUI では、既定で一般的なオプション (インライン メディアの再生、自動再生、JavaScript など) が有効になるため、一般的なシナリオは追加のコードなしで動作します。 異なる作成時オプションが必要な場合は、以下の高度な方法を使用します。

詳細: カスタム ハンドラーを使用して作成時の構成を提供する

作成時のオプションを変更する必要がある場合 (たとえば、iOS/Mac Catalyst の WKWebViewConfiguration を変更するには)、カスタム ハンドラーを登録し、 CreatePlatformViewをオーバーライドします。

using using Microsoft.Maui.Handlers; // For HybridWebViewHandler

// In MauiProgram.cs
builder.ConfigureMauiHandlers(handlers =>
{
    handlers.AddHandler<HybridWebView, MyHybridWebViewHandler>();
});

// Custom handler (iOS/Mac Catalyst shown; similar ideas apply for other platforms)
public class MyHybridWebViewHandler : HybridWebViewHandler
{
#if IOS || MACCATALYST
    protected override WebKit.WKWebView CreatePlatformView()
    {
        var config = new WebKit.WKWebViewConfiguration
        {
            // Example: change defaults established by MAUI
            AllowsInlineMediaPlayback = false,
        };

        // Recreate the platform view with your configuration
        var webview = new MauiHybridWebView(this, CoreGraphics.CGRect.Empty, config);
        return webview;
    }
#endif
}

注意事項

作成時の構成は高度なシナリオです。 各プラットフォームでの動作を検証し、可能な場合は初期化後の調整を優先します。

この HybridWebView では、基になるプラットフォーム Web ビューをカスタマイズし、準備ができたらコードを実行できるように、アプリ向けの初期化/初期化イベントが公開されます。

ほとんどの場合、 WebViewInitialized イベントのイベント ハンドラーを追加するだけです。

<HybridWebView WebViewInitialized="HybridWebViewInitialized" />

分離コードでは、 PlatformArgs プロパティを使用してプラットフォーム機能にアクセスできます。 このプロパティは、基になる Web ビューを制御するために必要なすべての設定と構成にアクセスできる PlatformWebViewInitializedEventArgs のインスタンスを提供します。

private void HybridWebViewInitialized(object sender, WebViewInitializedEventArgs e)
{
    if (e.PlatformArgs is null)
        return;

#if WINDOWS
    e.PlatformArgs.Settings.IsZoomControlEnabled = false;
    e.PlatformArgs.Settings.AreDefaultContextMenusEnabled = false;
#elif ANDROID
    e.PlatformArgs.Settings.BuiltInZoomControls = false;
    e.PlatformArgs.Settings.DisplayZoomControls = false;
#elif IOS || MACCATALYST
    e.PlatformArgs.Configuration.IgnoresViewportScaleLimits = false;
#endif
}

重要

各プラットフォームでは、Web ビューを作成する 前に 一部の構成を設定する必要がある場合があります。

ほとんどの場合、 WebViewInitializing イベントのイベント ハンドラーを追加するだけです。

<HybridWebView WebViewInitializing="HybridWebViewInitializing" />

分離コードでは、 PlatformArgs プロパティを使用してプラットフォーム機能にアクセスできます。 このプロパティは、基になる Web ビューを制御するために必要なすべての設定と構成にアクセスできる PlatformWebViewInitializingEventArgs のインスタンスを提供します。

private void HybridWebViewInitializing(object sender, WebViewInitializingEventArgs e)
{
    if (e.PlatformArgs is null)
        return;

#if IOS || MACCATALYST
    // Example: override defaults established by .NET MAUI
    e.PlatformArgs.Configuration.AllowsInlineMediaPlayback = false;
#endif
}

Web 要求をインターセプトする

HybridWebView は、ホストされた Web コンテンツ内から送信された Web 要求をインターセプトして応答できます。 これにより、ヘッダーの変更、要求のリダイレクト、ローカル応答の提供などのシナリオが可能になります。

Web 要求をインターセプトするには、 WebResourceRequested イベントを処理します。 イベント ハンドラーで、 Handledtrue に設定し、 SetResponse(statusCode, statusDescription, contentType, streamOrTaskOfStream)経由で応答を提供します。

<HybridWebView WebResourceRequested="HybridWebView_WebResourceRequested" />
private void HybridWebView_WebResourceRequested(object sender, WebViewWebResourceRequestedEventArgs e)
{
    // NOTE:
    // - This method MUST be synchronous; it's invoked on the WebView's thread.
    // - You MUST call SetResponse (even a minimal response) if you set Handled = true.

    // Example: serve a local image instead of the network resource
    if (e.Uri.ToString().EndsWith("sample-image.png", StringComparison.OrdinalIgnoreCase))
    {
        e.Handled = true;
        e.SetResponse(200, "OK", "image/png", GetLocalImageStreamAsync());
        return;
    }

    // Example: inject an authorization header for API calls
    if (e.Uri.Host.Equals("api.contoso.com", StringComparison.OrdinalIgnoreCase))
    {
        e.RequestHeaders["Authorization"] = $"Bearer {GetToken()}";
        // Fall through without setting Handled so the request proceeds normally
    }
}

private Task<Stream> GetLocalImageStreamAsync()
{
    // Return a stream containing PNG bytes (for example from a MauiAsset)
    return FileSystem.OpenAppPackageFileAsync("wwwroot/images/sample-image.png");
}

注意事項

WebResourceRequestedで長時間実行する作業は避けてください。 Handled = trueを設定した場合は、すぐに応答を指定する必要があります。 非同期コンテンツの場合は、ストリームの完了中に WebView を続行できるように、 Task<Stream>SetResponse に渡します。

一般的なパターンは次のとおりです。

  • 特定のホストのヘッダーの挿入または書き換え。
  • オフラインまたはテストのシナリオでローカル ファイルまたはメモリ内コンテンツを返す。
  • 適切な Location ヘッダーを含む 3xx 状態コードを返すことによって、別の URI にリダイレクトします。

実装の制限

  • Android
    • Android では、要求に対して "intercept-and-continue" を直接許可しません。 実装では、要求が発生しようとしていることを通知し、要求全体を置き換えるか、何もせずに Web ビューで行うことができます。
    • Android では、カスタム スキームはサポートされていません。
  • iOS/Mac Catalyst
    • iOS および Mac Catalyst では、 http 要求と https 要求のインターセプトは許可されません。
Platform HTTPS をインターセプトする カスタムスキームを傍受する 要求の変更
Android
iOS
Mac Catalyst
ウィンドウズ

::: moniker-end