サーバースクリプトの内部構造
サーバースクリプトの保存場所、実行されるまでの流れ、JavaScript から見えるオブジェクトを、1.5.8.1 のソースコードをもとにまとめます。
- スクリプトはサイト設定(Sites テーブルの
SiteSettings列の JSON)のServerScriptsに保存されます。 - 実行時は、拡張サーバースクリプトとサイトのスクリプトをまとめ、同じ条件のスクリプトを 1 本の文字列に結合して V8 エンジン(ClearScript)で 1 回だけ実行します。
- フォルダ(
ReferenceTypeがSitesのサイト)で動くのは「サイト設定の読み込み時」(WhenloadingSiteSettings)だけです。
全体像
図を読み込み中…
保存場所
サイトのサーバースクリプトは ServerScript クラスのリストとして、サイト設定の ServerScripts に入ります。サイト設定の画面にある「すべて無効化」は ServerScriptsAllDisabled です(SiteSettings.cs#L218-L220)。
ServerScript クラスが持つ項目は次のとおりです(ServerScript.cs#L7-L35)。
| 項目 | 内容 |
|---|---|
Id・Title・Name | 識別子・タイトル・名前。Name は //Include: での参照に使う |
Body | スクリプト本文 |
WhenloadingSiteSettings 〜 BeforeOpeningRow | 実行条件(15 種類)。条件と context.Condition の値の対応は context.Condition を参照 |
Shared | 共有スクリプト。ほかのスクリプトの前に連結される(後述) |
Functionalize | 本文を即時実行関数 (()=>{ ... })(); で囲む |
TryCatch | 本文を try { ... } catch (e) { logs.LogException(...) } で囲む |
Disabled | 無効化 |
TimeOut | タイムアウト(ミリ秒)。適用のされ方は タイムアウト を参照 |
Background | バックグラウンド実行の指定 |
バックグラウンドサーバースクリプトは ServerScript を継承した BackgroundServerScript クラスで、実行ユーザー(UserId)とスケジュールを追加で持ちます。保存先はサイト設定ではなくテナント設定(TenantSettings.BackgroundServerScripts)です(BackgroundServerScript.cs)。
スクリプトの集め方(GetServerScripts)
実行するスクリプトの一覧は SiteSettings.GetServerScripts が作ります(SiteSettings.cs#L6204-L6269)。
- 拡張サーバースクリプト(
Parameters.ExtendedServerScripts)のうち、ユーザー・サイト・コントローラ・アクションなどの条件に合うものをServerScriptに変換して先に並べる(絞り込みの詳細は 拡張サーバースクリプトの適用条件)。 - その後ろに、サイトのスクリプトのうち
Disabledでないものを並べる。 - 実行条件が 1 つでも付いているスクリプトについて、本文が
//debug//で始まるかを調べてデバッグ指定にする(ServerScript.cs#L181-L184)。 - 各スクリプトの本文にある
//Include:名前の行を、その名前のスクリプトの本文に置き換える。
//Include: による取り込み
行頭が //Include: の行は、コロンの後ろに書いた名前(前後の空白は除く)と Name が一致するスクリプトの本文に置き換わります。一致するものが複数あれば改行でつないで全部入ります。取り込んだ本文の中の //Include: も再帰的に展開され、その深さの上限は Script.json の ServerScriptIncludeDepthLimit(既定 10)です(SiteSettings.cs#L6271-L6311)。
//Include:common-functions
// ↑ この行が Name = "common-functions" のスクリプトの本文に置き換わる
model.ClassA = formatCode(model.ClassA);一覧には拡張サーバースクリプトも含まれるので、拡張サーバースクリプトの Name を指定して取り込むこともできます。
一覧はリクエスト内でキャッシュされる
作った一覧は SiteSettings の ServerScriptsAndExtended([NonSerialized])に保持され、2 回目以降はそのまま返されます(SiteSettings.cs#L158-L159)。キャッシュは SiteSettings のインスタンス単位なので、リクエストごとにサイト設定を読み直せば作り直されます。同じリクエストの中では、最初に作ったときのコントローラ・アクションで絞り込んだ結果が使われます。
実行の流れ
各タイミングの呼び出し元(SetByBeforeCreateServerScript など)は、条件を指定して ServerScriptUtilities.Execute を呼びます(ServerScriptUtilities.cs#L1324-L1372)。
図を読み込み中…
実行しない条件
次のいずれかに当てはまると、スクリプトは実行されません(ServerScriptUtilities.cs#L1178-L1192)。
- サイト設定で「すべて無効化」(
ServerScriptsAllDisabled)にしている Script.jsonのServerScriptがfalse、契約設定でサーバースクリプトが無効、またはリクエストでサーバースクリプトが無効化されている- サーバースクリプトの入れ子(
items.Updateなどから別のサーバースクリプトが動く)の深さが 10 以上 - 条件に合う(共有でない)スクリプトが 1 つもない
結合のしかた
実行するコードは次の順に改行でつないだ 1 本の文字列です(ServerScriptUtilities.cs#L1254-L1266)。
- 共有スクリプト(
Sharedのもの)の本文を全部 - 条件に合うスクリプトの本文を、一覧の順(拡張サーバースクリプト → サイトのスクリプト)に
Functionalize と TryCatch の囲みは 2. の各スクリプトに個別に付きます(ServerScriptUtilities.cs#L1285-L1303)。共有スクリプトには付きません。TryCatch のログには、スクリプトの Id・Title・Name を _ でつないだ文字列とスタックトレースが出ます。
同じ条件のスクリプトは同じスコープで動く
結合して 1 回で実行するため、Functionalize を付けていないスクリプトの let・const はほかのスクリプトと同じトップレベルに宣言されます。別々のスクリプトで同じ名前を let で宣言すると、後のスクリプトで構文エラーになり、その条件のスクリプトが全部動きません。スクリプトごとに Functionalize を付けるか、名前が重ならないようにしてください。共有スクリプトは関数化されないので、共有したい関数や変数はそのまま見えます。
デバッグ
条件に合うスクリプトのどれか 1 つでも本文が //debug// で始まっていれば、そのときの実行全体がデバッグモードになります。デバッグモードではエンジンが EnableDebugging と EnableRemoteDebugging を付けて作られ、実行開始時にデバッガーの接続を待って一時停止します(ScriptEngine.cs#L20-L54)。デバッグモードの間はタイムアウトが効きません(ServerScriptModel.cs#L185-L190)。
本番環境で //debug// を残さない
デバッガーが接続するまで処理が止まり、タイムアウトもかかりません。検証が終わったら先頭の //debug// を消してください。
JavaScript から見えるオブジェクト
エンジンに登録されるものは次のとおりです(ServerScriptUtilities.cs#L1221-L1253)。これ以外の .NET の型や OS の機能には触れません(理由は C# スクリプト・Python は使えるか)。
| 名前 | C# のクラス | 用途 |
|---|---|---|
JsonConvert | Newtonsoft.Json.JsonConvert(型) | .NET 側の JSON 変換 |
context | ServerScriptModelContext | ユーザー・サイト・リクエストの情報、Log・Error・AddResponse など |
grid | ServerScriptModelGrid | 一覧の件数など |
model | ExpandoObject | 対象レコードの値(読み書き) |
saved | ExpandoObject | 保存済みの値 |
depts・groups・users | ServerScriptModelDepts など | 組織・グループ・ユーザーの取得 |
columns | ExpandoObject | 項目ごとの表示・入力の制御(columns で変更できるプロパティ) |
siteSettings | ServerScriptModelSiteSettings | 既定のビュー・セクションなど |
view | ServerScriptModelView | フィルター・ソート(view.OnSelectingWhere とフィルター) |
items | ServerScriptModelApiItems | レコード・サイトの取得・作成・更新・削除、集計 |
hidden・responses・elements | ServerScriptModelHidden など | 隠し項目、レスポンス、画面要素の表示制御 |
extendedSql | ServerScriptModelExtendedSql | 拡張 SQL の実行 |
notifications | ServerScriptModelNotification | 通知 |
httpClient | ServerScriptModelHttpClient | HTTP 通信。Script.json の DisableServerScriptHttpClient が true なら登録されない |
utilities・logs | ServerScriptModelUtilities・ServerScriptModelLogs | ユーティリティ、ログ出力 |
_file_cs($ps.file) | ServerScriptFile | ファイル操作。DisableServerScriptFile が false のときだけ登録される(既定は true で使えない) |
_csv_cs($ps.CSV) | ServerScriptCsv | CSV の変換 |
$ps はエンジンの初期化時に JavaScript で定義されるオブジェクトで、$ps.file・$ps.CSV・$ps.JSON などは _file_cs・_csv_cs・JsonConvert を包んだ JavaScript の関数です(ServerScriptJsLibraries.cs)。$ps.file の制限は $ps.file と添付ファイル を参照してください。
model に入る値
model と saved には、どのテーブルでも次の値が入ります(ServerScriptUtilities.cs#L191-L430)。
ReadOnly・SiteId・Title・Body・Ver・Creator・Updator・CreatedTime・UpdatedTime・Comments- 分類・数値・日付・説明・チェック・添付ファイルの各項目(
ClassAなど)
期限付きテーブルでは IssueId・StartTime・CompletionTime・WorkValue・ProgressRate・RemainingWorkValue・Status・Manager・Owner・Locked、記録テーブルでは ResultId・Status・Manager・Owner・Locked が加わります。
変更の反映
model の値を書き換えると、その項目名が記録されます。実行後、記録された項目のうちそのユーザーが編集できる項目だけがレコードに反映されます(ServerScriptUtilities.cs#L1075-L1091、L680-L699)。
model には項目のほかに ExtendedRowCss と ExtendedRowData を書けます。行表示の前(BeforeOpeningRow)に設定すると、一覧の行(tr)の CSS クラスと data-extension 属性になります(ServerScriptUtilities.cs#L724-L737、HtmlGrids.cs#L369-L379)。
// 行表示の前: 日付A が過ぎている行に CSS クラスを付ける
if (model.DateA < new Date()) {
model.ExtendedRowCss = 'row-overdue';
}呼び出し元とコード自動生成
期限付きテーブル・記録テーブルの作成・更新・削除などでサーバースクリプトを呼ぶコードは、CodeDefiner のテンプレート(App_Data/Definitions/Definition_Code/Model_OnCreating_ServerScript_Body.txt など)から IssueModel.cs・ResultModel.cs に生成されています(Definition_Code)。たとえば作成前のテンプレートは、SetByBeforeCreateServerScript を呼び、context.ErrorData にエラーが入っていればそこで作成を止めます。サーバースクリプトの context.Error は context.ErrorData にエラーを設定するメソッドなので(ServerScriptModelContext.cs#L152-L156)、作成前に呼ぶと作成を中止できます。CodeDefiner については CodeDefiner を参照してください。
フォルダ(Sites)で動く条件
ReferenceType が Sites のサイト(フォルダ)では、サーバースクリプトはサイト設定の読み込み時(WhenloadingSiteSettings)だけ動きます。作成・更新・削除・画面表示の前などの条件は動きません。理由は 3 つあります。
| 箇所 | 1.5.8.1 の実装 |
|---|---|
| 管理画面 | サーバースクリプトのダイアログで、Sites のときは実行条件のチェックボックス群に hidden が付き、条件を選べない(SiteUtilities.cs#L17937-L17947)。スタイル・スクリプト・HTML のダイアログも同じ |
| API | サイト設定の API でサーバースクリプトを登録できるのは ServerScriptRefTypes(Results・Issues)だけ(ApiSiteSetting.cs#L13-L17、SiteUtilities.cs#L2442) |
| モデル | SiteModel の作成・更新・削除は SetByBeforeCreateServerScript などを呼ばない(SiteModel.cs に呼び出しがない) |
WhenloadingSiteSettings は、サイト設定を読み込むとき(ItemModel.SetSite)にレコードなしで実行されます。そのため model にレコードの値は入りません。また、対象がそのサイト自身(context.Id がサイト ID と同じ)で、アクションが次のいずれかのときは実行されません(_BaseModel.cs#L758-L787)。
createbytemplate・edit・update・copy・delete・updatesitesettings・updatesmartdesign
つまり、サイト設定の編集画面を開いたり保存したりするときには動かず、一覧やレコードの画面を開くときなどに動きます。
サイトの操作は items から
フォルダ自体にスクリプトを付けられなくても、テーブルのサーバースクリプトから items でサイトを扱えます。items.GetSite(id)・GetSiteByTitle・GetSiteByName・GetSiteByGroupName・GetClosestSite で取得し、items.NewSite(referenceType) で新しいサイトのモデルを作れます(ServerScriptModelApiItems.cs#L33-L114)。取得したサイトのモデルでは SiteId・SiteName・SiteGroupName・ReferenceType・ParentId・InheritPermission・Publish・DisableCrossSearch・各ビューのガイド(GridGuide など)・SiteSettings などを読め、SiteId 以外は書き込めます(ServerScriptModelApiModel.cs#L107-L178)。
// サイト名でサイトを探し、サイト名を書き換える
const sites = items.GetSiteByName('master-table');
if (sites.length > 0) {
context.Log('ParentId: ' + sites[0].ParentId);
sites[0].SiteName = 'master-table-old';
sites[0].Update();
}フォルダでも作成・更新・削除のサーバースクリプトを動かせるようにする改修案は、フォルダでサーバースクリプトを動かす にまとめています。
計算式のスクリプトとの違い
拡張計算式も同じ ScriptEngine(V8)で評価しますが、別の実装(FormulaServerScriptUtilities)です。登録されるのは model・context と FormulaServerScriptUtilities 型だけで、items や httpClient などは使えません。詳しくは 拡張計算式の実装ロジック を参照してください。