Open5

.NET 9 の HybridWebView を試す

ちゅうこちゅうこ

HybridWebView に自分はかなり期待をしている。
というのも、私は MAUI の View の作成にメインで使われている XAML があまり得意ではなく、その代替策(?)としてある Blazor もあまり得意ではない(VSCode での補完だったりが絶望的なのもあり)。
しかしながら、React などのように JSX を使用して View を作ることはある程度得意であるため、なるべくそういう方法で出来ないかと探していたためである。

ちゅうこちゅうこ

試した結果のリポジトリがこちら。
https://github.com/yamachu/PokedexWithDotnetWebTechnologies/tree/main/src/PokedexDotnet.Experimental.HybridWebView

試してみた感じかなり良かった。

この前身(?)となるプロジェクトの Eilon/MauiHybridWebView では戻り値を受け取れるなどの機能もあり、それに対して公式が追いついていないというか、なんというかという箇所はあるが今でも十分使えると思う。

ちゅうこちゅうこ

現状動かす際、macOS 15 を使用している場合は 1点注意が必要で、以下の Workaround が必要。

#if MACCATALYST
    // https://github.com/dotnet/maui/issues/23390#issuecomment-2202295194
    var handlerType = typeof(Microsoft.Maui.Handlers.HybridWebViewHandler);
    var field = handlerType.GetField("AppOriginUri", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic) ?? throw new Exception("AppOriginUri field not found");
    field.SetValue(null, new Uri("app://localhost/"));
#endif

macOS 15 には Safari 18 がインストールされていて、0.0.0.0 Day の対策されたものであるというのが恐らくの原因だろうけれども、Safari 18 の入った macOS 14 では動くという…

ちゅうこちゅうこ

React アプリケーションと動かす際は、以下のような CustomHooks を用意すると便利だった。
.NET に対して通信を行う箇所を、外部 API を呼び出すみたいな形でラップしてあげることである程度使い勝手は向上した。

import { useCallback } from "react";

type HybridWebViewType = {
  /**
   * Sends a message to .NET using the built in
   * @param {string} message Message to send.
   */
  SendRawMessage: (message: string) => void;
};

declare global {
  interface Window {
    HybridWebView: HybridWebViewType;
  }

  interface WindowEventMap {
    HybridWebViewMessageReceived: CustomEvent<{ message: string }>;
  }
}

export const useHybridWebView = () => {
  const sendInvokeMessageToDotNetAsync = useCallback(
    (methodName: string, paramValues: any[]) => {
      const timestamp = performance.now().toString();
      window.HybridWebView.SendRawMessage(
        JSON.stringify({ timestamp, methodName, paramValues })
      );
      return new Promise((resolve) => {
        const cb: Parameters<
          typeof addEventListener<"HybridWebViewMessageReceived">
        >[1] = (ev) => {
          const parsedMessage = JSON.parse(ev.detail.message);
          if (
            parsedMessage.timestamp === timestamp &&
            parsedMessage.methodName === methodName
          ) {
            window.removeEventListener("HybridWebViewMessageReceived", cb);
            resolve(parsedMessage.value);
          }
        };
        window.addEventListener("HybridWebViewMessageReceived", cb);
      });
    },
    []
  );

  return {
    sendInvokeMessageToDotNetAsync,
  };
};

HybridWebView で現在提供されている SendRawMessage は戻り値を受け取れないのだが、SendRawMessage でメッセージを送る際に messageId の様なものを同時に送って、EventListner で待ち受けて非同期で処理できるように工夫している。