WebView2で社内システムをデスクトップアプリ化する手順と注意点

技術・開発

はじめに

IEモード依存の社内システムを何とかしたい。けれど、React や Vue で全面的に作り直す予算も期間もない。

この板挟みで止まっている現場は多い。そこで出口として挙がるのがWebView2だ。Microsoft Edgeの描画エンジンをWindowsアプリの中に埋め込む部品で、画面はWeb技術のまま、ファイル操作や機器制御はネイティブ側に任せられる。

ただし、ここで最も多い誤解を先に潰しておきたい。WebView2に載せ替えても、ActiveXは動かない。 WebView2の中身はChromiumで、IEの描画エンジン(Trident)は入っていないからだ。「ガワをWebView2にすれば延命できる」という発想は、残念ながら成り立たない。

それでもWebView2を選ぶ価値はある。この記事では次を扱う。

  1. WebView2とElectronの使い分け、向くケースと向かないケース
  2. 最小構成で社内システムを表示するまでのコード
  3. ActiveXが担っていた「ローカル操作」をネイティブ側へ逃がす方法
  4. 配布方式の選択と、本番で必ず踏む落とし穴

なお、そもそもActiveXの現状を整理したい場合は「ActiveXはいつまで使える?」の記事を先に読んでほしい。


スポンサーリンク
スポンサーリンク

WebView2の基礎を3分で押さえる

WebView2は2つの部品でできている。

  • SDK:アプリに組み込むAPI。NuGetパッケージ Microsoft.Web.WebView2 を参照する
  • ランタイム:クライアント側で動くEdgeベースの実行環境

Visual C++ ランタイムや .NET ランタイムと同じ構図だ。アプリはSDKを参照してビルドし、実行時はクライアント上のランタイムを使って動く。

対応範囲は広い。.NET Framework 4.6.2以降、.NET Core 3.1以降のWinFormsとWPF、WinUI、Win32 C++から利用できる。

配布面の前提も整った。Windows 11にはランタイムがプリインストール済みで、Evergreen配布モードと同じく自動更新で管理される。Windows 10側も、22H2に限れば少なくとも2028年10月までEdgeとWebView2ランタイムの更新が継続する。ESU(拡張セキュリティ更新プログラム)を購入していない端末でも更新は届く。

Electronとの使い分け

観点 WebView2 Electron
配布サイズ 数MB(ランタイムは共有) 100MB超(Chromium同梱)
エンジンの更新 ランタイム側で自動更新 アプリを再ビルドして再配布
既存アプリへの部分導入 できる(1画面だけ置換も可) 難しい(フレームワークごと移行)
対応OS Windowsのみ Windows / macOS / Linux
ホスト側の言語 C#、C++ など Node.js(JavaScript)

判断はシンプルだ。Windows専用の社内システムで、既存の.NETアプリ資産があるならWebView2。 マルチOS対応が要件に入るか、開発チームがJavaScript一本ならElectronになる。

向くケース / 向かないケース

向くのは次のような場面だ。

  • 社内Webシステムを専用アプリの窓に閉じ込め、URL直打ちや外部サイト遷移を禁止したい
  • 既存のWinForms業務アプリの一部画面だけ、Web技術で作り直したい
  • 帳票印刷やローカルファイル書き出しなど、ブラウザだけでは足りない処理が残っている

逆に向かないのは、次のような場合になる。

  • 対象システムがIE専用のJavaScriptで書かれていて、改修予算がゼロ
  • iPadやMacからの利用要件がある
  • 単に社内Webを開くだけで、ネイティブ連携が一切いらない(それならEdgeのアプリモードで足りる)

手順1:環境を用意し、ランタイムの有無を確認する

必要なものはVisual Studio 2022以降と、NuGetパッケージ Microsoft.Web.WebView2 の2つだけだ。

配布前に、対象端末にランタイムが入っているかを確認しておこう。レジストリを見れば分かる。

# WebView2ランタイムのインストール状態とバージョンを確認する
$paths = @(
  'HKLM:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}',
  'HKLM:\SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}',
  'HKCU:\SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}'
)

$found = $paths | ForEach-Object {
    Get-ItemProperty -Path $_ -Name pv -ErrorAction SilentlyContinue
} | Where-Object { $_.pv -and $_.pv -ne '0.0.0.0' }

