netTcpBindingを使用してサービスコールバック内で、クライアントコールバックを呼び出すプログラムを作成します。http通信を使用する場合は、wsDualHttpBindingを使用します。クライアントコールバックのサンプルとは関係ありませんが、UIスレッド上でWCFサービスを開始、UIをもつクライアントのUIスレッドからサービスの呼び出しを行う場合は同期コンテキストの設定をデフォルト値trueからfalseに変更するのを忘れないで下さい。同期コンテキストを使用するか否かは、コールバック実装クラスの場合、CallbackBehaviorAttribute.UseSynchronizationContext を変更します。サービスクラスの場合、ServiceBehaviorAttribute.UseSynchronizationContext の値を変更します。

動作確認環境は次の通り

  • Windows Vista Enterprise(WCFサービス, クライアントともに同一OS上)
  • 開発環境 Visual Studio 2008 Professional (英語版)
  • .NET 3.5
  • バインディングnetTcpBinding

1.ソリューションの作成

Visual Studioを起動して、空のソリューションを新規作成します。ソリューション名はWCFSample016とします。

2.WCFサービスプロジェクトの作成

ソリューションを右クリックして、新規プロジェクトを作成します。プロジェクトテンプレートにWCF Service Libraryを選択肢、プロジェクト名をWCFSample.CallbackServiceとしてプロジェクトを作成します。自動で作成されるIService1.cs,Service1.cs,App.configを削除します。(App.configがあると、デバッグ実行時にVisual Studio によってサービスが自動でホストされます。)

2.1 プログラムの作成

プロジェクトを右クリック→Add→New Itemを選択肢、ファイルを新規作成します。Add New ItemダイアログでファイルタイプにWCF Serviceを選択肢、ファイル名をServiceWithCallbackとして作成します。

IServiceWithCallback.csを開いて次のように編集します。IServiceCallbackでコールバックのインタフェースを定義しています。ServiceContractAttribute.CallbackContractでサービスで使用するコールバックのインタフェースを指定します。

namespace WCFSample.CallbackService
{
    /// <summary>
    /// コールバックインターフェース
    /// </summary>
    public interface IServiceCallback
    {
        [OperationContract]
        void OnCallback(string msg);
    }

    [ServiceContract(CallbackContract=typeof(IServiceCallback))]
    public interface IServiceWithCallback
    {
        [OperationContract]
        void DoWork();
    }
}

ServiceWithCallback.csを開いてサービスクラスを次のように編集します。サービスメソッド内で、サービスを呼び出したクライアントのコールバックメソッドを呼び出す場合、ServiceBehaviorAttribute.ConcurrencyModeをConcurrencyMode.Reentrantにして下さい。この設定がないと、サービスを呼び出したクライアントにサービスメソッド内でコールバックメソッドを呼び出されたときに、クライアントのスレッドは最初のサービスメソッド呼び出しにより、ブロックされているので、例外が発生します。

namespace WCFSample.CallbackService
{
    [ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Reentrant)]
    public class ServiceWithCallback : IServiceWithCallback
    {
        public void DoWork()
        {
            IServiceCallback cb = OperationContext.Current.GetCallbackChannel<IServiceCallback>();
            if (((ICommunicationObject)cb).State == CommunicationState.Opened)
            {
                cb.OnCallback("呼び出されました");
            }
        }
    }
}

3 WCFホストの作成

WCFサンプル再作成(ブラッシュアップ) で作成したWCFSample.WPFHostを流用してWCFサービスをホストするプログラムを作成します。ので詳細はリンク元を参照して下さい。WCFSample.WPFHostをソリューションのフォルダにコピーし、既存のプロジェクトとして追加し、App.configを一度削除したあと、構成ファイルApp.configをプロジェクトに新規ファイルとして作成します。プロジェクトの参照にWCFSample.CallbackServiceを追加します。

3.1 構成ファイルの修正

App.configをWCF Service Configuration Editorで編集します。エディタのServiceの新規作成ウィザードにて次のように入力してサービスとエンドポイントを作成します。

  • ServiceTypeをWCFSample.CallbackService.ServiceWithCallback
  • ContractをWCFSample.CallbackService.IServiceWithCallback
  • 接続の種類をTCP
  • Addressをnet.tcp://localhost:8056/ServiceWithCallback

ウィザードの最後の確認ダイアログ以下の図のようになります。Finishを押してサービスを作成します。

 

 

 

