Skip to content

バックグラウンドで C# の DLL を実行する ​

第1版作成 最終更新 (日本時間)
確認バージョン1.5.1.01.5.8.1

1.5.8.1 のプリザンターでスケジュール実行できるのは、JavaScript のバックグラウンドサーバースクリプトだけです。このページは、C# で書いた DLL や EXE を定期実行する方法を比べた設計メモです。1 つ目の方法は本体の改修が要りませんが、残りは本体の標準機能ではありません。調査は 1.5.1.0 を対象に行いました。

前提にした現行実装(Quartz.NET のジョブ、Context の作り方、安全のための制御)は バックグラウンドサーバースクリプトの仕組み にまとめています。

流用できる既存の仕組み ​

Quartz.NET のジョブ ​

バックグラウンドサーバースクリプトは ClusterExecutionTimerBase([DisallowConcurrentExecution] 付き)を継承した BackgroundServerScriptJob として動き、クラスタリング時は DB のジョブストアで複数台に分散されます。同じ基底クラスを継承したジョブを足せば、C# の処理も同じスケジューラに載せられます。

DLL を読み込む 2 つの仕組み ​

項目ExtendedLibraryPDF プラグイン
置き場所実行ファイルと同じフォルダの ExtendedLibraries/(直下と 1 階層下のサブフォルダ)App_Data/UserPlugins/
読み込み起動時。AddApplicationPart() で Controller を登録(Startup.cs#L201-L207、#L469-L479)初回使用時に Assembly.LoadFrom() し、IPdfPlugin を実装した型を Activator.CreateInstance()(PdfPluginCache.cs#L12-L30)
キャッシュApplicationPart として常駐ConcurrentDictionary(プロセス内)
差し替え再起動が必要キャッシュ済みなら再起動が必要
設定ファイルを置くだけExtensions テーブル(ExtendedPlugin)

ExtendedLibrary の使い方は 拡張ライブラリ を参照してください。

4 つの方法 ​

方法 1:httpClient で別プロセスの Web API を呼ぶ(改修不要) ​

C# の処理を ASP.NET Core の Web API として別プロセスで動かし、バックグラウンドサーバースクリプトから httpClient で呼びます。

図を読み込み中…

js
httpClient.RequestUri = 'http://localhost:5001/api/my-task';
httpClient.Content = JSON.stringify({ tenantId: context.TenantId });
httpClient.MediaType = 'application/json';
httpClient.Post();
if (!httpClient.IsSuccess) {
    logs.LogSystemError('Task failed: ' + httpClient.StatusCode);
}
  • 本体を変えずにすぐ使え、プロセスも完全に分かれます。
  • Web API の配置と、API キーなどの認証は自前で用意します。
  • Script.json の DisableServerScriptHttpClient が false(既定)である必要があります。

方法 2:Quartz のジョブとして組み込む ​

ClusterExecutionTimerBase を継承した BackgroundAssemblyJob を作り、ジョブのデータ(テナント ID・DLL のパス・型名)から Assembly.LoadFrom() で読み込んで、IBackgroundTask を実装した型を実行します。例外は SysLogModel に記録します。

  • プリザンターの Context をそのまま使え、デバッグしやすい方法です。
  • 新しいジョブクラスに加え、テナント設定と画面の改修が要ります。
  • 同じプロセスで動くので、DLL の依存が本体と衝突する可能性があります。

方法 3:サーバースクリプトから Process.Start で EXE を起動する ​

process.start({ fileName, arguments, waitForExit, timeoutMs }) のようなホストオブジェクトを追加して EXE を起動します。子プロセスに分かれますが、任意のコマンド実行につながるため本番では勧めません。

方法 4:PDF プラグインの方式を汎用化する ​

PdfPluginCache と同じ方式で、汎用のタスク用プラグインを読み込みます。

csharp
public interface IBackgroundTaskPlugin
{
    string Name { get; }
    BackgroundTaskResult Execute(BackgroundTaskContext context);
}
// BackgroundTaskContext: TenantId・UserId・Parameters(JSON)
// BackgroundTaskResult: Success・Message
追加するもの変更するもの
Implem.Plugins/IBackgroundTaskPlugin.csTenantSettings に BackgroundTaskPlugins(SettingList)を追加
Libraries/BackgroundServices/BackgroundTaskPluginCache.csTenantUtilities にプラグインの設定画面を追加
Libraries/BackgroundServices/BackgroundTaskPluginJob.csStartup.cs でプラグインのジョブを初期化
ExtendedPlugin.cs の種類に BackgroundTask を追加

結果の型をインターフェースでそろえられ、プラグインは Implem.Plugins だけを参照すればよくなります。

比較 ​

観点1. httpClient2. Quartz のジョブ3. Process.Start4. プラグイン
本体の改修不要中小〜中中〜大
プロセスの分離ありなしありなし
既存の仕組みの流用高高低高
セキュリティリスク低高極めて高高
デバッグのしやすさ中高低高
クラスタリング対応済み対応できるノードに依存対応できる
タイムアウトhttpClient.TimeOutCancellationTokenProcess.WaitForExit(timeout)CancellationToken をプラグインに渡す

短期的には方法 1、組み込むなら方法 4 を勧めます。依存の衝突が心配なら方法 1 が安全です。

実装上の注意 ​

依存の衝突 ​

同じプロセスで読み込む方法 2・4 では、同じライブラリの別バージョンで FileLoadException や誤動作が起きることがあります。プラグインごとに AssemblyLoadContext(isCollectible: true と AssemblyDependencyResolver)で読み込むと軽減でき、本体の DLL は Implem.Plugins だけを参照させます。ネイティブライブラリの衝突はプロセスを分けないと避けられません。

ログ ​

既存のジョブと同じく、開始時に SysLogModel を作り、終わりに Finish し、例外も SysLogModel に記録します。

セキュリティ ​

方法主な脅威対策
1. httpClientSSRF(httpClient に接続先の制限が無い)、認証情報や応答の外部への送信Web API 側のファイアウォール、API キーを環境変数などで管理
2・4. DLL同じプロセス・同じ権限での任意コード実行、プロセスの停止、接続文字列の参照、DLL の差し替え、依存ライブラリの脆弱性配置フォルダの書き込み権限をデプロイ手順だけに限定、DLL の署名の検証、AssemblyLoadContext での分離、パスの検証
3. Process.Startコマンドインジェクション、OS レベルの権限、子プロセスの資源消費実行ファイルの許可リスト、UseShellExecute = false と引数のパラメータ化、cgroup などの資源制限、実行ログ、設定をテナント管理者ではなくサービス管理者に限定

PdfPluginCache は libraryPath を App_Data/UserPlugins と結合するだけで、../ を含むパスを検証していません(PdfPluginCache.cs#L18-L23)。$ps.file がセクション・パスを [0-9a-zA-Z_.\-](パスは / も可)の正規表現で制限しているのとは対照的です(ServerScriptFile.cs#L353-L355)。汎用化するときは、同様の正規表現チェックと、Path.GetFullPath した結果が基準フォルダの下にあることの確認を入れます。

マルチテナント ​

懸念現行の制御追加で要ること
テナント間のデータContext のテナント ID で SQL を絞るDLL が DB に直接つなぐと制御できない
共有資源の取り合い同じジョブの多重実行は防ぐテナントごとの資源の割り当て
エラーの波及ジョブごとに例外を捕捉DLL の読み込み方式ではプロセス全体に影響する
ログSysLogs にテナント ID を記録テナントごとのログの分離

方法 2・4 は、DLL の出どころと中身を信頼できることが前提です。信頼できない DLL を読み込む運用はしないでください。

関連ページ ​

変更履歴

第1版バックグラウンドサーバースクリプトの仕組みと、サーバースクリプトの他言語対応・DLL 実行・cron スケジュールの改修・設計メモを追加