いまさら、WCFで遊んでみる

だいぶ前に買って、ほったらかしていたエッセンシャルWCF:Windows Communication Foundation (Programmer’s Selection―Microsoft .net Development Series)を今更ながらに読み始めて、WCFでちょこちょこ遊んでみた。
個人的には、ローカルコンピュータ通信*1が気になってるので、その辺を中心にメモ。

とりあえず、どんなABCになるのか?

ローカルコンピュータ通信の場合、WCFのABCは大体以下のような感じじゃないかと。

Address

Adressは、ローカルコンピュータ通信の場合、何使っても大体行けるけど、名前付きパイプが、パフォーマンス的にも柔軟性も持ってるので、こいつを使うが大体の場合における最適解じゃ無いかと。
とした場合、Adressはnet.pipe://から始まる

Binding

バインディングは、有りモノを使う場合、netNamedPipeBindingになる。あんまり無いと思うけど、mexを公開する場合、そっちのバインディングはmexNamedPipeBindingになる。

Contract

これは、様々と言うことで。。。

VSデフォルトのConfigから変更する部分

何にも考えずに、WCFのサービスライブラリを作った場合、デフォルトでは、バインディングがwsHttpBindingになってる。ここを、netNamedPipeBindingへ変更して、
もし、mexを公開するなら、mexの方も、mexNamedPipeBindingに変更する。これだけで良いかというとちょっとまずい。
実際、これでデバッグ開始させて、WCFサービスホストを起ち上げると、エラーになるわけで。追加でどこを変更しなければならないかというと、ビヘイビアのserviceMetadataで、httpGetEnabledをfalseにしないとまずい。
さしあたり、Configをこのように変更することで、スケルトンで出来上がったサービスをWCFサービスホストに食わせることが可能になる。

セルフホストする場合

この手のものであれば、WASでどーこーと言うより、セルフホストする場面が多いかと思うので、セルフホストするときの手順。
ちなみに、このサンプルの場合、configは使わなかったりするw
実装として、ほめれたモンじゃないけど、さしあたりConsoleアプリでホスティングするときどうなるかというと、

  1. WCFのプロジェクトを参照する。
  2. System.ServiceModelを参照する。

が仕込み。プロジェクトを参照させただけじゃ、ホストできないので注意。
ここまでで来たら、次に、実際のコードを書いていくことになるわけで。
一番単純な方法は以下の通り。
ちなみに、以下のサンプルは、クライアントから受け取った文字列をホスト側コンソールに出力するだけの簡単なモノ

公開するメソッドをインターフェースで定義する
using System.ServiceModel;

namespace SampleServiceCore
{
	[ServiceContract]
	public interface ISampleService
	{
		[OperationContract(IsOneWay = true)]
		void SendMessage(string message);
	}
}

公開したいInterfaceにServiceContract属性をくっつけ、同様に、公開したい操作にOperationContract属性を付ける。

実装を書く
using System;

namespace SampleServiceCore
{
	public class SampleService : ISampleService
	{
		public void SendMessage(string message)
		{
			Console.WriteLine("Message receive:" + message);
		}
	}
}

ISampleServiceのInterfaceを持つクラスを作ればおっけー

ホスティングする
using System;
using System.ServiceModel;
using SampleServiceCore;

using System;
using System.ServiceModel;
using SampleServiceCore;

namespace Host
{
	class Program
	{
		static void Main(string[] args)
		{
			using (ServiceHost host 
				= new ServiceHost(typeof(SampleService), new Uri("net.pipe://sample")))
			{
				host.AddServiceEndpoint(typeof(ISampleService),
					new NetNamedPipeBinding(NetNamedPipeSecurityMode.None), string.Empty);

				host.Open();

				Console.WriteLine("press enter to exit...");
				Console.ReadLine();
			}
		}
	}
}

今回は、mexを公開しない方向で。
ここで注意するのは、ServiceHostのコンストラクタ。
2つのオーバーロードがあるけど、今回使ったのは、サービスを実装しているクラスのTypeを渡す方。
もう一方のObjectを渡す方は、シングルトンで使うコトが出来るけど、その場合、SampleServiceに仕込みを入れる必要がある。また、仕込みを入れないで、自分でインスタンスを先に作成して、Ojbectを渡す方を使った場合、実行時エラーになるので注意。

クライアントを作って実際に呼び出してみる

で、最後は、クライアントの作成。
クライアントアプリケーションにも、ホストと同様に、件のInterfaceとその実装が入ってるアセンブリと、System.ServiceModelを参照させる。
そこまでしたら、あと一息、実装は以下

using System.ServiceModel;
using SampleServiceCore;

namespace Client
{
	class Program
	{
		static void Main(string[] args)
		{
			using (ChannelFactory<ISampleService> factory
				= new ChannelFactory<ISampleService>
					(new NetNamedPipeBinding(NetNamedPipeSecurityMode.None), "net.pipe://sample"))
			{

				var client = factory.CreateChannel();

				client.SendMessage("Hello WCF!");

				var ret = client.GetData(100);
			}
		}
	}
}

ここまで言ったら、コンパイルして、ホストプロセスを起ち上げ、その後に、クライアントプロセスを起動すれば、ホスト側コンソールに、
”Hello WCF!”が出てくるハズ。

注意すべきトコロ

当たり前だけど、Host側エンドポイントをClientで正確に記述する必要がある。
また、NetNamedPipeBindigのコンストラクタで、セキュリティモードを指定できるけど、それもホスト/クライアントで一致させないとまずい。

次回は、同期呼び出しの戻り値を持つ操作とプリミティブ意外のデータをやりとりする方法当たりを書いてみようかと思います。

*1:プロセス間通信と言ってしまうと、割と語弊がある。この辺WindowsのProcessってモノと、ドトネトのAppDomainが1:1で対応していないため。