• 传说中的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。在流传输模式下使用消息协定来封装是不错的选择。

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

  • 相关阅读:
    [JavaScript-PHP]无刷新Ajax+POST使用阿里云短信平台发送短信
    [PHP]开源php拼音库的使用方法
    redis远程连接不上解决办法
    ServiceStack.Redis连接阿里云redis服务时使用连接池出现的(密码验证)问题
    .Net使用Redis详解之ServiceStack.Redis
    Windows下Redis的使用
    axios文件流下载(excel文件)
    生成线上用https证书,支持通配符和多域名,初学Let's Encrypt用于IIS,纯本地手动
    什么是TXT记录?如何设置、检测TXT记录
    v-cloak 的用法
  • 原文地址:https://www.cnblogs.com/GoogleGetZ/p/5752347.html
Copyright © 2020-2023  润新知