構成ファイルは次のようになります。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <services>
            <service name="WCFSample.CallbackService.ServiceWithCallback">
                <endpoint address="net.tcp://localhost:8056/ServiceWithCallback"
                    binding="netTcpBinding" bindingConfiguration="" contract="WCFSample.CallbackService.IServiceWithCallback" />
            </service>
        </services>
    </system.serviceModel>
</configuration>

3.2 プログラムの修正

ProductServiceHost.xam.csを次のように修正します。変更点のみ表示します。

namespace WCFSample.WPFHost
{
    /// <summary>
    /// Interaction logic for ProductServiceHost.xaml
    /// </summary>
    public partial class ProductServiceHost : Window
    {
        ...
        private void btnStart_Click(object sender, RoutedEventArgs e)
        {
            _host = new ServiceHost(typeof(WCFSample.CallbackService.ServiceWithCallback));
            _host.Open();
            lblState.Content = "サービス中...";
            btnStart.IsEnabled = false;
            btnStop.IsEnabled = true;
        }
        ...
    }
}

以上でホスト側の修正は終わりです。スタートアッププロジェクトに設定して、動作することを確認します。

4 クライアントプロジェクトの作成

ソリューションを右クリックし、新規プロジェクトを追加します。プロジェクトのテンプレートにConsole Applicationを選択、プロジェクト名をWCFSample.ConsoleClientで新規作成します。プロジェクトの参照でSystem.ServiceModel.dllを追加します。

4.1 プロキシクラスの作成

ソリューションをビルドした後、WCFSample.CallbackService.dllのあるフォルダまで移動し、次のコマンドを入力してメタ情報ファイルを作成します。

svcutil WCFSample.CallbackService.dll

 

 

同じフォルダ上で、次のコマンドを入力し、プロキシクラスをファイル名Proxy.csで作成します。作成されたProxy.csをクライアントのプロジェクトに追加します。

svcutil /namespace:*,WCFSample.Client *.wsdl *.xsd /out:Proxy.cs

 

 

4.2 構成ファイルの修正

構成ファイルApp.configを新規作成し、編集します。今回は構成はデフォルトなので、手作業で次のようにエンドポイントの定義を作成しました。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <client>
            <endpoint address="net.tcp://localhost:8056/ServiceWithCallback"
                binding="netTcpBinding" bindingConfiguration="" contract="WCFSample.Client.IServiceWithCallback"
                name="ServiceWithCallback_NetTcpEndpoint" />
        </client>
    </system.serviceModel>
</configuration>

4.3 プログラムの修正

Program.csを開いて次のように編集します。CallbackClientがコールバック実装クラスです。Mainメソッドの先頭にあるInstanceContextを作成するときにコールバックするインスタンスとして引数に指定しています。DoWorkを呼び出したときにWCFサービスからCallbackClientのOnCallbackメソッドが呼び出されるようにしています。f

namespace WCFSample.ConsoleClient
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("サービスが起動したらEnter");
            Console.ReadLine();

            InstanceContext context = new InstanceContext(new CallbackClient());
            ServiceWithCallbackClient proxy = new ServiceWithCallbackClient(context, "ServiceWithCallback_NetTcpEndpoint");
            proxy.DoWork();
            proxy.Close();

            Console.WriteLine("サービスが終了しました。");
            Console.ReadLine();
        }
    }

    [CallbackBehavior(UseSynchronizationContext=false)]
    class CallbackClient : IServiceWithCallbackCallback
    {

        #region IServiceWithCallbackCallback Members
       
        public  void OnCallback(string msg)
        {
            Console.WriteLine(msg);
        }

        #endregion

    }
}

今回は必要ありませんが、CallbackClientに付与しているCallbackBehavior.UseSynchronizationContextをtrueにしています。ボタンクリックなどのUIイベント上のスレッドからサービスのDoWorkメソッドを呼び出す場合は、同期コンテキスト上のスレッド(UIスレッド)でコールバックが実行されないようにUseSynchronizationContextをfalseにしないと、スレッドがデッドロック状態になり停止します。WCFサービスにおける同期コンテキストについてはMSDNや以下のリンクを参照

WCFでの同期コンテキスト
http://msdn.microsoft.com/ja-jp/magazine/cc163321.aspx

 4.4 動作確認

ソリューションのプロパティからホストとクライアントをマルチプルスタートアッププロジェクトと設定してからデバッグ実行すると動作していることが確認できます。

 

 

 

説明は以上です。間違え等がありましたらご指摘くださいません。

今回作成されたプロジェクトファイルWCFSample016.zipです。DB接続がないので環境があれば動かせると思います。