Skip to content

MS_JSONRestService

nishi_74322014 edited this page Aug 19, 2026 · 1 revision

JSONを送信するRESTサービスを作成する方法

概要

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

補足(本ページの読みどころ): 個別の実装手順よりも、
後半の**「相互運用に関する注意事項」**が本ページの核心である。

**「いきなり POCO をデータコントラクトとせず、
先ずは出力したい JSON フォーマットを確認する」**という主張は、
現在の API ファースト/スキーマ ファーストの考え方と一致しており、
実装技術が変わっても価値が失われない。

なお、WCF は .NET Framework 限定の技術であり、
新規開発では ASP.NET Web API / ASP.NET Core を使う
WCFのタイムアウトの最新化の補足を参照)。
WCF の節は既存システムの保守のための記録として読むとよい。

JSON フォーマットとクラスの定義

JSONを返すサービスを作成する

WCFの場合

WCF で JSON を返す場合、既定では DataContractJsonSerializer が使用される。

以下に注意する。

  • contract の定義
  • Web.configの設定方法
  • url の指定方法

DataContractJsonSerializer を使用

  • WCF サービスを作成し、
    • コントラクト部分に WebGet 属性を追加する(POST の場合は、WebInvoke 属性を追加する。)
    • UriTemplate に、WebAPI に対応する URL パスの一部を設定する。
  • ポイントは以下の 2 点。
    • ResponseFormat に、WebMessageFormat.Json を指定し、JSON を返すことを宣言する。
    • サービスメソッドの戻り値の型に、先ほど定義したクラス / プロパティの型を指定する。
  • サービスを実装する。
[ServiceContract]
public interface IJSONService
{
    [OperationContract]
    [WebGet(ResponseFormat = WebMessageFormat.Json, UriTemplate = "GetJson")]
    Sample GetJson();
}

public class JSONService : IJSONService
{
    public Sample GetJson()
    {
        // JSON にシリアライズする元となるオブジェクトを作成する
        Sample sample = new Sample()
        {
            StringKey = "StringValue",
            IntKey = 123,
            ListKey = new List<string>() { "List1", "List2", "List3" }
        };

        // クライアントにデータを返す
        return sample;
    }
}

public class Sample
{
    public string StringKey { get; set; }
    public int IntKey { get; set; }
    public List<string> ListKey { get; set; }
}
  • 作成した WCF サービスを、REST サービスとして公開するための設定を Web.config に行う。
<system.serviceModel>
    <services>
        <service name="WebApplication1.JSONService">
            <endpoint address="" binding="webHttpBinding" behaviorConfiguration="MyBehavior"
                      contract="WebApplication1.IJSONService" />
        </service>
    </services>
    <behaviors>
        <serviceBehaviors>
            <behavior name="">
                <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
                <serviceDebug includeExceptionDetailInFaults="false" />
            </behavior>
        </serviceBehaviors>
        <endpointBehaviors>
            <behavior name="MyBehavior">
                <webHttp/>
            </behavior>
        </endpointBehaviors>
    </behaviors>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"
        multipleSiteBindingsEnabled="true" />
</system.serviceModel>
  • http://~/JSONService.svc/GetJson にリクエストを送ったときの実行結果
{"IntKey":123,"ListKey":["List1","List2","List3"],"StringKey":"StringValue"}

補足(この出力結果に既に問題が現れている): 実行結果の JSON を
よく見ると、プロパティの順序が定義順と違う
StringKey, IntKey, ListKey と定義したのに
IntKey, ListKey, StringKey の順で出力されている)。

これは DataContractJsonSerializer
アルファベット順に並べるためである。

影響 内容
通常の JSON パーサ 問題ない(JSON にキーの順序の意味は無い)
署名対象にする場合 問題になる(バイト列が変わる)
目視での確認 定義と見比べにくい

順序を明示したい場合は [DataMember(Order = n)] を使うが、
そもそもこの種の細かい制御が必要になる時点で
JSON.NET 等に切り替えた方がよい
、というのが
次節以降の流れである。

