API リクエストにクエリパラメータで独自データを渡す
サーバースクリプトの context.QueryStrings.Data() は API リクエストでも使えます。API の URL にクエリパラメータを付ければ、テーブルに専用のフラグ項目を作らなくても、サーバースクリプトの処理を呼び出し側から切り替えられます。ただし使えるのは、コマンドボタンやスクリプトなどブラウザのセッションで呼び出す場合だけです。リクエストボディで ApiKey を指定して呼び出す場合は使えません。
使える条件
| 呼び出し方 | 認証 | context.QueryStrings |
|---|---|---|
| コマンドボタン・スクリプトなど、プリザンターの画面から呼び出す | ブラウザのセッション(Cookie 認証) | 使える |
| リクエストボディで ApiKey を指定し、認証ユーザを上書きして呼び出す | セッションなし | 使えない(Data() は常に空文字列) |
WARNING
リクエストボディで ApiKey を指定して認証ユーザを上書きした場合、context.QueryStrings.Data() は常に空文字列を返します。コマンドボタンやスクリプトからの呼び出しでは ApiKey を指定せずブラウザのセッションをそのまま使うため、この制約には当たりません。
正確には、分かれ目は ApiKey の有無ではなく、リクエストにログイン済みの Cookie が付いているか(User.Identity.IsAuthenticated)です。確認したソースでも API コントローラは同じ判定をしています(ItemsController.cs、Context.cs)。
仕組み
QueryStrings クラス
context.QueryStrings の実体は QueryStrings クラスで、Dictionary<string, string> を継承したシンプルな実装です。
public class QueryStrings : Dictionary<string, string>
{
public string Data(string key)
{
return this
.Where(o => o.Key.ToLower() == key.ToLower())
.Select(o => o.Value)
.FirstOrDefault()
?? string.Empty;
}
public bool Bool(string key)
{
return Data(key).ToBool();
}
public int Int(string key)
{
return Data(key).ToInt();
}
public long Long(string key)
{
return Data(key).ToLong();
}
}| メソッド | 戻り値 |
|---|---|
Data(key) | 値の文字列。キーの大文字・小文字は区別しない。キーがなければ空文字列 |
Bool(key) | Data(key) を bool に変換した値 |
Int(key) | Data(key) を int に変換した値 |
Long(key) | Data(key) を long に変換した値 |
値が入るタイミング
QueryStrings に値を入れるのは Context クラスの SetData() です。HTTP リクエストのクエリ文字列を & と = で分解し、URL デコードしてから格納します。そのため日本語やスペースを含む値も扱えます。
private void SetData()
{
// ...
var request = AspNetCoreHttpContext.Current.Request;
foreach (var o in request.QueryString.Value?.PadLeft(1, '?').Substring(1).Split('&'))
{
var keyAndValue = o.Split('=');
var key = HttpUtility.UrlDecode(keyAndValue.FirstOrDefault());
var value = HttpUtility.UrlDecode(keyAndValue.Skip(1).FirstOrDefault());
QueryStrings[key] = value;
}
// ...
}サーバースクリプトの context には、この Context.QueryStrings への参照がそのまま渡されます。
public readonly QueryStrings QueryStrings;
public ServerScriptModelContext(Context context, ...)
{
QueryStrings = context.QueryStrings;
// ...
}ServerScriptModelContext.cs#L21
API では認証方式で決まる
API コントローラは、sessionData に User?.Identity?.IsAuthenticated の結果を渡して Context を作ります。SetData() は sessionData が true のときに呼ばれるため、QueryStrings が使えるかどうかは認証方式で決まります。
[HttpPost("{id}/Update")]
public ContentResult Update(long id)
{
var body = default(string);
using (var reader = new StreamReader(Request.Body)) body = reader.ReadToEnd();
var context = new Context(
sessionStatus: User?.Identity?.IsAuthenticated == true,
sessionData: User?.Identity?.IsAuthenticated == true,
apiRequestBody: body,
contentType: Request.ContentType,
api: true);
// ...
}図を読み込み中…
コマンドボタンやスクリプトからの呼び出しではブラウザのセッション Cookie が送られるので IsAuthenticated が true になり、クエリパラメータが QueryStrings に入ります。
使い方
パスの前提
以降の /api/items/... はルート配置の場合のパスです。サブディレクトリ配置の環境ではパスが変わります。スクリプトから URL を組み立てるときは $('#ApplicationPath').val() や context.ApplicationPath を使ってください。
基本
更新 API の URL にクエリパラメータを付け、サーバースクリプトで取得します。
POST /api/items/{レコードID}/update?mode=export// サーバースクリプト(更新後)
var mode = context.QueryStrings.Data('mode');
if (mode === 'export') {
// エクスポート処理
}複数のパラメータを渡す
POST /api/items/{レコードID}/update?action=notify&target=slack&channel=general// サーバースクリプト(更新後)
var action = context.QueryStrings.Data('action');
var target = context.QueryStrings.Data('target');
var channel = context.QueryStrings.Data('channel');
if (action === 'notify' && target === 'slack') {
// Slack通知処理
}コマンドボタンから処理を振り分ける
処理の種類をクエリパラメータで指定し、1 つのサーバースクリプトで複数の処理を振り分ける例です。switch で分岐できるので、サーバースクリプトの数が増えすぎるのを防げます。
// コマンドボタンのスクリプト
$.ajax({
url: $('#ApplicationPath').val() + 'api/items/' + $p.id() + '/update?action=approve',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({ ApiVersion: 1.1 }),
success: function (data) {
// API のレスポンスは { Id, StatusCode, Message } 形式
console.log(data.StatusCode, data.Message);
location.reload();
}
});// サーバースクリプト(更新後)
var action = context.QueryStrings.Data('action');
switch (action) {
case 'approve':
// 承認処理
model.ClassA = '承認済';
break;
case 'reject':
// 却下処理
model.ClassA = '却下';
break;
}API の呼び出しでは AddResponse は画面に反映されない
context.AddResponse() で積んだ指示(ResponseCollection)が画面に反映されるのは、HtmlScripts.OnEditorLoad で HTML ページに埋め込まれる経路だけです。API(api/items/...)のレスポンスは ApiResponse(Id / StatusCode / Message など)しか返さないため、API の呼び出しで AddResponse を使っても画面には反映されません。処理結果を画面に出したいときは、呼び出し元のスクリプト側で表示を制御してください(AddResponse については context.AddResponse の Method と引数)。
型変換メソッドを使う
数値やフラグを渡すときは、型変換メソッドを使うとコードが簡潔になります。
POST /api/items/{レコードID}/update?copies=3&dryRun=true// サーバースクリプト(更新後)
var copies = context.QueryStrings.Int('copies'); // 3
var dryRun = context.QueryStrings.Bool('dryRun'); // true
if (!dryRun) {
for (var i = 0; i < copies; i++) {
// レコードの複製処理
}
}注意点
- キーの大文字・小文字は区別されません。
Data()は内部でToLower()で比較するため、action・Action・ACTIONは同じキーとして扱われます。 - キーだけを指定した場合(
?flag)、値はnullになります。Data()はnullのとき空文字列を返すので、存在チェックにはBool()か空文字列との比較を使います。
// ?flag だけの場合
context.QueryStrings.Data('flag'); // ""(空文字列)
context.QueryStrings.Bool('flag'); // false
// ?flag=true の場合
context.QueryStrings.Data('flag'); // "true"
context.QueryStrings.Bool('flag'); // true