if ($found) {
    "インストール済み: バージョン $($found[0].pv)"
} else {
    "未インストール(Evergreen Bootstrapper の同梱が必要)"
}

このスクリプトをログオンスクリプトに仕込んで結果を集計すれば、配布計画がそのまま立つ。Windows 11主体の環境なら、ほぼ全台が「インストール済み」で返るはずだ。


手順2:最小構成で社内システムを表示する

WinFormsの場合、フォームにWebView2コントロールを置いて数行書くだけで動く。

using Microsoft.Web.WebView2.Core;

public partial class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();
        InitializeAsync();
    }

    private async void InitializeAsync()
    {
        // ユーザーデータフォルダーは必ず書き込み可能な場所を指定する
        var userDataFolder = Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
            "MyLegacyApp", "WebView2");

        var env = await CoreWebView2Environment.CreateAsync(null, userDataFolder);
        await webView.EnsureCoreWebView2Async(env);

        webView.Source = new Uri("https://kintai.example.local/");
    }
}

WPFなら、XAMLに1行置いてから同じ初期化を書く。

<wv2:WebView2 x:Name="webView"
              xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf" />

ここでの要点はユーザーデータフォルダーの指定だ。既定ではアプリの実行ファイルと同じ場所に作ろうとするため、C:\Program Files 配下にインストールした場合は書き込み権限がなく起動に失敗する。本番でつまずく原因の第1位がこれになる。 LocalApplicationData 配下を明示しておこう。


手順3:ネイティブ機能との橋渡しを設計する

ActiveXが担っていたローカル操作を、どこへ逃がすか。WebView2には3つの経路がある。

Web → ネイティブ(postMessage)

Web側からメッセージを投げ、C#側で受ける。最も基本的な方式だ。

// Web側(社内システムのJavaScript)
window.chrome.webview.postMessage({
  action: "printReport",
  reportId: 12345
});
// ネイティブ側(C#)
webView.CoreWebView2.WebMessageReceived += (s, e) =>
{
    var json = e.WebMessageAsJson;
    var msg = JsonSerializer.Deserialize<WebMessage>(json);

    if (msg.Action == "printReport")
    {
        PrintService.Print(msg.ReportId);   // ネイティブの印刷処理を呼ぶ
    }
};

ネイティブ → Web(ExecuteScriptAsync)

処理結果をWeb側へ返す場合はこちらを使う。

await webView.CoreWebView2.ExecuteScriptAsync(
    $"window.onPrintFinished({JsonSerializer.Serialize(result)});");

C#オブジェクトを直接公開(AddHostObjectToScript)

ActiveXの CreateObject に最も近い感覚で使えるのがこれだ。C#のクラスをJavaScriptから呼べる。

[ClassInterface(ClassInterfaceType.AutoDual)]
[ComVisible(true)]
public class DeviceBridge
{
    public string ReadCardId() => CardReader.Read();   // 既存の機器制御ロジック
}

// 登録
webView.CoreWebView2.AddHostObjectToScript("device", new DeviceBridge());
// Web側から呼ぶ
const id = await window.chrome.webview.hostObjects.device.ReadCardId();

移植の考え方はこうなる。ActiveXコントロールが持っていた処理をC#側のクラスへ移し、Web側はその窓口を叩くだけにする。 画面のHTMLとJavaScriptは、極力そのまま活かせる形に寄せていく。

ただし公開する範囲は絞ってほしい。ホストオブジェクトはサンドボックスの外側にあり、任意のファイル操作を公開すれば、ActiveXと同じ危険性を自作したことになる。「カードを読む」「指定の帳票を印刷する」といった業務単位のメソッドだけを出すのが原則だ。


手順4:表示できた後に必ずやる設定

動いたところで安心しがちだが、業務利用にはあと4つ要る。

想定外のURLへ遷移させない

webView.CoreWebView2.NavigationStarting += (s, e) =>
{
    var allowed = new[] { "kintai.example.local", "keihi.example.local" };
    var host = new Uri(e.Uri).Host;

    if (!allowed.Contains(host))
    {
        e.Cancel = true;   // 社外サイトへの遷移をブロック
    }
};

社内システム専用アプリなら、ホワイトリスト方式が基本になる。フィッシング経由の事故を構造的に防げる。

新しいウィンドウの扱いを決める

