wsDualHttpBindingを使用したクライアントコールバックのサンプルを掲載します。wsDualHttpBindingをwsHttpBindingと異なり、トランスポートセキュリティを使用できない,信頼できるセッションが必ず使用されるなどの違いがあります。WCF Sample 016:netTcpBindingを使用してクライアントコールバックを実装する の内容をwsDualHttpBindingで行った場合にどのように記述するかを書いています。プログラムなどの説明が不足している場合はこちらも参照して下さい。

動作環境は次の通り

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

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

Visual Studioを起動してからのソリューションWCFSample021を作成します。

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

ソリューションエクスプローラからソリューションを右クリック→[Add]→[New Project]をクリック。表示されるダイアログのプロジェクトテンプレートで、WCF Service Libraryテンプレートを選択し、WCFSample.Servicという名前でプロジェクト作成します。作成時に自動生成されるApp.config,IService.cs,Service.csは削除します。

2.1 コールバックコントラクト,サービスコントラクト,サービスクラスの作成

コールバックコントラクトを作成します。プロジェクトを右クリック→[Add]→[New Item]をクリック。表示されるダイアログのファイルのタイプにInterfaceを選択し、IServiceCallback.csというファイルを作成し、次のように編集します。

namespace WCFSample.Service
{
    interface IServiceCallback
    {
        [OperationContract(IsOneWay=true)]
        void OnCallback(string msg);
    }
}

続いて、サービスコントラクト,サービスクラスを作成します。ソリューションエクスプローラからプロジェクトを右クリック→[Add]→[New Item]をクリックしてAdd New Itemダイアログを表示します。ファイルのタイプにWCF Serviceを選択し、名前をCallbackServiceと入力して[Add]ボタンをクリックしてファイルを追加します。作成されたICallbackService.cs,CallbackService.csを編集します。

// ICallbackService.csの中身
namespace WCFSample.Service
{
    [ServiceContract(CallbackContract=typeof(IServiceCallback))]
    public interface ICallbackService
    {
        [OperationContract(IsOneWay=true)]
        void DoWork();
    }
}
/////////////////////////////
// CallbackService.csの中身
namespace WCFSample.Service
{
    [ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession, ConcurrencyMode=ConcurrencyMode.Single)]
    public class CallbackService : ICallbackService
    {
        public void DoWork()
        {
            IServiceCallback cb = OperationContext.Current.GetCallbackChannel<IServiceCallback>();

            if (((ICommunicationObject)cb).State == CommunicationState.Opened)
            {
                cb.OnCallback("呼び出されました");
            }
        }
    }
}

サービスクラスはコールバックApp.configが作成されている場合は削除しておきます。

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

WCFサンプル再作成(ブラッシュアップ) で作成したプログラムを修正します(ソリューションはリンク先からダウンロードできます)。ソリューションを右クリックし、既存のプロジェクトの追加で、WCFSample.WPFHostプロジェクトを追加します。追加されたプロジェクトのReferencesを展開し、WCFSample.ProductServiceを削除します。次に、プロジェクトを右クリック→[Add Reference]でWCFSample.Serviceプロジェクトを参照に追加します。App.configを下記のように空にします。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
</configuration>

App.configを右クリック→[Edit WCF Configuration]をクリックし、Service Configuration Editorを起動します。

ConfigurationペインのServicesを選択し、右側ServicesペインのCreate a New Serviceをクリックしてウィザードを開始します。ウィザードでは表示されるダイアログで次のように設定します。

  • Service type : WCFSample.Service.CallbackService
  • Contract : WCFSample.Service.ICallbackService
  • What communication mode is your service using? : HTTP
  • What method of insteroperablility do you want to use : Advanced Web Services interoperabilityを選択,Duplex communicationを選択
  • Address : http://localhost:8056/CallbackService

ウィザードの結果最後に表示されるダイアログは次の通りになります。

 

 

保存されたApp.configは次のようになります。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <services>
            <service name="WCFSample.Service.CallbackService">
                <endpoint address="http://localhost:8056/CallbackService" binding="wsDualHttpBinding"
                    bindingConfiguration="" contract="WCFSample.Service.ICallbackService" />
            </service>
        </services>
    </system.serviceModel>
</configuration>

ProductServiceHost.csも下記のように修正します。(修正部分のみ掲載します)

public partial class ProductServiceHost : Window
    {
        ...
        private void btnStart_Click(object sender, RoutedEventArgs e)
        {
            _host = new ServiceHost(typeof(WCFSample.Service.CallbackService));
            _host.Open();
            lblState.Content = "サービス中...";
            btnStart.IsEnabled = false;
            btnStop.IsEnabled = true;
        }
        ...
    }

Vista環境の場合,httpプロトコルでポートを開く場合にアクセス許可が必要です。Administrator権限でコマンドプロンプトを起動し、httpプロトコルでポートを開くためのアクセス許可を割り当てておきます。

>netsh http add urlacl url=http://+:8056/CallbackService user=マシン名\ユーザ名

そのほかのOSで使用している場合にアクセス権がないためエラーが発生する場合は以下のページを参照してください。
-Configuring HTTP and HTTPS
http://msdn2.microsoft.com/en-us/library/ms733768.aspx
http://msdn.microsoft.com/ja-jp/library/ms733768.aspx  (日本語)

ホストプロジェクトをスタートアッププロジェクトに設定し、動作することを確認します。

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

