[[Open棟梁Project>http://opentouryo.osscons.jp/]] - [[マイクロソフト系技術情報 Wiki>http://techinfoofmicrosofttech.osscons.jp/]]

* 目次 [#ec67115c]
#contents

*概要 [#zb9de49d]
[[WCF]] や [[WebAPI]] で、指定の REST Format に適合する JSON を返す方法を説明する。

*はじめに [#vf3529ba]
**出力したい JSON フォーマットを確認する [#dbc5c33d]
まず、どのようなフォーマットの JSON を出力したいのかを確認します。~
これがなければ、始まりません。

**出力したい JSON に合わせて、クラスやプロパティを定義する [#e10b9cbb]
次に、出力したい JSON に合わせて、クラスやプロパティを定義します。

***基本 [#i16a4292]
基本的には、以下のルールに従って、クラスやプロパティを定義します。

-JSON オブジェクト ({ キー名 : 値 }) の場合
--「キー名」を名前に持つプロパティを定義する
-JSON 配列 ([ 値1, 値2, ... ]) の場合
--リストまたは配列のプロパティを定義する

-キー名が不定の場合
--キー名が不定 (実行時に決まる) の場合、事前のクラス/プロパティ定義ができない。~
--このようなときは、Dictionary<TKey, TValue> を、Dictionary<string, object> として定義する。

***ルール [#t6d4fac1]
|出力したいJSON|定義するクラス・プロパティ|h
|{ "key1" : "value1", "key2" : "value2" }|public class Sample&br;{&br;&nbsp;&nbsp;&nbsp;&nbsp;''string key1'' { get; set; }&br;&nbsp;&nbsp;&nbsp;&nbsp;''string key2'' { get; set; }&br;}|
|[ "1", "2", "3" ]|''List<string>'' listData { get; set; }|
|{ "key1" : [ "1", "2", "3" ] }|public class Sample&br;{&br;&nbsp;&nbsp;&nbsp;&nbsp;''List<string> key1'' { get; set; }&br;}|
|{ "key" : { "key1" : "value1", "key2" : "value2" } }|public class Sample&br;{&br;&nbsp;&nbsp;&nbsp;&nbsp;''string key1'' { get; set; }&br;&nbsp;&nbsp;&nbsp;&nbsp;''string key2'' { get; set; }&br;}&br;public class Sample2&br;{&br;&nbsp;&nbsp;&nbsp;&nbsp;''Sample key'' { get; set; }&br;}|
|[ { "key1" : "value1", "key2" : "value2" }, &br;{ "key1" : "value3", "key2" : "value4" } ]|public class Sample&br;{&br;&nbsp;&nbsp;&nbsp;&nbsp;''string key1'' { get; set; }&br;&nbsp;&nbsp;&nbsp;&nbsp;''string key2'' { get; set; }&br;}&br;''List<Sample>'' listData { get; set; }|

***Web Essentials を使用してクラスを自動生成する [#y3ecc544]
Visual Studio の拡張機能である、Web Essentials を使うと、JSON からクラスを自動生成してくれます。~
Web Essentials は、Visual Studio のメニューから、[ツール] - [拡張機能と更新プログラム] で追加できます。~
&ref(WebEssentials.png,nolink,80%);~
~
JSON 文字列をコピーし、Visual Studio のエディターで、右クリックまたは [編集] メニューから、[形式を選択して貼り付け] - [JSON をクラスとして貼り付ける] を選択すると、その JSON フォーマットに合ったクラスが生成されます。

*JSONを返すサービスを作成する [#n81c4388]
**WCF の場合 [#wdb092a0]
WCF で JSON を返す場合、既定では DataContractJsonSerializer が使用されます。

以下に注意して下さい。

-contractの定義
-web.configの設定方法
-urlの指定方法


***既定の DataContractJsonSerializer を使用して JSON を返す方法 [#c008c778]
-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>

-&color(red){"http://~/JSONService.svc/GetJson"}; にリクエストを送ったときの実行結果
 {"IntKey":123,"ListKey":["List1","List2","List3"],"StringKey":"StringValue"}

-既定の DataContractJsonSerializer の注意点~
既定の DataContractJsonSerializer では、Dictionary 型のオブジェクトを正しく扱えません。~
これは、[[基本>#i16a4292]]でもご紹介しましたように、キー名が不定の場合に起きる問題です。~
~
Dictionary 型のオブジェクトを扱う場合は、[http://www.newtonsoft.com/json JSON.NET] や JavaScriptSerializer など、その他のSerializerを使用する必要があります。その方法は、次に紹介します。

***DataContractJsonSerializer 以外の Serializer を使用して JSON を返す方法 [#t49e9049]
-WCF サービスを作成し、
--コントラクト部分に WebGet 属性を追加する(POSTの場合は、WebInvoke属性を追加を追加する。)
--UriTemplate に、WebAPIに対応するURLパスの一部を設定する。

-ポイントは以下の 2 点です。
--ResponseFormat には何も指定しない
--サービスメソッドの戻り値の型は、[[System.ServiceModel.Channels.Message>https://msdn.microsoft.com/ja-jp/library/system.servicemodel.channels.message.aspx]] を指定する~
(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; }
 }

-作成した 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>

-&color(red){"http://~/JSONService2.svc/GetJson"}; にリクエストを送ったときの実行結果
 {"key":{"key1":"value1","key2":"value2","key3":"value3"}}

***WCF における REST(JSON) 処理の参考情報 [#g54ac3a7]
-WCF を利用した RESTful Web サービス の 作成~
http://garafu.blogspot.jp/2013/08/wcf-restful-web.html
-WCF Web サービス に JSON オブジェクト を 渡す 方法~
http://garafu.blogspot.jp/2014/05/wcf-request-jsonobject.html
-How to create a JSON WCF RESTful Service in 60 seconds - CodeProject~
http://www.codeproject.com/Articles/167159/How-to-create-a-JSON-WCF-RESTful-Service-in-sec

-[[WCF における REST(JSON)のサンプル・コード>https://github.com/OpenTouryoProject/OpenTouryotemplates/tree/develop/root_VS2012/programs/C%23/Samples/WebApp_sample/ProjectX_sample/App_Code]]

***WebサイトをIIS以下に配置した場合に例外が発生 [#i07e8464]
以下の例外(メッセージ)が出力されることがある。

 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 属性を追加する。


**ASP.NET Web API の場合 [#paf23e5b]
ASP.NET Web API は、既定で JSON.NET によって Serialize されます。~
このため、Dictionary 型も問題なく Serialize できます。

-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''を必ずつけるようにしてください。

*Java など、他プラットフォームとの相互運用 [#nf281504]
Java などの他プラットフォームとの相互運用を考えた場合も、まずは出力したい JSON フォーマットを確認することが基本です。

つまり、以下のような流れとなります。
+出力したい JSON フォーマットを決める~
(これがデータコントラクトとなる)
+そのフォーマットにあわせて、
++Java であれば POJO、
++.NET であれば POCO

>クラスを作成する。

**POJO または POCO をデータコントラクトとすべきでない理由 [#z310e642]
.NET と Java では、JSON シリアライザの挙動が違います。

-そのため、同じ形式の POJO または POCO であっても、シリアライズ後の JSON フォーマットが異なります。~
(例えば、以下のように Java と .NET では、ジェネリック型を含むオブジェクトの JSON シリアライズ結果が異なる)

-このため、実際に送受信される JSON フォーマットを、コントラクトとするのが良いと思います。

|言語|POJO / POCO サンプル|シリアライズされた JSON|h
|Java|public class Sample {&br();&nbsp;&nbsp;&nbsp;&nbsp;public String _key1;&br();&nbsp;&nbsp;&nbsp;&nbsp;public String _key2;&br();&nbsp;&nbsp;&nbsp;&nbsp;public void setKey1(String key1) {&br();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this._key1 = key1;&br();&nbsp;&nbsp;&nbsp;&nbsp;}&br();&nbsp;&nbsp;&nbsp;&nbsp;public String getKey1() {&br();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return this._key1;&br();&nbsp;&nbsp;&nbsp;&nbsp;}&br();&nbsp;&nbsp;&nbsp;&nbsp;public void setKey2(String key2) {&br();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this._key2 = key2;&br();&nbsp;&nbsp;&nbsp;&nbsp;}&br();&nbsp;&nbsp;&nbsp;&nbsp;public String getKey2() {&br();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return this._key2;&br();&nbsp;&nbsp;&nbsp;&nbsp;}&br();}|{ "key1" : "value1", "key2" : "value2" }|
|~|public class Sample {&br();&nbsp;&nbsp;&nbsp;&nbsp;public HashMap<String, String> _key;&br();&nbsp;&nbsp;&nbsp;&nbsp;public void setKey(HashMap<String, String> key) {&br();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this._key = key;&br();&nbsp;&nbsp;&nbsp;&nbsp;}&br();}|~|
|~|public class Sample {&br();&nbsp;&nbsp;&nbsp;&nbsp;public HashMap<String, String> _key;&br();&nbsp;&nbsp;&nbsp;&nbsp;public void setKey(HashMap<String, String> key) {&br();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this._key = key;&br();&nbsp;&nbsp;&nbsp;&nbsp;}&br();&nbsp;&nbsp;&nbsp;&nbsp;public void getKey() {&br();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return this._key;&br();&nbsp;&nbsp;&nbsp;&nbsp;}&br();}|~|
|.NET|public class Sample {&br();&nbsp;&nbsp;&nbsp;&nbsp;public string key1 { get; set; }&br();&nbsp;&nbsp;&nbsp;&nbsp;public string key2 { get; set; }&br();}|~|
|~|public class Sample {&br();&nbsp;&nbsp;&nbsp;&nbsp;public Dictionary<string, string> key { get; set; }&br();}|[&br();&nbsp;&nbsp;&nbsp;&nbsp;{&br();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"Key": "Prop1", &br();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"Value": "Value1"&br();&nbsp;&nbsp;&nbsp;&nbsp;}, &br();&nbsp;&nbsp;&nbsp;&nbsp;{&br();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"Key": "Prop2", &br();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"Value": "Value2"&br();&nbsp;&nbsp;&nbsp;&nbsp;}&br();]&br();&br();''同じ形式のジェネリック型であっても、&br();Java と .NET ではシリアライズ結果が異なる''|

*参考 (Microsoft Azure が公開している REST API サンプル) [#lcad8d73]
数個、Microsoft Azure が公開している REST API をピックアップしてみました。

-List all virtual machines in a resource group~
https://msdn.microsoft.com/en-us/library/azure/mt163572.aspx
>resourceGroup の name が REST の Uri に含まれていて、Responce が JSON 形式で返されます

-Start a virtual machine~
https://msdn.microsoft.com/en-us/library/azure/mt163628.aspx
>Responce が HTTP 200 だけのもあります

-Create or update a virtual machine~
https://msdn.microsoft.com/en-us/library/azure/mt163591.aspx
>呼び出すパラメタが大きく、変更を加える操作は、PUT で行われ、Request Body に パラメタが JSON 形式で渡されます。

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


トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS