传说中的WCF(8):玩转消息协定

Message翻译成中文,相信各位不陌生,是啊,就是消息,在WCF中也有消息这玩意儿,不知道你怎么去理解它。反正俺的理解,就像我们互发短信一个道理,通讯的双方就是服务器与客户端,说白了吧,就是二者之间的通信。

我们知道一个WCF服务,先是定义服务协定,而服务协定中会有若干个服务操作协定(OperationContract),是这样吧?而所谓的操作协定,就是一个方法。

于是,我的结论出来了,客户端与服务器端通信,每调用一回操作协定就相当于发送/接收一条消息,你干脆理解为一个OperationContract就是一条Message,哈,这样应该好接受了吧。

之前的文章中,我们吹了修改SOAP头相关的技术,而我们今天要说的Message其实也是序列化为SOAP的,反正这一切都和SOAP有关。可能 你会问,如果我不懂SOAP的具体知识,那我使用Message会遇到困难吗?明确Tell you,不会,你看下文我给各位准备的例子就知道了,我们不懂SOAP也可以耍Message的,就像我们不懂如果种水稻但也会做饭一个道理。

消息协定可以使用我们更灵活地封装自定义消息。既然我们可以把操作协定的一次调用看作是一条消息,那么,就可能出现下面三种情况:

a:只接收消息,但不进行回答;

b:只回复消息不接收输入消息;

c:既接收输入消息,同时发送回复消息。

先定义我们所需要的消息协定类。

    [MessageContract]
    public class CarMessage
    {
        [MessageBodyMember]
        public string CarName;
        [MessageBodyMember]
        public int MakeYear;
        [MessageBodyMember]
        public string SerType;
    }

    [MessageContract]
    public class Person
    {
        [MessageHeader]
        public string Zip { get; set; }
        [MessageHeader]
        public string Address;

        [MessageBodyMember]
        public int Age { get; set; }
        [MessageBodyMember]
        public string Name { get; set; }
        [MessageBodyMember]
        public string Email { get; set; }
    }

    #region 输入输出消息协定
    [MessageContract]
    public class RequrestMessage
    {
        [MessageHeader]
        public int maxNum;
        [MessageBodyMember]
        public string CheckName;
    }

    [MessageContract]
    public class ResponseMessage
    {
        [MessageBodyMember]
        public string Name;
        [MessageBodyMember]
        public int CheckResult;
    }
    #endregion

我们看到,消息协定的定义和数据协定很像,也是先写一个类,然后附加MessageContractAttribute,而对于类的成员(字段或属 性,不管是公共的还是私有的)可以附加MessageHeaderAttribute或MessageBodyMemberAttribute。

其实,MessageHeaderAttribute与MessageBodyMemberAttribute并没有根本的区别,只是一个是消息头,一个是消息正文罢了,这只是针对SOAP消息而言。

接着,定义服务协定和服务器类。

    [ServiceContract]
    public interface IService
    {
        [OperationContract]
        void PostMessage(CarMessage msg);

        [OperationContract]
        Person GetPerson();

        [OperationContract]
        ResponseMessage CheckRenpin(RequrestMessage rqmsg);
    }

    public class MyService : IService
    {
        public void PostMessage(CarMessage msg)
        {
            Console.WriteLine("车子名字:{0}", msg.CarName);
        }

        public Person GetPerson()
        {
            Person ps = new Person();
            ps.Name = "鸟人";
            ps.Age = 107;
            ps.Email = "nb@niube.com";
            ps.Zip = "990";
            ps.Address = "非洲";
            return ps;
        }
public ResponseMessage CheckRenpin(RequrestMessage rqmsg) { ResponseMessage respMsg = new ResponseMessage(); Random rand = new Random(); respMsg.CheckResult = rand.Next(rqmsg.maxNum); respMsg.Name = rqmsg.CheckName; return respMsg; } }

剩下的就是对服务主机的一些设置,这个每次都一样的了。

        static void Main(string[] args)
        {
            // 服务器基址
            Uri baseAddress = new Uri("http://localhost:1378/services");
            // 声明服务器主机
            using (ServiceHost host = new ServiceHost(typeof(MyService), baseAddress))
            {
                // 添加绑定和终结点
                WSHttpBinding binding = new WSHttpBinding();
                host.AddServiceEndpoint(typeof(IService), binding, "/test");
                // 添加服务描述
                host.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true });
                try
                {
                    // 打开服务
                    host.Open();
                    Console.WriteLine("服务已启动。");
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
                Console.ReadKey();
            }
        }

确认服务器端正常运行后,在客户端添加服务引用。
在客户端生成的代理类中,消息协定和数据协定并不一样了,服务的操作协定和服务器端我们定义的不一样了。

我们看到,在服务器端定义的消息协定类,在客户端代码中,类的成员都被拆开了。这样就得出这样一个结论:

作为操作协定的输入消息协定(作为参数)封装了操作方法的所有in参数;作为操作协定的返回值的消息协定(return)封装了out参数和返回值。

接下来,我们看看包含数据协定的消息协定的例子。

    #region 包含数据协定的消息协定
    [DataContract]
    public class ArtistInfo
    {
        [DataMember]
        public string ArtistName;
        [DataMember]
        public DateTime CreateTime;
    }

    [MessageContract]
    public class Worker
    {
        [MessageHeader]
        public ArtistInfo WorkerArtist;
        [MessageBodyMember]
        public string WorkerName;
        [MessageBodyMember]
        public string WorkerNo;
        [MessageBodyMember]
        public int WorkerAge;
    }
    #endregion

消息协定的类是Worker,但Worker的WorkerArtist字段是一个数据协定类ArtistInfo。

然后我们在服务协定上再加一个方法。

    [ServiceContract]
    public interface IService
    {
        ........
        [OperationContract]
        void SetWorkerInformation(Worker wk);
    }

    public class MyService : IService
    {
        .............
        public void SetWorkerInformation(Worker wk)
        {
            Console.WriteLine("工作名字:{0}",wk.WorkerName);
            ArtistInfo info = wk.WorkerArtist;
            Console.WriteLine("工人作品创建时间:{0}", info.CreateTime.ToString("yyyy-MM-dd HH:mm:ss"));
            Console.WriteLine("工人作品名字:{0}", info.ArtistName);
        }
    }

 现在,在客户端来测试一下SetWorkerInformation方法。

        static void Main(string[] args)
        {
            WS.ServiceClient cl = new WS.ServiceClient();

            WS.ArtistInfo info = new WS.ArtistInfo
            {
                ArtistName = "高级垃圾",
                CreateTime = new DateTime(2018, 7, 17)
            };
            cl.SetWorkerInformation(info, 180, "老妖", "NB-117");

            Console.ReadKey();
        }

由于方法返回void,因为在客户调用方法后,控制台窗口是一片空白,我们主要观察服务器端的控制台窗口是否输出了相关的内容。

这表明调用成功了。

对于消息协定什么时候使用,你看看吧,啥时候需要就毫不犹豫地用吧,这个显然要比Message类好用,毕竟Message类也有一些莫名的Bug,也不知道是不是我的bug。在流传输模式下使用消息协定来封装是不错的选择。

而对于消息头的消息正文,这个没有什么严格的规定的,不信你试试。一般的原则可以是,类似附加信息之类的可以用作头部,比较重要的信息作为正文。你不妨试试,无论你的成员定义为头部还是正文,在代码调用是看不什么根本区别。

原文地址:https://www.cnblogs.com/yuanli/p/3541712.html