WCF 服务方法通过接收消息、处理消息、回复消息来完成调用。所有的数据都被序列化,并转换成消息后再行传输,接收方执行相反的动作获得数据对象。
消息描述
最简单就是消息描述就是方法参数,所有的基本类型可以直接被序列化。我们还可以使用 MessageParameterAttribute 为参数定义消息名称。
public interface IContract
{
[OperationContract]
double Add(double a, double b);
[OperationContract]
void Test([MessageParameter(Name="myString")]string s);
}
对于自定义类型,我们可以使用 DataContractAttribute 或 MessageContractAttribute 来定义,这些在前面的章节已经提过,此处不再做进一步说明。
WCF 服务方法支持 ref / out 关键字,也就是说底层引擎会重新为添加了关键字的对象赋予返回值。我们使用 "Message Logging" 和 "Service Trace Viewer" 查看一下 Reply Message。
Server.cs
public interface IContract
{
[OperationContract]
double Add(double a, ref double b);
}
public class MyService : IContract
{
public double Add(double a, ref double b)
{
b += 2;
return a + b;
}
}
client.cs
new EndpointAddress("http://localhost:8080/myservice")))
{
double b = 2;
double c = client.Add(1, ref b);
Console.WriteLine("c={0};b={1}", c, b);
}
Reply Message
<s:Envelope xmlns:s="http://...">
<s:Header>
<Action s:mustUnderstand="1" xmlns="http://...">http://tempuri.org/IContract/AddResponse</Action>
</s:Header>
<s:Body>
<AddResponse xmlns="http://tempuri.org/">
<AddResult>5</AddResult>
<b>4</b>
</AddResponse>
</s:Body>
</s:Envelope>
</MessageLogTraceRecord>
在 Reply Message 中除了返回值 "AddResult" 外,还有 "b"。:-)
需要注意的是,即便我们使用引用类型的参数,由于 WCF 采取序列化传送,因此它是一种 "值传递",而不是我们习惯的 "引用传递"。看看下面的例子,注意方法参数和数据结果的不同。
Server.cs
public class Data
{
[DataMember]
public int I;
}
[ServiceContract]
public interface IContract
{
[OperationContract]
void Add(Data d);
[OperationContract]
void Add2(ref Data d);
}
public class MyService : IContract
{
public void Add(Data d)
{
d.I += 10;
}
public void Add2(ref Data d)
{
d.I += 10;
}
}
Client.cs
new EndpointAddress("http://localhost:8080/myservice")))
{
Data d = new Data();
d.I = 1;
client.Add(d);
Console.WriteLine("d.I={0}", d.I);
Data d2 = new Data();
d2.I = 1;
client.Add2(ref d2);
Console.WriteLine("d2.I={0}", d2.I);
}
输出:
d.I=1
d2.I=11
序列化引擎
缺 省情况下,WCF 使用 DataContractSerializer 引擎对相关参数进行序列化,这也是 WCF 推荐的方式。另外一个选择是 XmlSerializer,也就是 ASP.NET Web Service 所使用的序列化引擎。XmlSerializer 仅支持 DataContractSerializer 所支持的部分类型,但它允许你使用 XmlAttributeAttribute 等特性对序列化生成的 XML 进行更多的控制。
DataContractSerializer 支持的类型:
- 支持所有的基本类型,还包括 XmlElement 和 DateTime 这样的常用类型。
- 支持使用 DataContractAttribute 标记的类型。
- 支持使用 SerializableAttribute 标记或者实现 ISerializable 接口的类型。
- 实现 IXmlSerializable 接口的类型。
- 大多数集合(含泛型)类型,包括常用的 Array、List、IList 等。
通 过向服务添加 XmlSerializerFormatAttribute 标记,就可以手动切换到 XmlSerializer 引擎。此时使用自定义类型,无须使用 SerializableAttribute、ISerializable、DataContractAttribute 等特性或接口。
{
public int I = 1234;
public string X = "Hello, World!";
}
[ServiceContract, XmlSerializerFormat]
public interface IContract
{
[OperationContract]
void Test(Data d);
}
public class MyService : IContract
{
public void Test(Data d)
{
Console.WriteLine("i={0}; x={1}", d.I, d.X);
}
}
public class WcfTest
{
public static void Test()
{
AppDomain.CreateDomain("Server").DoCallBack(delegate
{
ServiceHost host = new ServiceHost(typeof(MyService));
host.AddServiceEndpoint(typeof(IContract), new BasicHttpBinding(),
"http://localhost:8080/myservice");
host.Open();
});
ChannelFactory<IContract> factory = new ChannelFactory<IContract>(new BasicHttpBinding(),
"http://localhost:8080/myservice");
IContract client = factory.CreateChannel();
Data d = new Data();
d.I = 123456;
d.X = "China";
client.Test(d);
}
}
我们为自定义类型添加控制标记,看看 Request Message 的变化。
{
[System.Xml.Serialization.XmlAttribute]
public int I = 1234;
[System.Xml.Serialization.XmlElement]
public string X = "Hello, World!";
}
Request Message
<s:Envelope xmlns:s="http://.../">
<s:Header>
<Action s:mustUnderstand="1" xmlns="http://...">http://tempuri.org/IContract/Test</Action>
</s:Header>
<s:Body xmlns:xsi="http://..." xmlns:xsd="http://...">
<Test xmlns="http://tempuri.org/">
<d I="123456">
<X>China</X>
</d>
</Test>
</s:Body>
</s:Envelope>
</MessageLogTraceRecord>
需要注意一个区别,我们 DataContractSerializer 和 DataContractAttribute 的时候,只有标记了 DataMemberAttribute 的成员被序列化,而 XmlSerializer 是序列化全部 "可视" 成员。
有关 WCF 序列化的更多细节,以后再写相关的研究专题。本文只是作为初次学习的一个简要记录。