Skip to content

MS_JSONRPCService

nishi_74322014 edited this page Aug 19, 2026 · 1 revision

JSONを受信するJSON-RPCサービスを作成する方法

概要

WCFASP.NET Web API で、指定の JSON を受ける(受信する)方法を説明する。

補足(ページ名の「JSON-RPC」は一般名詞としての用法): 標準仕様の
JSON-RPC 2.0{"jsonrpc":"2.0","method":"...","params":...} という
形式を定めたもの)とは異なり、本ページが扱っているのは
**「JSON を POST で受け取るサービス」**という広い意味である。

【JSON-RPC 2.0(仕様)】
  POST /endpoint
  {"jsonrpc":"2.0","method":"getUser","params":{"id":1},"id":1}
     ↑ 単一のエンドポイントに「メソッド名」を渡す

【本ページの例】
  POST /JSONService.svc/PostAnswersKeyValue?userName=...
  [{"key":"k1","value":"v1"}, ...]
     ↑ URL がメソッドを表し、Body に JSON を載せる(REST 寄り)

対になるJSONを送信するRESTサービスを作成する方法
「返す側」、本ページが**「受ける側」**という対応になっている。

JSONを受信するサービスを作成する

WCF の場合

以下のように WCF を定義する。

WebAPIと同様、WCF でもイイ感じに Binding してくれる。

  • KeyValRequest が POST された JSON で、
  • ソレ以外のパラメタは QueryString
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class JSONService : IJSONService
{
  public StringResponse PostAnswersKeyValue(
    KeyValRequest[] akv,
    string userName, string enterpriseID, string storeID, string deviceID,
    string screenID, string initializeScreenInfoID, string time)
  {
    ・・・

    // 基本的に正常系の戻り値を返す。
    return new StringResponse()
    {
      IsError = isError,
      Message = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff")
    };
  }
}

StringResponse で JSON も返せる。

補足(この引数の割り当てが「イイ感じ」の中身): WCF が
どの引数をどこから取るかを自動で決めている点が要点である。

POST /JSONService.svc/PostAnswersKeyValue?userName=u1&storeID=s1&...
Content-Type: application/json

[{"key":"k1","value":"v1"},{"key":"k2","value":"v2"}]
  ↑ この配列が akv にバインドされる

・複合型(配列・クラス)が 1 つ  → Body から
・単純型(string, int 等)       → UriTemplate / QueryString から

規則は単純で、

引数の型 どこから取るか
複合型(クラス、配列) Body(1 つだけ許される)
単純型(string、int、DateTime 等) URL(UriTemplate かクエリ文字列)

複合型を 2 つ以上引数に取れないという制約があるため、
「JSON を 2 種類受け取りたい」場合は
1 つのラッパー クラスにまとめる必要がある。

なお、AspNetCompatibilityRequirementsMode.Allowed
指定されている理由は
JSONを送信するRESTサービスを作成する方法
「注意点」で述べたとおりである
Allowed は互換モードの有無にかかわらず動く)。

補足(受信側では「常に正常系を返す」設計に注意): コード中の
**「基本的に正常系の戻り値を返す。」**というコメントは、
意図を理解しておく必要がある。

この例はWeb Storage に溜めたデータを送信する処理であり
(後掲の JavaScript を参照)、
**「送信が成功したら Web Storage をクリアする」**という
動作になっている。

【HTTP 500 を返す設計】
  サーバ側で業務エラー → 500 を返す
     ↓ クライアントは error コールバックへ
  Web Storage をクリアしない → 次回も同じデータを送る → 永久に失敗し続ける

【本文の設計(200 + IsError フラグ)】
  サーバ側で業務エラー → 200 で IsError=true を返す
     ↓ クライアントは success コールバックへ
  「受け取った」ことは確定 → クリアするか否かを業務判断で決められる

つまり、「通信の失敗」と「業務の失敗」を分けている
これ自体は妥当な設計だが、注意点がある。

注意 内容
監視で異常が見えない 常に 200 なので、エラー率の監視に引っかからないJmeterによるWebアプリの負荷テストの「HTTP 200 で返ってくるエラー画面」と同じ問題)
REST の作法から外れる HTTP のステータス コードを使わない
クライアントの実装漏れ IsError を見忘れると失敗に気付かない

現在の設計指針としては、

  • 通信・入力の誤り(4xx)、サーバの障害(5xx)は HTTP で表す
  • 業務上の結果(在庫切れ、承認却下)は 200 + 本文で表す

と切り分け、エラー本文は
**RFC 9457(Problem Details for HTTP APIs)**の形式に揃えるのが
標準的である。

呼び出し側の実装(jQuery)

function PostJsonFromWebStorage(url) {
    // Web Storageのすべての情報の取得
    var jsonArray = new Array();

    for (var i = 0; i < storage.length; i++) {
        var _key = storage.key(i);

        // Web Storageのキーと値を表示
        var jsonBean = {
            key: _key,
            value: storage.getItem(_key)
        };

        jsonArray.push(jsonBean);
    }

    // <p id="url"></p> に表示
    if (document.getElementById("url") != null) {
        $("#url").text(url);
    }
    // <p id="request"></p> に表示
    if (document.getElementById("request") != null) {
        $("#request").text("request:" + JSON.stringify(jsonArray).toString());
    }

    CallService("POST", url, JSON.stringify(jsonArray), "application/json; charset=utf-8", "JSON", false);
}

