かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

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を作る

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

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