JSON.NETなどを使用

  • WCF サービスを作成し、
    • コントラクト部分に WebGet 属性を追加する(POST の場合は、WebInvoke 属性を追加する。)
    • UriTemplate に、WebAPI に対応する URL パスの一部を設定する。
  • ポイントは以下の 2 点です。
    • ResponseFormat には何も指定しない
    • サービスメソッドの戻り値の型は、System.ServiceModel.Channels.Message を指定する
      (WCF の Message Body に JSON を直接書き込む)
  • サービスを実装する。
[ServiceContract]
public interface IJSONService2
{
    [OperationContract]
    [WebGet(UriTemplate = "GetJson")]
    Message GetJson();
}

public class JSONService2 : IJSONService2
{
    public Message GetJson()
    {
        // JSON にシリアライズする元となるオブジェクトを作成する
        Sample2 sample = new Sample2()
        {
            key = new Dictionary<string, string>()
            {
                {"key1", "value1"},
                {"key2", "value2"},
                {"key3", "value3"}
            }
        };

        // JSON.NET を使用して JSON 形式にシリアライズ
        string jsonStr = JsonConvert.SerializeObject(sample);

        // 'X-Content-Type-Options: nosniff' ヘッダーを追加する
        WebOperationContext.Current.OutgoingResponse.Headers.Add("X-Content-Type-Options", "nosniff");

        // JSON を返す
        return WebOperationContext.Current.CreateTextResponse(jsonStr,
            "application/json; charset=utf-8",
            Encoding.UTF8);
    }
}

public class Sample2
{
    public Dictionary<string, string> key { get; set; }
}

補足(X-Content-Type-Options: nosniff を付けている理由): 一見
JSON とは無関係に見えるが、セキュリティ上の必須事項である。

【nosniff が無い場合】
  サーバ:Content-Type: application/json で JSON を返す
  ブラウザ(旧 IE 等):中身を見て「HTML かも」と判断(MIME スニッフィング)
      ↓
  JSON の中にスクリプト要素が含まれていたら【実行してしまう】
      ↓ XSS が成立する

X-Content-Type-Options: nosniff を付けると、
ブラウザは Content-Type を信じて中身を推測しなくなる

利用者が入力した文字列を JSON に含めて返す APIでは
特に重要で、Webアプリケーション脆弱性対策
セキュリティ関連のHTTPヘッダでも扱われている。
現在はすべての応答に付けるのが標準である。

  • 作成した WCF サービスを、REST サービスとして公開するための設定を Web.config に行う。
<system.serviceModel>
    <services>
        <service name="WebApplication1.JSONService2">
            <endpoint address="" binding="webHttpBinding" behaviorConfiguration="MyBehavior"
                      contract="WebApplication1.IJSONService2" />
        </service>
    </services>
    <behaviors>
        <serviceBehaviors>
            <behavior name="">
                <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
                <serviceDebug includeExceptionDetailInFaults="false" />
            </behavior>
        </serviceBehaviors>
        <endpointBehaviors>
            <behavior name="MyBehavior">
                <webHttp/>
            </behavior>
        </endpointBehaviors>
    </behaviors>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"
        multipleSiteBindingsEnabled="true" />
</system.serviceModel>
  • http://~/JSONService2.svc/GetJson にリクエストを送ったときの実行結果
{"key":{"key1":"value1","key2":"value2","key3":"value3"}}

参考情報

WCF における JSON 処理の参考情報

