かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

RESTでJSONなサービスをWCFで作ったり使ったりする

最近RESTが本格的に主流になりそうな感じですね。
SOAPでいい〜んだよSOAPでと思ってたら、以下のようなニュースとかも出てきたりして、ちょっと下火な感じがします。


ということでトレンドを追いかけるということと、どうせなら得意なC#でやっちゃえ!ということで今回はWCFでRESTでJSONなサービスを作る&使うことをやってみようと思います。下のような記事も出てるので、WCFでRESTやっておいて損はないでしょう。

ということで早速作っていきましょう。




プロジェクトの作成と実行方法の設定

まず、サービスを公開するためのWebアプリケーションを作成します。新規作成から「ASP.NET 空のWebアプリケーション」を作ります。余計なページとかは今回邪魔なのでシンプルに行きます。名前はRESTWcfAppにしました。
ここでWCFのサービスを公開します。


次に「WCF サービス ライブラリ」のプロジェクトを作成します。名前はRESTWcfApp.Libにしました。ここに、WCFのサービスのインターフェースと実装を作ります。


最後に「コンソールアプリケーション」のプロジェクトを作成します。名前はRESTWcfApp.Clientにしました。ここに、WCFのサービスを使うプログラムを作ります。

全部のプロジェクトのターゲットのフレームワークを.NET Framework 4にします(Client Profileじゃない)。そして、System.ServiceModelとSystem.ServiceModel.Webを参照に追加します。(最初から追加されてるのもあるだろうけど)
RESTWcfAppプロジェクトにはSystem.ServiceModel.Activationも追加します。

そして、RESTWcfApp.ClientとRESTWcfAppプロジェクトにRESTWcfApp.Libを参照に追加します。

ここまでで、プロジェクトは以下のようになります。



Webアプリケーションは、デフォルトだと適当なポート番号で起動するようになってるので8080番ポートで起動するように設定しておきます。

そして、ソリューションのプロパティからスタートアップの方法をマルチスタートアッププロジェクトにして以下のように設定します。


設定の説明だけで、すごい長くなりましたが、以上でプロジェクトの設定完了です!!

WCFのサービスを作る

今回は、名前を渡すと「こんにちは名前さん」という文字列を返してくれるという非常に簡単なサービスを作ります。文字列を渡すパターンとオブジェクトを渡すパターンの2パターン定義します。ついでに、文字列を渡す方をGETで、オブジェクトを渡す方をPOSTで定義してみます。

まず、RESTWcfApp.Libプロジェクトに最初からあるService1とかいうのは容赦なく消します。邪魔なだけなので。
そこに、GreetService.csを作ります。今回は、小さなサンプルなので1ファイルに全部書いちゃいました。

namespace RESTWcfApp.Lib
{
    using System.Runtime.Serialization;
    using System.ServiceModel;
    using System.ServiceModel.Web;
    using System.ServiceModel.Activation;

    [ServiceContract]
    public interface IGreetService
    {
        // GETメソッドで /greet/name引数の値の形で呼び出される
        [OperationContract]
        [WebGet(ResponseFormat = WebMessageFormat.Json, UriTemplate = "greet/{name}")]
        string Greet(string name);

        // POSTメソッドでgreet2というURLで呼び出される
        [OperationContract]
        [WebInvoke(Method = "POST", 
            RequestFormat = WebMessageFormat.Json, 
            ResponseFormat = WebMessageFormat.Json,
            UriTemplate = "greet2")]
        GreetResponse Greet2(GreetRequest req);
    }

    // 単純にIGreetServiceを実装する
    // ASP.NET互換で動くように設定する
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
    public class GreetService : IGreetService
    {
        public string Greet(string name)
        {
            return "こんにちは" + name + "さん";
        }

        public GreetResponse Greet2(GreetRequest req)
        {
            return new GreetResponse
            {
                Message = this.Greet(req.Name)
            };
        }
    }

    // こんなJSONになるイメージ? { "name" : "たろう" }
    [DataContract]
    public class GreetRequest
    {
        [DataMember(Name = "name")]
        public string Name { get; set; }
    }

    // こんなJSONになるイメージ? { "message" : "こんにちはたろうさん" }
    [DataContract]
    public class GreetResponse
    {
        [DataMember(Name = "message")]
        public string Message { get; set; }
    }
}

ポイントはWebGetやWebInvoke属性と、DataContractやDataMemberでJSONの形を決めてる所だと思います。後は、ASP.NET互換で動くようにしてるところです。これは、あとでどういうことか示します。

