Part 3. Hello World WCF クライアントの開発
さて、前回のエントリでは Hello World メッセージを返す WCF サーバを作成し、SOAP over HTTP でリクエストを受け付けるエンドポイントを作成しました。引き続き本エントリでは、このサーバアプリケーションを呼び出すクライアントアプリケーションを作成していくことにします。
クライアントアプリケーション作成時には、Part 2. で作成した WCF サーバを起動しておき、WSDL ファイルを入手できる状態にしておく必要があります。このため、以降の作業は Part.2 で作成した WCF サーバアプリケーションを起動した状態で作業を進めてください。
※ Part 2. で完成させたソリューションは以下になります。
本エントリでは、以下の作業を行います。
- WCF プロキシクラスの作成と利用
- サービスログとトランスポートログ
- TCP/IP 通信への通信プロトコルの切り替え
では具体的な作業手順を以下に説明したいと思います。
[Step. 10] WCF プロキシクラスの作成と利用
まず、Visual Studio 2008 をもう一つ起動し、新規にコンソールアプリケーションをひとつ作成します(名前は "Sample01.Client" にしておきます)。
※ (参考) WCF サーバ/クライアントアプリケーションを開発する際に、サーバとクライアントのソリューションを分けるということに違和感を覚える方も多いと思います。……が、その違和感は極めてごもっともだと思います;。本来、ソリューションは「ビルドの単位」なわけで、I/F 整合性を保ってビルドされる複数のプロジェクトは同一ソリューションに含まれるべきです。SOAP over HTTP は疎結合と言われますが、スマートクライアントなどの開発においてはサーバ/クライアントは実態として密結合であることがほとんど。このため、XML Web サービスなどを使ってスマクラを作る場合には、サーバとクライアントは同一ソリューションに含める形で作るべきだと私は考えています。ところが WCF での開発の場合、サーバ側のアプリケーションがちゃんとビルド+実行できないと、MEX エンドポイントが公開できず、クライアント側でプロキシクラスを手軽に作ることができないという問題があり、サーバ/クライアントのソリューションを分けて作業しないと、恐ろしく開発作業が面倒になります(実際に一度やってみるとよいかも....かなり苦労します)。このため、(論理的には不適切だと思うのであまりお薦めしたくはないのですが開発の利便性を優先させて)、サーバ/クライアントをソリューションとして分けて開発する、という方法で説明しています。
作成したら、ここに WCF サービスを呼び出すためのプロキシクラスを作成します。
- ソリューションエクスプローラのコンソールアプリケーションプロジェクトを右クリックし、「サービス参照の追加」を選択します。
- アドレス欄に、MEX エンドポイントの URL を打ち込み、移動ボタンを押します。
(手順通りに作業している場合は、http://localhost:8000/Sample01.Server/WcfService/mex という名前にしているはずです。) - 正しく WSDL ファイルが入手できると、下図のように WCF サービスの内容が表示されます。
- さらに必要に応じて詳細設定を行いますが、ここでは特になにもせずに OK ボタンを押下します。(※ 例えば、既定では WCF のプロキシクラスが作成されますが、ASP.NET XML Web サービスのプロキシクラスを作成する場合には詳細設定からオプションを指定します。)
以上の作業により、プロキシクラスが作成されます。
作成されたプロキシクラスは、以下のようなコードでインスタンス化して利用することができます。
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace Sample01.Client
7: {
8: class Program
9: {
10: static void Main(string[] args)
11: {
12: ServiceReference1.WcfServiceClient proxy = new ServiceReference1.WcfServiceClient();
13: string ret = proxy.GetMessage("Nobuyuki");
14: proxy.Close();
15:
16: Console.WriteLine(ret);
17: }
18: }
19: }
※ なお、コード中でプロキシクラスを作成・利用した後に ".Close()" メソッドを呼び出してプロキシを閉じていますが、WCF を使う場合には、この .Close() 処理のコードを記述するクセをつけてください。単純な SOAP over HTTP 呼び出しなどでは、「単発のメソッド呼び出し」で処理が終了するため、.Close() 処理は不要ですが、例えば WS-ReliableMessaging や WS-Security などを利用している場合には、サーバとクライアント間で論理セッションが張られていることがあります。このため、.Close() 処理によりこうした論理セッションを解放してやらないと、サーバ/クライアントそれぞれでリソースリークとなることがあります。
実行すると、WCF サーバが呼び出され、以下のように Hello World メッセージが表示されます。
ここで、クライアント側のソリューション(Sample01.Client)内にある app.config ファイルを、テキストエディタや SvcConfigEditor で開いてみてください。
1: <?xml version="1.0" encoding="utf-8" ?>
2: <configuration>
3: <system.serviceModel>
4: <bindings>
5: <basicHttpBinding>
6: <binding name="BasicHttpBinding_WcfService" closeTimeout="00:01:00"
7: openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
8: allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
9: maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
10: messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
11: useDefaultWebProxy="true">
12: <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
13: maxBytesPerRead="4096" maxNameTableCharCount="16384" />
14: <security mode="None">
15: <transport clientCredentialType="None" proxyCredentialType="None"
16: realm="" />
17: <message clientCredentialType="UserName" algorithmSuite="Default" />
18: </security>
19: </binding>
20: </basicHttpBinding>
21: </bindings>
22: <client>
23: <endpoint address="http://localhost:8000/Sample01.Server/WcfService"
24: binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_WcfService"
25: contract="ServiceReference1.WcfService" name="BasicHttpBinding_WcfService" />
26: </client>
27: </system.serviceModel>
28: </configuration>
この設定ファイルに書き出された内容の詳細は理解する必要はありませんが、以下の点に注目してください。
- サーバに比べるとかなり複雑な構成設定が記述されています。これはクライアント側の app.config ファイルには WCF ランタイムの既定の設定値による構成設定値も書き出されるためです。
- SvcConfigEditor でみると、「サービス」のセクションではなく、「クライアント」のセクションにエンドポイントの構成設定(A/B/C 情報)が書き出される形になっています。(大枠として A/B/C について設定しなければならない点は同一ですが、詳細な設定項目はサーバ側とクライアント側でかなり異なっています。)
ちなみに、クライアント側の設定の中の重要な部分だけ抜粋すると、以下のようになります。記述するセクションは異なりますが、サーバ側の構成設定とクライアントの構成設定とが対称的な設定になっていることに着目してください。
■ クライアント側の構成設定(の要点)
1: <?xml version="1.0" encoding="utf-8" ?>
2: <configuration>
3: <system.serviceModel>
4: <client>
5: <endpoint address="http://localhost:8000/Sample01.Server/WcfService"
6: binding="basicHttpBinding" contract="ServiceReference1.WcfService" />
7: </client>
8: </system.serviceModel>
9: </configuration>
■ サーバ側の構成設定(の要点)
1: <?xml version="1.0" encoding="utf-8" ?>
2: <configuration>
3: <system.serviceModel>
4: <services>
5: <service name="Sample01.Server.WcfService">
6: <endpoint address="http://localhost:8000/Sample01.Server/WcfService"
7: binding="basicHttpBinding" contract="Sample01.Server.WcfService" />
8: </service>
9: </services>
10: </system.serviceModel>
11: </configuration>
[Step. 11] サービスログとトランスポートログ
さて、このままだと実際にサーバが呼び出されたか否かがわかりませんので、構成設定ファイルを書き替えて、ログファイルを取得してみることにします。
まず、いったん WCF のサーバ側アプリケーションを停止し、サーバ側(Sample01.Server)のソリューション内にある app.config ファイルを SvcConfigEditor で開きます。次に、この中の「診断」(Diagnostics)セクションを開き、以下の設定作業を行います。
- 構成ウィンドウ内の「診断」セクションの「メッセージログ」を有効化する。
- メッセージログの設定項目として現れる「ログ記録レベルとして、「間違った形式のメッセージ」「サービスメッセージ」「トランスポートメッセージ」の 3 つを有効化する。
- 構成ウィンドウ内のさらに「診断」セクションの下の「メッセージログ」の項目を開き、"LogEntireMessage" プロパティを "True" に変更する。
通常は以上の作業でおしまいですが(これにより C:\Users\nakama\Documents\Visual Studio 2008\Projects\Sample01.Server\Sample01.Server\App_messages.svclog などにログファイルが出力されるようになる)、ここではコンソール画面上にもログデータが出力されるようにしてみます。追加で以下の作業を行ってみてください。
- リスナセクションに、新しいリスナをひとつ追加する。
- "TypeName" プロパティを、System.Diagnostics.Console.TraceListener に変更する。("..." ボタンから選択すると簡単。選択すると、"System.Diagnostics.ConsoleTraceListener, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" が設定される。)
- ソースとして "System.ServiceModel.MessageLogging" を追加する。
以上の作業を行ったら、実際に WCF サービスを起動し、ログファイルを取得してみます。
- まず、既存のログファイルが残っている場合はこれを削除します。(Visual Studio のソリューションエクスプローラ上で、「すべてのファイルを表示」オプションを入れ、そこに現れる "App_messages.svclog" を削除するとラク。)
- WCF サーバアプリケーション(Sample01.Server)を起動します。
- WCF クライアントアプリケーション(Sample01.Client)を実行します。(サーバ側のコンソール上にログが出力されることを確認してください。)
- 呼び出しが終了したら、サーバアプリケーション上でリターンキーなどを押下し、穏やかに終了させてください。(=Ctrl+C などで強制終了させないでください。(host.Close() 処理により、ログファイルがクローズされるため。))
以上の作業により、App_messages.svclog ログファイルが出力されます。このファイルの中身は XML 形式で出力されたデータファイルであるため、このままではとても読み取れません。このため、サービストレースビューアというツールで読み取ります。(プログラムメニュー内の Microsoft Windows SDK 6.0A 下にあります)
※ このツールについても、SvcConfigEditor ツールなどと同様に、Visual Studio に登録しておくと便利です。
実際に出力されたログファイルの内容を確認すると、1 回のリクエストしか行っていないにもかかわらず、4 つのログが出力されています。これは、以下のような理由によります。
- 先に、サービスレベルとトランスポートレベルのログを出力するように設定しましたが、これは下図のように、WCF パイプラインの入口と出口のところそれぞれについてロギングを行う、という設定になります。
- メッセージが入ってくる流れと、レスポンスが出ていく流れそれぞれでロギングが行われるので、都合 4 つのログが出力されます。
なお、今回のサンプルでは、サービスレベルとトランスポートレベルのログがほとんど同じになっています。しかし、WS-Security などを使う場合には、2 つの場所で記録される内容がかなり変わります。(トランスポートレベルのメッセージは暗号化されており、サービスレベルのメッセージは暗号化されていない) WS-Security などを使う場合には、実際にログを取って確認してみるとよいでしょう。
[Step. 12] TCP/IP 通信への通信プロトコルの切り替え(サーバ側)
では最後に、WCF ランタイムの大きな特徴の一つである、マルチプロトコル対応(同一通信特性を持つ通信プロトコルへの切り替え)を示してみたいと思います。ここでは、SOAP over HTTP 通信を、TCP/IP 通信に取り替えてみたいと思います。作業は以下の流れで行います。
- サーバ側のバインディングの取り換え(basicHttpBinding から netTcpBinding への取り換え)
- クライアント側でのプロキシクラスの作り直し
ではまず、サーバ側のバインディングの取り換えを行います。サーバ側アプリケーションを停止させた上で、app.config ファイルを SvcConfigEditor で開き、以下の作業を行います。
- サービスのエンドポイントを開き、Binding を netTcpBinding に取り替えます。
- さらに、Address を net.tcp://localhost:8001/Sample01.Server/WcfService に切り替えます。(※ ポート番号は 8001 番などにずらしてください。これは、ポート 8000 では HTTP による MEX エンドポイントの公開を行っており、同一のポート番号に異なるプロトコルを割り当てることができないためです。)
なお、netTcpBinding では、既定でトランスポートレベル暗号化と呼ばれる暗号化処理が行われるようになっていますが、これは Windows 統合認証ベースで動作するため、ネットワーク環境によってはうまく動かないことがあります。そこで、今回は簡単のために暗号化を off にすることにします。以下の作業を行ってください。
- 「バインド」セクションから「新しいバインド構成」を選択し、netTcpBinding の新しい構成設定を追加する。
- "Name" プロパティを、"NewBinding0" から適当な名前(例えば "WcfService_NetTcpBinding" などのわかりやすい名前)に変更しておく。
- セキュリティタブを開き、セキュリティモードを "Transport" から "None" に変更する。
- 最後に、「サービス」セクションのエンドポイント構成設定に戻り、今、作成したバインド構成を BindingConfiguration として割り当てる。(この作業も忘れがちなので注意してください。)
以上の作業が済んだら、サーバアプリケーションを起動してください。(※ Windows Firewall にひっかかると思いますので、適宜許可を与えてください。)

見た目は特に変わっていませんが、このサーバは以下の 3 つのプロトコルを受け付けるようになっています。
なお、ポート 8000 番上の MEX エンドポイントは、HTTP 通信による WSDL ファイル取り出し要求を受け付けますが、そこで公開されている情報は TCP/IP 通信を受け付けるエンドポイントの情報であることに注意してください。 (実際に WSDL ファイルを見てみるとわかります。)
ご参考までに、ここまでの作業で作成された app.config(サーバ側)を示します。
1: <?xml version="1.0" encoding="utf-8" ?>
2: <configuration>
3: <system.diagnostics>
4: <sources>
5: <source name="System.ServiceModel.MessageLogging" switchValue="Warning, ActivityTracing">
6: <listeners>
7: <add type="System.Diagnostics.DefaultTraceListener" name="Default">
8: <filter type="" />
9: </add>
10: <add name="ServiceModelMessageLoggingListener">
11: <filter type="" />
12: </add>
13: <add name="NewListener">
14: <filter type="" />
15: </add>
16: </listeners>
17: </source>
18: </sources>
19: <sharedListeners>
20: <add initializeData="C:\Users\nakama\Documents\Visual Studio 2008\Projects\Sample01.Server\Sample01.Server\App_messages.svclog"
21: type="System.Diagnostics.XmlWriterTraceListener, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
22: name="ServiceModelMessageLoggingListener" traceOutputOptions="Timestamp">
23: <filter type="" />
24: </add>
25: <add type="System.Diagnostics.ConsoleTraceListener, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
26: name="NewListener" traceOutputOptions="LogicalOperationStack, DateTime, Timestamp, ProcessId, ThreadId, Callstack">
27: <filter type="" />
28: </add>
29: </sharedListeners>
30: </system.diagnostics>
31: <system.serviceModel>
32: <bindings>
33: <netTcpBinding>
34: <binding name="WcfService_NetTcpBinding">
35: <security mode="None" />
36: </binding>
37: </netTcpBinding>
38: </bindings>
39: <diagnostics>
40: <messageLogging logEntireMessage="true" logMalformedMessages="true"
41: logMessagesAtServiceLevel="true" logMessagesAtTransportLevel="true" />
42: </diagnostics>
43: <behaviors>
44: <serviceBehaviors>
45: <behavior name="WcfService_ServiceBehavior">
46: <serviceDebug httpHelpPageUrl="http://localhost:8000/Sample01.Server/WcfService/help"
47: includeExceptionDetailInFaults="false" />
48: <serviceMetadata httpGetEnabled="true" httpGetUrl="http://localhost:8000/Sample01.Server/WcfService/mex" />
49: </behavior>
50: </serviceBehaviors>
51: </behaviors>
52: <services>
53: <service behaviorConfiguration="WcfService_ServiceBehavior" name="Sample01.Server.WcfService">
54: <endpoint address="net.tcp://localhost:8001/Sample01.Server/WcfService"
55: binding="netTcpBinding" bindingConfiguration="WcfService_NetTcpBinding"
56: contract="Sample01.Server.WcfService" />
57: </service>
58: </services>
59: </system.serviceModel>
60: </configuration>
[Step. 13] TCP/IP 通信への通信プロトコルの切り替え(クライアント側)
次に、今度はクライアント側を TCP/IP 通信に切り替えます。この作業は簡単で、サーバ側アプリケーションを起動した状態(=WSDL ファイルを取得可能な状態)にしたままで、Visual Studio のサービス参照を更新するだけです。これにより、サーバ側から最新の WSDL ファイルが入手され、プロキシクラスがリフレッシュされ、TCP/IP 通信による WCF サービス呼び出しを行うことができるようになります。
※ 見た目は何も変わっていませんが、TCP/IP 通信によるサーバ呼び出しが行われています。
なお TCP/IP 通信の場合には、サーバ呼び出しはバイナリエンコード方式(SOAP メッセージが、XML 形式ではなくバイナリ形式にエンコードされる)が利用されています。これについての詳細は Part.1 で解説していますので、そちらを読み返してみてください。
[Step. 14(おまけ)] HTTP/Binary 通信を利用する方法
最後におまけで、CustomBinding を利用して、バイナリエンコード方式と HTTP 通信を組み合わせる方法についても紹介しておきます。
ASP.NET XML Web サービスや .NET Remoting に関してよく聞く要望の一つに、「HTTP 通信上で、バイナリエンコードされたメッセージ形式が利用できないか?」というものがありました。ASP.NET XML Web サービスは、SOAP over HTTP に最適化されて実装されている、つまりメッセージ形式は XML シリアル化された SOAP メッセージに限定されていたため、この要望にこたえることができませんでしたが、WCF の場合、CustomBinding を利用することで、バイナリエンコードされたメッセージを HTTP プロトコルでやり取りすることができるようになっています。
具体的には、以下のような作業を行います。
- 新しいバインド構成として、CustomBinding ベースのバインド構成を作成する。
- 名前を "HttpBinaryBinding" などに変更する。
この画面の下側に現れる「バインド要素の拡張の位置」というのは、これは簡単にいえば WCF パイプラインの内部に含まれるモジュール(これを BindingElement と呼びます)の一覧になります。通常、basicHttpBinding や netTcpBinding などを使う場合には、このモジュールセットは自動的に用意されるのですが、CustomBinding を使うと、この内部構成を自分で決定できるため、バイナリエンコード方式と HTTP プロトコルを組み合わせる、といったことも可能になります。
以上の作業により、サーバ側で HTTP 通信+バイナリエンコード方式を使った WCF サービスを作成することができたので、あとはクライアント側のサービス参照を更新して使ってみてください。(見た目には変わったことがわかりませんが) HTTP/Binary ベースで通信する WCF サービスが作成できます。(余力のある方はパケットキャプチャなどでみてみるとよいと思います。)
※ 最終的に完成したソリューションは以下になります。
[このエントリのまとめ]
というわけでいろいろ解説してきましたが、WCF を利用すると、以下のようなことが簡単にできるようになります。
- サービス参照を作成することで、WCF サービス呼び出しのためのプロキシクラスを簡単に作ることができる。
- 構成設定ファイルを書きかえることで、比較的簡単にログファイルを出力できる。
- サーバ側の構成設定を変更することで、同一通信特性を持つバインディングへの変更が容易にできる。
従来は非常に難しかった、TCP/IP 通信や名前付きパイプ通信を行うサーバアプリケーションも、比較的簡単に開発できることをご理解いただければ幸いです。そして、次回の Part.4 の WCF 最終回では、IIS 上でこの WCF を利用する方法について解説をしたいと思います。
……ふう、というわけで Part 3 書き終わりです。書き終わってから思いましたが、ロギングの話はちょっと余分だったかもしれませんね;。