F#+WCFでRESTfulなサービスをこしらえる・クライアントをこしらえて遊ぶ

クライアントを作ってみよう!


今まではずっとHTTP GETでオペレーションを実装して来たので、ブラウザから確認出来ましたが、
他のHTTPの動詞を使おうとするとブラウザでは少々力不足です。


と言うわけで今日はクライアントを準備します。今日はまだPOSTとかは出てきません。

サービスを作る


サービスの基本的な作り方も分かったので新しいサービスを追加します。


Account.fs を追加

#light  
namespace FSharpWCF  
  
open System  
open System.Runtime.Serialization  
open System.ServiceModel  
open System.ServiceModel.Web

  
[<ServiceContract>]  
type IAccountService = interface
  [<OperationContract>]  
  [<WebGet(UriTemplate="account")>]  
  abstract GetAllAccounts : unit -> (string * string) []
  
  [<OperationContract>]  
  [<WebGet(UriTemplate="account/{id}")>]  
  abstract GetAccount : id:string -> string
end  
  
type AccountService() =
  static let accounts = Map.ofList [("0", "cat"); ("1", "dog")]
  interface IAccountService with  
    member this.GetAllAccounts () =
      accounts |> Map.toArray
    member this.GetAccount id =
      let ac = accounts |> Map.tryFind id
      match ac with
      | Some (x) -> x
      | None     -> raise (WebFaultException(System.Net.HttpStatusCode.NotFound))


.config ファイルも修正

        ....
      </service>
      <service behaviorConfiguration="FSharpWCF.GetDataServiceBehavior"
        name="FSharpWCF.AccountService">
        <endpoint behaviorConfiguration="Web" binding="webHttpBinding"
          contract="FSharpWCF.IAccountService">
          <identity>
            <dns value="localhost" />
          </identity>
        </endpoint>
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8080/FSharpWCF/AccountService/" />
          </baseAddresses>
        </host>
      </service>
    </services>


GetAllAccount オペレーションは UriTemplate での変数束縛がないので引数は unit です。
返り値ですが、ID(string)と名前(string)のタプルの配列を返してみました。配列は返した事がありますが、タプルはどうなるのでしょうか。
Account サービスに実験データ(account)を用意して、このタプルの配列を返してみます。
"名前"とか言っておいて実験データは"cat","dog"です。

[{"m_Item1":"0","m_Item2":"cat"},{"m_Item1":"1","m_Item2":"dog"}]

おお、見事にシリアライズされました。要素の名前が当然ながら自動生成されたものですがちゃんとシリアライズされています。


もう一方のGetAccount オペレーションはIDを引数に取り、そのIDのアカウントが見つかれば名前を返し、見つからなければNotFoundException(HTTP 404)を返します。
パターンマッチとかOptionが使用出来るので良いですね。F#っぽくなってきました。
このオペレーションもブラウザで確認する事が出来ました。存在しないIDを指定すると例外が発生します。

クライアントを作る


そろそろクライアントを作り始めます。
F#で作成してもいいのですが、F#で作ったサービスとC#で作ったクライアントの連携が見たいのでC#で作ります。

  • 同じソリューション内にC#プロジェクトを作成(僕はWindowsFormアプリケーションで作成しました)
  • 「参照の追加」よりF#で作成したサービスプロジェクトを参照します
  • 適当にボタンを配置して、イベントハンドラとして以下のソースコードを記述します。
    private void button1_Click(object sender, EventArgs e)
    {
      var baseuri = new Uri("http://localhost:8080/FSharpWCF/AccountService/");
      using (var f = new WebChannelFactory<IAccountService>(new WebHttpBinding(), baseuri))
      {
        var service = f.CreateChannel();
        var message1 = service.GetAllAccounts();
        foreach (var item in message1)
        {
          Console.WriteLine(item.Item1 + " : " + item.Item2);
        }

        var message2 = service.GetAccount("1");

        Console.WriteLine(message2);
      }
    }
  • ソリューションのプロパティからスタートアップをマルチスタートアッププロジェクトに指定し、サービスとクライアントが同時にスタートされるようにします。


  • デバッグ実行し、クライアントのボタンを押す事でサービスとの通信を開始します


WebChannelFactory でサービスへのチャンネルファクトリを作成し、CreateChannel で実際にサービスへのチャンネルを作成します。
チャンネルを通じてサービスのオペレーションへアクセス可能です。もちろんインテリセンスも有効です。
GetAllAccount(), GetAccount() がブラウザと同じように動く事が確認出来ました。
気になるのはGetAllAccount() の返り値の型ですが、これはTupleとなりました。二項のタプルではなく3,4と増えたらどうなるか気になる所ではありますが、保留します。
(追記:4つ組のタプルはTupleになりました)