WSDualHttpBinding を拡張してコンテキストを送信できるようにする

WSHttpContextBinding ではコールバックが使えません

コンテキスト付きバインディングでコールバックを使いたい場合、TCP なら NetTcpContextBinding を使うことで可能になります。それに対し HTTP にはコールバック可能なコンテキスト付バインディングが提供されていません(WSHttpContextBinding はコールバックに未対応)。

WSDualHttpBinding を拡張してしまえばいい

HTTP でコールバックを行うときに使うのが WSDualHttpBinding です。このクラスを拡張し、コンテキストも扱えるようにするのが手っ取り早そうです。NetTcpContextBinding も NetTcpBinding を拡張しているようですしね。

WSDualHttpBinding の BindingElement スタックに ContextBindingElement を追加します

WSDualHttpBinding を継承して新しいバインディングを作成。

public class WSDualHttpContextBinding : WSDualHttpBinding
{
    // BindingElement のスタックを組み上げるメソッドをオーバーライド
    public override BindingElementCollection CreateBindingElements()
    {
        var elements = base.CreateBindingElements();

        // 先頭にコンテキストを扱う BindingElement を挿入
        elements.Insert(0, new ContextBindingElement());

        return elements;
    }
}

Add メソッドを使って末尾に追加してはダメです。末尾には必ず TransportBindingElement が無いといけないので、実行時に例外が発生してしまいます。

コンテキストを扱えるか試してみます

class Program
{
    static void Main(string[] args)
    {
        string address = "http://localhost:3000/SampleService";

        ServiceHost host = new ServiceHost(typeof(SampleService));
        host.AddServiceEndpoint(typeof(ISampleService),
            new WSContextDualHttpBinding(),
            address);
        host.Open();

        InstanceContext callback = new InstanceContext(
            new CallbackHandler());
        ISampleService client = DuplexChannelFactory<ISampleService>.CreateChannel(
            callback,
            new WSContextDualHttpBinding(),
            new EndpointAddress(address));

        // コンテキストをセット
        IContextManager manager = ((IClientChannel)client).GetProperty<IContextManager>();
        IDictionary<string, string> context = manager.GetContext();
        context["Message"] = "Good by.";
        manager.SetContext(context);

        // サービス呼び出し
        client.Greeting("Bob");

        Console.ReadLine();

        ((IClientChannel)client).Close();
        host.Close();
    }
}

public interface ISampleServiceCallback
{
    [OperationContract(IsOneWay = true)]
    void Reply(string message);
}

[ServiceContract(CallbackContract = typeof(ISampleServiceCallback))]
public interface ISampleService
{
    [OperationContract(IsOneWay = true)]
    void Greeting(string name);
}

public class CallbackHandler : ISampleServiceCallback
{
    public void Reply(string message)
    {
        Console.WriteLine(message);
    }
}

public class SampleService : ISampleService
{
    public void Greeting(string name)
    {
        ISampleServiceCallback callback = OperationContext.Current
            .GetCallbackChannel<ISampleServiceCallback>();
        callback.Reply(string.Format("Hello, {0}!", name));

        // コンテキストを取り出す
        ContextMessageProperty props = OperationContext.Current.IncomingMessageProperties[ContextMessageProperty.Name] as ContextMessageProperty;
        string message = props.Context["Message"];
        callback.Reply(message);
    }
}

サンプルの実行結果がこちら。

f:id:griefworker:20090518121840p:image

ちゃんとコンテキストを渡せていますね。