Webサービスとして公開する

さて、今までWCFでサービスを公開する際の一般的な方法は.svcファイルを作ってWebアプリケーションに置くことでした。今回は、ASP.NET4で追加されたルーティングを使って公開する方法でやってみます。
この方が構成ファイルでうだうだ書くより、個人的に好きです。

まず、グローバルアプリケーションクラスを作成して、Application_Startに以下の記述を追加します。

protected void Application_Start(object sender, EventArgs e)
{
    // /GreetServiceというURLでアクセスGreetServiceクラスで実装してる
    // サービスにアクセスできるようにする。
    RouteTable.Routes.Add(
        new ServiceRoute("GreetService",
            new WebServiceHostFactory(),
            typeof(GreetService)));
}

このようにすることで、.svcみたいな、わけわからない拡張子じゃなくて綺麗で、よりRESTっぽいURLでWCFのサービスを公開することが出来ます。

このルーティングを使うにはWCFがASP.NETの上で動くようにしないといけません。ということでWeb.configを以下のようにします。

<?xml version="1.0" encoding="utf-8"?>

<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>
  <system.serviceModel>
    <!-- WCFがASP.NET環境で動くように -->
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
  </system.serviceModel>
</configuration>

これでサービスとして公開することが出来ました。実行してみましょう。Webブラウザとコンソールアプリが起動すると思います。コンソールアプリは、まだ何も作ってないので放置しておいて、ブラウザのほうに以下のURLを入力してみましょう。

http://localhost:8080/GreetService/greet/ugaya40

そうすると、ブラウザに以下のような表示がされると思います。

とりあえず動作は確認できました。

クライアントアプリケーションの作成

REST形式のWebサービスは公開できたので、今度はこれを使っていきます。コンソールアプリケーションを以下のように書いてしまえばOKです。

namespace RESTWcfApp.Client
{
    using System;
    using System.ServiceModel.Web;
    using RESTWcfApp.Lib;

    class Program
    {
        static void Main(string[] args)
        {
            // FiddlerでキャプチャしたいのでURLをFiddlerがlocalhostを捕捉するためのURLにする
            // var f = new WebChannelFactory<IGreetService>(new Uri("http://localhost:8080/GreetService"));
            var f = new WebChannelFactory<IGreetService>(new Uri("http://ipv4.fiddler:8080/GreetService"));
            var service = f.CreateChannel();

            // greetのほう(GET)
            var message = service.Greet("太郎");

            // greet2のほう(POST)
            var response = service.Greet2(
                new GreetRequest
                {
                    Name = "田中"
                });

            // 結果を表示
            Console.WriteLine("Greet: {0}", message);
            Console.WriteLine("Greet2: {0}", response.Message);

            // 後始末
            f.Close();
        }
    }
}

WebChannelFactory使ってProxy作って、あとはメソッドを呼ぶだけです。URLがFiddlerを使ってリクエストとレスポンスをキャプチャしたいので、それ用のURLになってます。Fiddlerを使わない場合はlocalhostでOKです。

ということでFiddlerを起動して、アプリケーションを実行すると以下のように表示されます。ちゃんとサービスが呼べているのがわかると思います。

Greet: こんにちは太郎さん
Greet2: こんにちは田中さん
続行するには何かキーを押してください . . .

Fiddlerでキャプチャした結果を見てみよう

まずは、最初のGreetメソッドの呼び出しです。これはGETで呼び出されてるのがわかると思います。パラメータもURLの一部として渡されてます。

そして、Grret2メソッドの呼び出しに該当する部分も見てみます。


これはPOSTで、BODYにJSON形式でデータが渡されてることがわかると思います。レスポンスもばっちりJSONです。

まとめ

ということで.NET Framework 4のWCFでRESTのサービスを公開・呼び出す方法のポイントは以下のようになりそうです。

  • Client Profileは残念ながらダメ
  • WebGet, WebInvokeでRESTで呼ばれた時の挙動を設定
  • DataContractとDataMemberでJSONのフォーマットを設定
  • [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]をサービスの実装クラスに設定する
  • .svcファイルじゃなくてRouteTableを使ってサービスを公開できる
    • 別に.svcファイルでもRESTのサービスは公開できるけど、ここらへんは好みの問題?
    • RESTの時はWebServiceHostFactoryをファクトリに設定する
  • Web.configにを追加する
  • WebChannelFactoryを使ってサービスを呼び出すためのProxyを作る

以上、ちょっと長めのエントリになりましたけど終わりです。

追記:プロジェクトは、こちらからダウンロードできます。