WCF のコンテキスト付バインディングで任意のオブジェクトを送信する方法

WCF には NetTcpContextBinding や WsHttpContextBinding といった、コンテキスト付バインディグが提供されています。コンテキスト付バインディングを使えば、クライアントからサービスに、任意のメタ情報を送信できます。

ただし、コンテキストとして指定できるのは文字列だけなので、使い勝手が悪いです。

そこで、先日紹介した「DataContractSerializer を使ってシリアル化・逆シリアル化を行うサンプル」の出番。

この方法を利用すれば、コンテキストに任意のオブジェクトを設定できそう。

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Text;
using System.Xml;
namespace SerializeSample
{
    class Program
    {
        [DataContract]
        public class Person
        {
            [DataMember]
            public string Name { get; set; }
            [DataMember]
            public int Age { get; set; }
        }

        [ServiceContract]
        public interface IGreetingService
        {
            [OperationContract]
            string Greet(); 
        }

        public class GreetingService : IGreetingService
        {
            public string Greet()
            {
                // コンテキストから Person を取り出す
                Person p = GetContext<Person>("Person");
                return string.Format("Hello, {0}!", p.Name);
            }
        }

        // コンテキストにオブジェクトを追加
        public static void SetContext<T>(IClientChannel channel, string key, T obj)
        {
            IContextManager manager = channel.GetProperty<IContextManager>();
            IDictionary<string, string> context = manager.GetContext();
            context.Add(key, Serialize(obj));
            manager.SetContext(context);
        }

        // コンテキストからオブジェクトを取り出す
        public static T GetContext<T>(string key)
        {
            ContextMessageProperty properties = (ContextMessageProperty)OperationContext.Current
                .IncomingMessageProperties[ContextMessageProperty.Name];
            return Deserialize<T>(properties.Context[key]);
        }

        // DataContractSerializer を使ってシリアル化
        public static string Serialize<T>(T obj)
        {
            StringBuilder buffer = new StringBuilder();
            using (XmlWriter writer = XmlWriter.Create(buffer))
            {
                DataContractSerializer serializer = new DataContractSerializer(typeof(T));
                serializer.WriteObject(writer, obj);
            }
            return buffer.ToString();
        }

        // DataContractSerializer を使って逆シリアル化
        public static T Deserialize<T>(string s)
        {
            using (StringReader text = new StringReader(s))
            using (XmlReader reader = XmlReader.Create(text))
            {
                DataContractSerializer serializer = new DataContractSerializer(typeof(T));
                return (T)serializer.ReadObject(reader);
            }
        }

        static void Main(string[] args)
        {
            // サービス起動
            string address = "net.tcp://localhost:8080/Greeting";
            ServiceHost host = new ServiceHost(typeof(GreetingService));
            host.AddServiceEndpoint(typeof(IGreetingService),
                new NetTcpContextBinding(),
                address);
            host.Open();

            // サービスを呼び出すためのチャネル生成
            IGreetingService proxy = ChannelFactory<IGreetingService>.CreateChannel(
                new NetTcpContextBinding(),
                new EndpointAddress(address));

            // コンテキストをセット
            Person p = new Person()
            {
                Name = "Ichiro",
                Age = 35
            };
            SetContext((IClientChannel)proxy, "Person", p);

            // サービス呼び出し。
            // サービス側では、コンテキストから Person を取り出す。
            string result = proxy.Greet();

            // 結果を出力
            Console.WriteLine(result);
            Console.ReadLine();
        }
    }
}

f:id:griefworker:20090917101250p:image

結構いいんじゃない?