ソリューションエクスプローラからソリューションを右クリック→[Add]→[New Project]でダイアログを表示する。Console ApplicationをWCFSample.Clientという名前で作成します。出力されたProxy.csをプロジェクトに追加します。

4.1 プロキシクラスの作成

WCFサービルライブラリプロジェクトで作成されるWCFSample.Service.dllを使用して、プロキシを作成します。Visual Studio 2008 Command Promptを起動して、コマンドを入力します。メタデータを作成

>svcutil WCFSample.Service.dll

 

 

メタデータからプロキシクラスを作成します。

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

 

 

作成されたProxy.csをクライアントプロジェクトに追加します。

4.2 App.configの編集

プロジェクトを右クリック→[Add]→[New Item]をクリック。Add New ItemダイアログでApplication Configuration FileをTemplatesとしてApp.configファイルを作成します。App.configを右クリックしてService Configuration Editorを起動します。今回はWCFホストプロジェクトのApp.configからウィザードを使用してコンフィギュレーションを作成してみます。コンフィギュレーションエディタひ左ペインからクライアントをクリックし、右側ペインのClientからCreate New Clientをクリックしてウィザードを開始します。

表示されるダイアログでFrom service configを選択し、WCFホストプロジェクトのApp.configを入力し[Next]ボタンをクリックします。

 

 

次に表示されるダイアログで作成したエンドポイント(今回のサンプルでは1つしかありません)を選択し[Next]ボタンをクリック。

 

 

次に表示されるダイアログで、バインディング名を入力して[Next]ボタンをクリックします。

 

 

ウィザードの確認ダイアログが表示されるので内容を確認して、[Finish]ボタンをクリックします。

 

 

 

保存後、App.configは次のようになりました。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <client>
            <endpoint address="http://localhost:8056/CallbackService" binding="wsDualHttpBinding"
                bindingConfiguration="" contract="WCFSample.Service.ICallbackService"
                name="WSDualHttp_ClientBinding">
                <identity>
                    <certificateReference storeName="My" storeLocation="LocalMachine"
                        x509FindType="FindBySubjectDistinguishedName" />
                </identity>
            </endpoint>
        </client>
    </system.serviceModel>
</configuration>

このままだと証明書を使用してしまうので、identityを削除して次のように編集します。bindingsを追加し、双方向通信時にサービスからクライアントの呼び出しに使用するアドレスとしてclientBaseAddressを追加しています。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <client>
            <endpoint address="http://localhost:8056/CallbackService" binding="wsDualHttpBinding"
                bindingConfiguration="clientBehavior" contract="WCFSample.Client.ICallbackService"
                name="WSDualHttp_ClientBinding">
            </endpoint>
        </client>
      <bindings>
        <wsDualHttpBinding>
          <binding name="clientBehavior" clientBaseAddress="http://localhost:8060/CallbackServiceClient/">
          </binding>
        </wsDualHttpBinding>
      </bindings>
    </system.serviceModel>
</configuration>

クライアントでもhttpポートを開く許可が必要です。次のように割り当てておきます。 管理者権限でコマンドプロンプトを起動し次のように入力します。
> netsh http add urlacl url=http://+:8060/CallbackServiceClient/  user=ドメイン名orマシン名/アクセスを許可するユーザ名

4.3 Program.csの編集

参照の追加でSystem.ServiceModel.dllを追加し,Program.csを次のように編集します。binding.ClientBaseAddressをApp.configのアドレス+GUIDとするようにプログラム上で変更しています。これは、同じOS上で複数のクライアントが起動したときにエラーが発生しないようにするためです。

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

            InstanceContext context = new InstanceContext(new CallbackClient());
            CallbackServiceClient proxy = new CallbackServiceClient(context, "WSDualHttp_ClientBinding");
            WSDualHttpBinding binding = (WSDualHttpBinding) proxy.Endpoint.Binding;
            binding.ClientBaseAddress = new Uri(binding.ClientBaseAddress, Guid.NewGuid().ToString());
            proxy.DoWork();

            Console.WriteLine("コールバックが完了するまで待機");
            Console.ReadLine();
            proxy.Close();

            Console.WriteLine("サービスが終了しました。");
            Console.ReadLine();
        }
    }
    [CallbackBehavior(UseSynchronizationContext = false)]
    class CallbackClient : ICallbackServiceCallback
    {

        #region IServiceWithCallbackCallback Members

        public void OnCallback(string msg)
        {
            Console.WriteLine(msg);
        }

        #endregion

    }
}

サービス側でhttpポートを開く権限が必要なのと同じように、クライアントでもhttpポートを開く権限が必要です。管理者権限で開いたコマンドプロンプトで次のように入力して、許可をするようにします。

App.configやプログラム内でclientBaseAddressを指定しなかった場合
デバッグ実行して調べたところ、clientBaseAddressが無視低の場合、クライアントのアドレスとして、http://【ローカルマシーン名】:80/Temporary_Listen_Address/【GUID】が使用されるようです。【】で囲まれた部分は環境により変わります。IISが動作しているので例外が発生すると思ったのですが、とくに問題なく動きました。というか同じマシン上のWCFクライアントはポートが同じでもUriが異なれば同じポートが使えるんですがこれってVistaだけ?。(理由を知っていたら教えてくださいm(_ _)m。)

また、http://【ローカルマシーン】/Temporary_Listen_Address/はアクセス許可が既定で割り当てられているようです。コマンドプロンプト上で次のコマンドで確認できます(| moreは見安さのために追加しています)。

>netsh http show urlacl | more