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。在流传输模式下使用消息协定来封装是不错的选择。
而对于消息头的消息正文,这个没有什么严格的规定的,不信你试试。一般的原则可以是,类似附加信息之类的可以用作头部,比较重要的信息作为正文。你不妨试试,无论你的成员定义为头部还是正文,在代码调用是看不什么根本区别。