// ---------------------------------------------------------------
// ajax
// ---------------------------------------------------------------
// 引数
//         Type : GET or POST or PUT or DELETE verb
//         Url : Location of the service
//         Data : Data sent to server
//         ContentType : Content type sent to server
//         DataType : Expected data format from server
//         ProcessData : True or False
// 戻り値  -
// ---------------------------------------------------------------
function CallService(Type, Url, Data, ContentType, DataType, ProcessData) {
    $.ajax({
        type: Type,
        url: Url,
        data: Data,
        cache: false,
        contentType: ContentType,
        dataType: DataType,
        processdata: ProcessData,
        success: function (data) {
            // On Successfull service call
            ServiceSucceeded(data);
        },
        error: function (data) {
            // When Service call fails
            ServiceFailed(data);
        }
    });
}

// ---------------------------------------------------------------
// $.ajaxのコールバック(success)
// ---------------------------------------------------------------
// 引数    data
// 戻り値  -
// ---------------------------------------------------------------
function ServiceSucceeded(data) {
    // Success
    if (document.getElementById("response") != null) {
        // <p id="response">response</p> に結果を表示
        $("#response").text("response-success:" + JSON.stringify(data).toString());
    }
    ClearWebStorage(); // 送信成功のため、WebStorageをクリア
}

// ---------------------------------------------------------------
// $.ajaxのコールバック(error)
// ---------------------------------------------------------------
// 引数    data
// 戻り値  -
// ---------------------------------------------------------------
function ServiceFailed(data) {
    // Error
    if (document.getElementById("response") != null) {
        // <p id="response">response</p> に結果を表示
        $("#response").text("response-error:" + JSON.stringify(data).toString());
    }
    // 送信失敗のため、WebStorageをクリアしない。
}

補足($.ajax のオプションで押さえるべき 3 点): このコードには
JSON を POST する際の必須設定が含まれている。

オプション なぜ必要か
contentType application/json 既定は application/x-www-form-urlencoded。これを指定しないとサーバ側で JSON として解釈されない
data JSON.stringify(...) オブジェクトのまま渡すと jQuery がフォーム形式に変換してしまう
dataType "JSON" 応答を JSON として解釈する

contentType の指定漏れは非常に多い誤りで、
「サーバ側で引数が null になる」という症状で現れる。

なお、processdata(小文字)は
正しくは processData(大文字の D)である。
jQuery のオプションは大文字小文字を区別する
ため、
この記述では既定値(true)のままになっている。
ただし、data が既に文字列であれば processData
実質的に影響しないため、動作としては問題にならない。

補足(この「Web Storage に溜めて後で送る」構造): コード全体が
オフライン対応の実装になっている点が興味深い。

① 入力のたびに Web Storage(localStorage)に保存
     ↓ 通信できない状況でも入力を続けられる
② 通信可能なときに、溜まった分をまとめて POST
     ↓
③ 成功したら Web Storage をクリア

これは Store and Forward と呼ばれる古典的な方式で、
現在のモバイル Web でも有効な考え方である。

ただし、現在はより整理された手段がある。

手段 内容
Service Worker + Background Sync ブラウザを閉じても、通信可能になった時点で自動送信
IndexedDB localStorage より大容量・非同期・構造化データ向き
navigator.sendBeacon() 離脱時の送信(IE、WWWブラウザのいろいろ

また、localStorage に業務データを保存することには
セキュリティ上の注意が要る。

  • XSS があれば JavaScript から全部読める(Cookie の HttpOnly のような保護が無い)、
  • 端末に平文で残る

ため、個人情報や機密情報を置かない
送信後は確実にクリアするという運用が前提になる
Webアプリケーション脆弱性対策)。

ASP.NET Web API の場合

移行メモ: 原典ではこの節は見出しのみで、本文が存在しない。

補足(Web API での受信): 未記載であるため、要点を補っておく。
WCF の場合と同じく、属性でバインド元を指定する形になる。

public class AnswersController : ApiController
{
    // POST api/answers?userName=u1&storeID=s1
    public StringResponse Post(
        [FromBody] KeyValRequest[] akv,      // Body の JSON
        [FromUri] string userName,           // クエリ文字列
        [FromUri] string storeID)
    {
        ...
    }
}
属性 バインド元
[FromBody] 要求本文1 つだけ
[FromUri] URL(ルート テンプレート/クエリ文字列)
省略時 単純型は URL、複合型は Body(WCF と同じ既定)

[FromBody] を 2 つ指定できないという制約は WCF と同じで、
理由も同じ(要求本文は 1 回しか読めないストリームであるため)。

なお、ASP.NET Core では属性名が変わる
[FromBody][FromQuery][FromRoute][FromForm])。
移行時には**[FromUri][FromQuery]** の置き換えが必要になる。


Tags: 移行, .NET開発, 通信技術, .NET Core, ASP.NET, ASP.NET Web API

NetDevInfraWiki

マイクロソフト系技術情報 Wiki
Open 棟梁 Wiki

(未着手)

開発基盤部会 Wiki

移行管理: DONETODO

Clone this wiki locally