閉域網×IEしか使えない電子カルテ環境でPleasanterを動かす：WebView2内蔵クライアントの構築

# はじめに
はじめての技術ブログなので至らぬ点があればご容赦頂ければ幸いです。
私は院内SEとして医療機関内で使用するアプリケーション、ツールの開発を担当しています。
同じような医療系SE、院内のシステム担当者に役に立てばと思いこの記事を記します。


### 【当院の電カル環境について】
いわゆるスタンドアロン構成で、インターネットにはつながらない閉鎖網です。
2025年現在であれば、医療機関の多くがゼロトラストよりもこちらを採用しているように思えます。
また、ブラウザはIEのみがインストールされており、Pleasanterの動作環境であるEdgeやChromeはありません。

### 【経緯】
院内部署の業務改善のため、当院の電子カルテ環境にてPleasanterを導入したいとの依頼を受けました。
WindowsServer2019で新たにサーバーを構築し、Pleasanterをインストール、セットアップを終え、一息ついたタイミングでクライアント側にはIEしかないことを思い出しました。
依頼を受けた際に、得意げに「あ～わかんないけどできますよ！」といった手前引くに引けないし、、、

# 構成

### WindowsServer2019
| 項目 | 内容 |
|:-:|:-:|
| vCPU  |1   |
|  コア数 |2Core   |
| メモリ  |4GiB   |
|  ストレージ | 256GiB  |
|  インストール構成 | IIS+Pleasanter+Pleasanter動作環境  |

・閉域網内（電子カルテネットワーク）に配置
・外部ネットワークとは分離されたオンプレ構成


### クライアントアプリ
| 項目 | 内容 |
|:-:|:-:|
| 開発言語  | 	C#（.NET Framework4.7.2 / WinForms）  |
| 開発環境  | VisualStudio  |
|  Web表示エンジン | Microsoft.Web.WebView2  |
| 動作要件  | WebView2によりPleasanterを組み込み表示  |

・WebView2によるアプリ内ブラウザでPleasanterサーバに接続
・IE非対応対策として、モダンなEdgeランタイムに基づくブラウザエンジンを採用
***
クライアントにEdgeが入っていない以上、当然WebView2もインストールされていません。
対象となる端末すべてにランタイムをインストールするのもめんどくさいので、Edgeのランタイム（WebView2）をバンドルしたクライアントアプリを作成しました。


### 1.Pleasanterのセットアップ
公式HPを頼りにして、WindowsServer2019にPleasanterをインストールします。

https://pleasanter.org/ja/manual/getting-started-pleasanter-windows-server2019

今回はADサーバから職員のユーザー情報を同期させるようにしました。

https://pleasanter.org/ja/manual/authentication-json

### 2.WebView2のダウンロード
下記から「修正バージョン」をダウンロード。
バージョンとアーキテクチャを選択して、ランタイムを落とします。

https://developer.microsoft.com/ja-jp/microsoft-edge/webview2?form=MA13LH#download

### 3.C#+WinFormでクライアントアプリの作成
PrismWPFでもMAUIでもいいのですが、とりあえずWinFormで作成してみました。
VisualStudioで下記ライブラリを追加して

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4049441/747e4fd7-a3a9-4ded-9cc3-46c6ac455625.png)

公式ドキュメント＆ChatGPTを頼りにコーディング。

```c#
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.WinForms;
using System;
using System.IO;
using System.Windows.Forms;

namespace PleasanterLauncher
{
    public partial class Form1 : Form
    {
        private WebView2 webView;

        public Form1()
        {
            InitializeComponent();
            InitializeWebView2();
        }

        private async void InitializeWebView2()
        {
            //WebView2コントロールのインスタンスを作成
            webView = new WebView2
            {
               Dock = DockStyle.Fill
            };            
            
            Controls.Add(webView);
            
            //オプションの設定。今回はお試しなので何も定義してません。
            var options = new CoreWebView2EnvironmentOptions();

            // WebView2の固定ランタイムを使うために明示的に設定。                      
            string runtimeFolder = Path.Combine(Application.StartupPath,@"Microsoft.WebView2.FixedVersionRuntime.134.0.3124.85.x64");
            
            //固定ランタイムを指定してWebView2環境を作成。           
            var webView2Environment = await CoreWebView2Environment.CreateAsync
            (
                runtimeFolder, 
                null, 
                options
            );

            //WebView2環境を初期化
            await webView.EnsureCoreWebView2Async(webView2Environment);

             //URLを指定してプリザンターを表示
             webView.CoreWebView2.Navigate("http://172.29.14.53/");

        }     
    }
}

```