注意点

  • DataContractJsonSerializer を使用する場合、相互運用性に注意する。
  • Web サイトを IIS 以下に配置した場合に例外が発生することがある。
    • 以下の例外(メッセージ)が出力されることがある。

      ASP.NET との互換性がないため、サービスをアクティブにできません。
      このアプリケーションでは、ASP.NET との互換性が有効になっています。
      web.config 内で ASP.NET の互換性モードを無効にするか、
      RequirementsMode に Allowed または Required が設定されたサービスの型に、
      AspNetCompatibilityRequirements 属性を追加してください。
      
    • この場合、

      • ASP.NET 互換性
        https://msdn.microsoft.com/ja-jp/library/ms752234.aspx

        [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
        public class CalculatorService : ICalculatorSession

        にあるように、サービスのクラスに、
        AspNetCompatibilityRequirements 属性を追加する。

補足(この例外が出る理由): WCF は本来
IIS に依存しない(自己ホストもできる)ため、
既定では ASP.NET のパイプラインを使わない

【既定(互換モード無し)】
  要求 → IIS → WCF が独自に処理
           ↑ HttpContext.Current は null
             セッション、認証、Cookie は使えない

【ASP.NET 互換モード】
  要求 → IIS → ASP.NET のパイプライン → WCF
           ↑ HttpContext.Current が使える

Web.configaspNetCompatibilityEnabled="true" にすると
アプリ全体が互換モードになるため、
すべてのサービス クラスが属性で意思表示する必要がある
これが例外メッセージの意味である。

属性の値 意味
Required 互換モードでのみ動く(HttpContext を使う)
Allowed どちらでも動く
NotAllowed(既定) 互換モードでは動かない ← これが例外の原因

IISの動作モデル
クラシック/統合モードの話と同じく、
**「ASP.NET のパイプラインに乗るかどうか」**という論点である。

ASP.NET Web API の場合

ASP.NET Web API は、既定で JSON.NET によって Serialize される。このため、Dictionary 型も問題なく Serialize できる。

設定

Web API の返す JSON フォーマット(JSON シリアライズのフォーマット)をWebApiConfigで、以下のように指定できる。

// JSON データにはCamelCaseを使用 (JSON.NET)
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();

JSON.NETを使用

  • Web アプリケーションに、Web API を作成する。
public class ValuesController : ApiController
{
    // GET api/values
    public Sample Get()
    {
        // JSON にシリアライズする元となるオブジェクトを作成する
        Sample sample = new Sample()
        {
            key = new Dictionary<string, string>()
            {
                {"key1", "value1"},
                {"key2", "value2"},
                {"key3", "value3"}
            }
        };

        // JSON を返す
        return sample;
    }
}

public class Sample
{
    public Dictionary<string, string> key { get; set; }
}
  • http://~/api/Values にリクエストを送ったときの実行結果
{"key":{"key1":"value1","key2":"value2","key3":"value3"}}

注意点

  • ASP.NET Web API の注意点
    既定では、ASP.NET Web API は、HTTP リクエストに含まれる Accept ヘッダーの内容により、クライアントに返すデータの形式が決められます。
    • Accept に application/xml が含まれていた場合は、XML として返されます。(レスポンスの Content-Type ヘッダーが application/xml となる)
    • Accept に application/json が含まれていた場合は、JSON として返されます。(レスポンスの Content-Type ヘッダーが application/json となる)
      このため、常に JSON としてデータを受け取りたい場合は、リクエストヘッダーに Accept: application/json を必ずつけるようにしてください。

補足(コンテンツ ネゴシエーションが引き起こす事故): この仕様は
REST の設計としては正しいが、実務では事故の原因になる

【開発時】 Postman や curl で Accept: application/json を付けて確認 → JSON
【本番】  ブラウザから直接アクセス(Accept: text/html,application/xhtml+xml,...)
           ↓ XML が返る
         「JSON が返ってこない」という問い合わせ

対処は 2 通りある。

手段 内容
クライアント側 本文の推奨。Accept: application/json を必ず付ける
サーバ側 XML フォーマッタを外す(JSON しか返さないと決める)
// XML を返さないようにする(ASP.NET Web API)
config.Formatters.Remove(config.Formatters.XmlFormatter);

JSON API と決めているなら、サーバ側で XML を外すのが
確実で、事故も減る。

なお、ASP.NET Core では既定で JSON のみであり、
XML を返すには明示的に追加する必要がある
(既定値がより実態に即した形に変わった)。

相互運用に関する注意事項

Java など、他プラットフォームとの相互運用を行なう際に注意すべき点。

DataContractJsonSerializerの問題

既定の DataContractJsonSerializer では、Dictionary 型のオブジェクトを正しく扱えない。

DataContractJsonSerializerの対策

その他のSerializerを使用する

Dictionary 型のオブジェクトを扱う場合は、JSON.NET など、その他の Serializer を使用する。

JSON フォーマットを、コントラクトとする

Java などの他プラットフォームとの相互運用を考えた場合、
いきなり POJO または POCO をデータコントラクトとせず、
先ずは出力したい JSON フォーマットを確認する事から始める。

以下の手順で、

  1. 出力したい JSON フォーマットを決める
    (これがデータコントラクトとなる)
  2. そのフォーマットにあわせて、
    1. Java であれば POJO
    2. .NET であれば POCO

クラスを作成する。

補足(この主張が本ページで最も重要): 「いきなり POCO を
データコントラクトとしない」という指針は、
現在の API 設計の標準的な考え方そのものである。

【コード ファースト(本文が戒めている方)】
  C# のクラスを書く → シリアライザが JSON を生成 → それが仕様になる
     ↓ 問題
  ・シリアライザを変えると JSON が変わる(互換性が壊れる)
  ・.NET の型の都合が JSON に漏れる(Dictionary、DateTime、enum)
  ・相手(Java 側)が同じ形を作れる保証が無い

【スキーマ ファースト(本文の推奨)】
  JSON の形(=契約)を先に決める
     ↓
  各言語で、その形に合うクラスを作る
     ↓
  ・実装技術を変えても契約は変わらない
  ・言語間で確実に一致する

現在は、この「契約を先に決める」という作業が
**OpenAPI(Swagger)**として標準化されている
Swagger / OpenAPI)。