既存システムが window.open を多用している場合、そのままだと別ウィンドウが乱立する。NewWindowRequested イベントで、同一WebView内に開くか、専用の子ウィンドウを出すかを制御しよう。

開発者ツールと右クリックを閉じる

webView.CoreWebView2.Settings.AreDevToolsEnabled = false;
webView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false;

一般ユーザー向けの配布では切っておくのが無難だ。ただし障害調査で必要になるため、設定ファイルで切り替えられるようにしておくと運用が楽になる。

統合Windows認証を通す

社内システムがWindows認証を使っている場合、BasicAuthenticationRequested の処理や、Edgeの認証ポリシー(AuthServerAllowlist)の設定が必要になることがある。ここは事前検証の対象に必ず入れてほしい。


手順5:ランタイムの配布方式を選ぶ

方式は2つ。基本はEvergreen(自動更新される共有ランタイム)を選ぶ。

項目 Evergreen 固定バージョン
更新 自動。セキュリティ修正がすぐ届く 手動。アプリごと更新して再配布
配布サイズ 小さい(Bootstrapperのみ同梱) 大きい(ランタイム一式を同梱)
互換性リスク Chromium更新で挙動が変わる可能性あり 検証したバージョンで固定できる
運用負荷 低い 高い(サポート期間は最新版-2程度)
向く用途 一般的な社内アプリ 医療機器・生産設備など検証が厳格な系

固定バージョンは一見安全に見えるが、サポート範囲が狭いため数か月おきに更新版を作り続ける必要がある。「固定すれば保守が要らない」ではなく「固定した瞬間から保守義務が発生する」と理解しておこう。


手順6:IEモードからの移植で必ず詰まる箇所

ここが本記事で最も伝えたいパートだ。WebView2への載せ替えは、実質的に「Chromium対応への改修」を意味する。IEモードで動いていたページをそのまま貼っても、多くはエラーで止まる。

代表的な非互換を挙げる。

IE時代の記述 WebView2での扱い 対応
<object classid="clsid:..."> 動作しない ホストオブジェクト経由でネイティブへ移す
new ActiveXObject(...) 未定義エラー 同上
document.all(...) 一部は動くが非推奨 querySelector へ書き換え
attachEvent / detachEvent 未定義エラー addEventListener へ書き換え
<!--[if IE]> 条件付きコメント 単なるコメント扱い 分岐そのものを削除
<script language="VBScript"> 実行されない JavaScriptへ書き換え
独自のActiveX印刷部品 動作しない window.print() またはネイティブ印刷へ

作業の進め方としては、先にEdgeの通常モードで社内システムを開いてみるのが早い。そこで出るコンソールエラーが、そのまま改修リストになる。WebView2の実装に入る前に、この棚卸しを済ませておきたい。

「WebView2化」の工数の大半は、実はC#側ではなくJavaScript側の書き換えだ。見積もりを立てる際は、ここを厚めに取っておこう。


まとめ

  • WebView2はEdgeの描画エンジンをWindowsアプリに埋め込む部品で、SDKとランタイムの2構成になる
  • Windows 11はプリインストール済み。Windows 10 22H2も少なくとも2028年10月まで更新が続く
  • Windows専用かつ既存の.NET資産があるならWebView2、マルチOSならElectronという住み分けになる
  • ActiveXが担っていた処理は、ホストオブジェクトやpostMessage経由でC#側へ移す。公開範囲は業務単位に絞る
  • ユーザーデータフォルダーの指定漏れが本番障害の定番。LocalApplicationData 配下を明示する
  • 配布はEvergreenが基本。固定バージョンは検証が厳格な系だけに使う
  • 最大の作業量はIE専用JavaScriptの書き換えにある。「載せ替えれば動く」ではない

WebView2は、レガシー資産を丸ごと救う魔法ではない。けれど、画面資産を活かしながら段階的に移行できる点で、全面作り直しより現実的な選択肢になる。1画面ずつ移せるという性質が、予算と期間の制約が厳しい現場では効いてくる。

今日できる最初の一歩

移行対象の社内システムを、EdgeのIEモードではなく通常モードで開いてみてほしい。 F12を押してコンソールを見る。そこに並ぶエラーの数が、WebView2化に必要な改修規模の一次見積もりになる。

エラーがゼロなら、あなたのシステムはすでに移行準備が整っている。数十行なら、1画面ずつ着手すればいい。

コメント