<details><summary>細かい説明</summary>


```c#
 webView = new WebView2
            {
               Dock = DockStyle.Fill // フォーム全体にWebView2を表示
            };     
            Controls.Add(webView);
```
WebViewを画面全体に表示する処理です。あまり深いことは考えずに記述するだけでOK

***

```c#
 
var options = new CoreWebView2EnvironmentOptions();
```
オプションの設定。今回はお試しなので何も定義してません。
***


```c#         
string runtimeFolder = Path.Combine(Application.StartupPath,@"Microsoft.WebView2.FixedVersionRuntime.134.0.3124.85.x64");       
```
ランタイムフォルダを配置するディレクトリを、実行ファイル(.exe)からの相対パスでここに記述します。
@"←この後に相対パス+ランタイムフォルダの名前を指定します。
今回は実行ファイルと同じディレクトリに入れる予定なので、ランタイムフォルダの名前のみを定義してます。 
***
```c#
 
var webView2Environment = await CoreWebView2Environment.CreateAsync
            (
                runtimeFolder, 
                null, 
                options
            );
```
第一引数：参照するランタイムのフォルダパスを指定します。（ランタイムがインストールされた環境ならnullでOK)
今回は事前に定義したruntimeFolderを使用。

第二引数：キャッシュやCookieなどのユーザーデータの保存先です。
当院では同一端末を複数の職員が使用することもあり、ログイン情報がのこるとまずいのでnullを指定しました。
キャッシュの保存が必要であればruntimeFolderを指定しましょう。

第三引数：オプションのオブジェクトです。とりあえず入れておきましょう。

***
```c#
await webView.EnsureCoreWebView2Async(webView2Environment);
```
環境をリフレッシュします。とりあえず入れておきましょう。

***
```c#
webView.CoreWebView2.Navigate("http://172.29.14.53/");
```
""の間にプリザンターのサーバアドレスを指定します。
必要に応じてjsonファイルにするなり構成ファイルから呼び出すなりするといいかもしれません。
</details>


### 4.ランタイムを指定したディレクトリへ
2でダウンロードしたランタイムを3で指定したパスに配置します。
今回は実行ファイルと同じディレクトリを指定しているので、そこへ展開しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4049441/87bdc30f-13f6-4f9e-a1bd-18f27776d2c2.png)

### 5.動作確認
実際に動かしてみました。ちゃんと動いていますね。
職員情報の取得も問題なく行えているようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4049441/4039afe2-c915-43ab-ac22-4bd1222404da.png)

※ちなみに、「ランタイムが見つからない」といった旨のエラーが表示された場合は、CoreWebView2Environment.CreateAsyncの第一引数で正しくランタイムのパスが指定されているか確認しましょう。

ここまでくれば後は本番環境で動作確認をして、リリースするのみです。



# 今後の改修予定
現状クライアントアプリはPleasanterを表示する機能のみのため、最低限の機能を追加しようと考えています。

・「アプリ作成ガイド」を開くと、「Alt+←」のショートカットキー以外では戻れなくなってしまいます。ページを戻る／進む　ボタンの実装が必要ですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4049441/18ab0654-abcf-466b-a86d-d13c24fcddda.png)

・電子カルテとの連携
　患者情報などを扱う目的で導入したため、電子カルテとの連携が必要です。スクリプトの作成が必須となりますね。



# 終わりに
ひとまずEdge非対応環境でPleasanterを動かすことができました。
まだまだ改良できるポイントはあるので、運用しながらより改善を目指していきます。