手段 内容
OpenAPI 定義を先に書く YAML/JSON で契約を定義
コード生成 定義から各言語のクライアント/サーバのひな形を生成
契約テスト 実装が定義どおりかを自動検証

つまり、本ページが述べている手順が、
ツールによって自動化されたというのが現在の状況である。
主張自体は今も完全に有効である。

補足(DateTime の相互運用も同種の問題): 本文は Dictionary 型を
例に挙げているが、日付でも同じ問題が起きる。

シリアライザ 出力
DataContractJsonSerializer "/Date(1234567890000)/".NET 独自形式
JSON.NET(既定) "2025-01-01T00:00:00Z"(ISO 8601)
System.Text.Json "2025-01-01T00:00:00Z"(ISO 8601)

/Date(...)/Java や JavaScript がそのままでは解釈できない
これも「.NET の都合が JSON に漏れている」例であり、
JSON フォーマットを先に決めていれば防げた問題である。

なお、.NET Core 3.0 以降は System.Text.Json が標準であり、
JSON.NET(Newtonsoft.Json)は明示的に追加する形になった。
新規開発では System.Text.Json を使う(JSON)。

参考

Microsoft Azure が公開している REST API

数個、Microsoft Azure が公開している REST API をピックアップした。

これらの REST API でも、JSON のフォーマットが明記されており、
JSON フォーマットレベルでデータコントラクトを結ぶ必要がある。

移行メモ(体裁): 原典の「Responce」は
「Response」の誤字であるため修正した(2 箇所)。

補足: ここで挙げられている Azure の REST API は、
Azureの管理ポータルとARM APIで述べた
ARM API そのものである。
Microsoft 自身が「JSON フォーマットを契約として明記している」
という点が、本文の主張の裏付けになっている。

なお、ARM API の定義は
OpenAPI(Swagger)仕様として GitHub で公開されており
Azure/azure-rest-api-specs)、
各言語の SDK はそこから自動生成されている。
まさに前掲の「スキーマ ファースト」の実践例である。


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

NetDevInfraWiki

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

(未着手)

開発基盤部会 Wiki

移行管理: DONETODO

Clone this wiki locally