WCF之消息
在WCF中客户端与服务端都是靠消息进行传输的,消息格式和SOAP一样,具有消息标头和正文。客户端和服务端之间的所有通讯最终都会产生System.ServiceModel.Channels. Message
实例。在平时操作中,很少直接操作Message类型,而是通过WCF服务模型构造(如数据契约、消息契约和操作契约)来描述传入消息和传出消息。所以,要想透彻的理解WCF有必要对Message多些了解。下面看一个简单的类,来了解一下Message用到的类。
using System;
using System.ServiceModel;
using System.Runtime.Serialization;
using System.IO;
using System.Xml;
using System.Text;
using System.ServiceModel.Channels;
namespace WcfMessage
{
[DataContract(Namespace="http://www.cnblogs.com/qiuwuyu")]
public class Fruit
{
private string m_Name = string.Empty;
private string m_Price = string.Empty;
public Fruit(string name, string price)
{
m_Name = name;
m_Price = price;
}
[DataMember]
public string Name
{
get{ return m_Name; }
set{ m_Name = value; }
}
[DataMember]
public string Price
{
get { return m_Price; }
set { m_Price = value; }
}
}
[DataContract(Namespace = "http://www.cnblogs.com/qiuwuyu")]
public class FruitHeader
{
private string m_HeaderKey = string.Empty;
[DataMember]
public string HeaderKey
{
get { return m_HeaderKey; }
set { m_HeaderKey = value; }
}
public FruitHeader(string key)
{
m_HeaderKey=key;
}
}
class Program
{
static void Main(string[] args)
{
Fruit fruit = new Fruit("banana", "6.00");
//把Fruit类型序列化为xml文件
using (FileStream fs = new FileStream("Fruit.xml", FileMode.Create))
{
DataContractSerializer dataSer = new DataContractSerializer(typeof(Fruit));
dataSer.WriteObject(fs, fruit);
}
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load("Fruit.xml");
//用二进制编码的xml文档
using (FileStream binStream = new FileStream("Fruit.bin", FileMode.Create))
{
using (XmlWriter xw = XmlDictionaryWriter.CreateBinaryWriter(binStream))
{
xmlDoc.WriteContentTo(xw);
}
}
//用mtom编码的xml文档
using (FileStream mtomStream = new FileStream("Fruit.mtom", FileMode.Create))
{
using (XmlWriter xw = XmlDictionaryWriter.CreateMtomWriter(mtomStream, Encoding.UTF8, 1024, "text/xml"))
{
xmlDoc.WriteTo(xw);
}
}
//创建一消息,并用文本编码的消息
Message message1 = Message.CreateMessage(MessageVersion.Soap11WSAddressingAugust2004, "*", fruit);
MessageHeader mHeader = MessageHeader.CreateHeader("FruitHeader", "http://www.cnblogs.com/qiuwuyu",
new FruitHeader("password"));
message1.Headers.Add(mHeader);
using (FileStream stream = new FileStream("FruitMessageHeader.xml", FileMode.Create))
{
using (XmlWriter xw = XmlDictionaryWriter.CreateTextWriter(stream))
{
message1.WriteMessage(xw);
}
}
//创建一个没有消息版本,并用文本编码的消息
Message message = Message.CreateMessage(MessageVersion.None, "*", new XmlNodeReader(xmlDoc));
using (FileStream stream = new FileStream("FruitMessageNone.xml", FileMode.Create))
{
using (XmlWriter xw = XmlDictionaryWriter.CreateTextWriter(stream))
{
message.WriteMessage(xw);
}
}
}
}
}
XmlDictionary是定义了一个私有的可以表示SOAP消息中元素名字、属性和XML namespace声明的key-value列表的字典结构。字典在常见文本字符串和整数之间建立映射,并为压缩和解压缩XML提供一种有效机制。XmlDictionaryWriter是WCF中一个优化的读取XML编写器。
执行上述代码后,查看生成文件。
Fruit.xml文件:
二进制编码的Fruit.xml文件:
MTOM编码的Fruit.xml文件:
文本编码的消息:
无消息版本信息本文本编码的消息:
下面看一个直接用Message写的小实例,从整体上感觉下。
服务契约:
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using FruitModel;
namespace IFruitService
{
[ServiceContract(Namespace = "http://www.cnblogs.com/qiuwuyu")]
public interface IFruitPriceService
{
[OperationContract(Action="*",ReplyAction="*")]
Message GetFruit(Message m);
}
}
Action指定为“*”表示可以处理服务端接收到的任何消息。如果没有指定Action值,则默认为服务约定命名空间/约定名称(默认为接口名称)/操作名称(默认为方法名称)。
ReplyAction为“*”表示不向消息中添加答复操作。如果没有指定Action值,则默认为服务约定命名空间/约定名称(默认为接口名称)/操作名称(默认为方法名称)Response。
在客户端应用程序中指定星号可指示 不验证答复操作。服务实现:
using System;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using IFruitService;
using FruitModel;
namespace FruitPriceService
{
public class FruitPriceService : IFruitPriceService
{
public Message GetFruit(Message m)
{
if ( OperationContext.Current.IncomingMessageHeaders.GetHeader<FruitHeader>(0).HeaderKey!="password" )
{
FaultCode faultCode = new FaultCode("Invalid Key");
return Message.CreateMessage(m.Version, faultCode, "Invalid Key", "*");
}
return Message.CreateMessage(m.Version, "*", new Fruit("banana", "6.00"));
}
}
}寄存服务:
客户端调用:using System;
using System.ServiceModel;
using IFruitService;
namespace WcfMessageHost
{
class Program
{
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(typeof(FruitPriceService.FruitPriceService),
new Uri("http://localhost:8000/Fruit")))
{
host.AddServiceEndpoint(typeof(IFruitPriceService), new BasicHttpBinding(), "FruitService");
host.Open();
Console.WriteLine("Fruit Service Is Running...");
Console.ReadLine();
}
}
}
}传入正确的HeaderKey的执行结果:using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Runtime.Serialization;
using IFruitService;
using FruitModel;
namespace WcfMessageClient
{
class Program
{
static void Main(string[] args)
{
EndpointAddress epAddr = new EndpointAddress("http://localhost:8000/Fruit/FruitService");
IFruitPriceService proxy = ChannelFactory<IFruitPriceService>.CreateChannel(new BasicHttpBinding(), epAddr);
MessageHeader mHeader = MessageHeader.CreateHeader("FruitHeader", " http://www.cnblogs.com/qiuwuyu",
new FruitHeader("password"));
Message message = Message.CreateMessage(MessageVersion.Soap11, "*",string.Empty);
message.Headers.Add(mHeader);
Message m = proxy.GetFruit(message);
if (m.IsFault)
{
Console.WriteLine(m.ToString());
}
else
{
Fruit fruit = m.GetBody<Fruit>();
Console.WriteLine("Name:" + fruit.Name + " Price:" + fruit.Price);
}
Console.WriteLine("Fruit Client Is Running...");
Console.ReadLine();
}
}
}
传入错误的HeaderKey的执行结果:
如果HeaderKey不正确可以返回一个FaultMessage,如下 FaultCode faultCode = new FaultCode("InvalidKey"); return Message.CreateMessage(m.Version,faultCode, "Invalid Key", "*"); 在客户端可以用IsFault判断是否是FaultMessage并进行一些相应的逻辑处理。