Message翻译成中文,相信各位不陌生,是啊,就是消息,在WCF中也有消息这玩意儿,不知道你怎么去理解它。反正俺的理解,就像我们互发短信一个道理,通讯的双方就是服务器与客户端,说白了吧,就是二者之间的通信。
我们知道一个WCF服务,先是定义服务协定,而服务协定中会有若干个服务操作协定(OperationContract),是这样吧?而所谓的操作协定,就是一个方法。
于是,我的结论出来了,客户端与服务器端通信,每调用一回操作协定就相当于发送/接收一条消息,你干脆理解为一个OperationContract就是一条Message,哈,这样应该好接受了吧。
之前的文章中,我们吹了修改SOAP头相关的技术,而我们今天要说的Message其实也是序列化为SOAP的,反正这一切都和SOAP有关。可能 你会问,如果我不懂SOAP的具体知识,那我使用Message会遇到困难吗?明确Tell you,不会,你看下文我给各位准备的例子就知道了,我们不懂SOAP也可以耍Message的,就像我们不懂如果种水稻但也会做饭一个道理。
消息协定可以使用我们更灵活地封装自定义消息。既然我们可以把操作协定的一次调用看作是一条消息,那么,就可能出现下面三种情况:
a:只接收消息,但不进行回答;
b:只回复消息不接收输入消息;
c:有借有还,再借不难,既接收输入消息,同时发送回复消息。
先定义我们所需要的消息协定类。
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消息而言。
接着,定义服务协定和服务器类。
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;
}
}
剩下的就是对服务主机的一些设置,这个每次都一样的了。
{
// 服务器基址
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方法。
{
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。在流传输模式下使用消息协定来封装是不错的选择。
而对于消息头的消息正文,这个没有什么严格的规定的,不信你试试。一般的原则可以是,类似附加信息之类的可以用作头部,比较重要的信息作为正文。你不妨试试,无论你的成员定义为头部还是正文,在代码调用是看不什么根本区别。
转IT黄老邪