在设计数据实体(Entity)时,某些属性必须是只读的,比如 CreateTime、LastLogonTime 等。但 DataContract 并不允许我们这么做。
public class Data
{
private int x;
public Data()
{
x = new Random(DateTime.Now.Millisecond).Next(10000);
}
[DataMember]
public int X
{
get { return x; }
}
}
使用上面这个 DataContract,你能得到的只能是下面这个的结果。
Message="No set method for property 'X' in type 'Data'."
Source="System.Runtime.Serialization"
改成 XmlSerializer 序列化引擎会怎么样呢?
{
private int x;
public Data()
{
x = new Random(DateTime.Now.Millisecond).Next(10000);
}
public int X
{
get { return x; }
}
}
[ServiceContract, XmlSerializerFormat]
public interface IService
{
[OperationContract]
void Test(Data d);
}
编译能通过,看看生成的客户端代理是什么样子?
{
[NonSerializedAttribute()]
private ExtensionDataObject extensionDataField;
public ExtensionDataObject ExtensionData
{
get
{
return this.extensionDataField;
}
set
{
this.extensionDataField = value;
}
}
}
很糟糕,属性 X "丢失"。 在 MSDN 上有这样一句话,"XML 序列化不转换方法、索引器、私有字段或只读属性(只读集合除外)"。
接下来,我们改用 "Serializable",结果会如何呢?
public class Data
{
private int x;
public Data()
{
x = new Random(DateTime.Now.Millisecond).Next(10000);
}
public int X
{
get { return x; }
}
}
[ServiceContract]
public interface IService
{
[OperationContract]
void Test(Data d);
}
这次生成的代理倒是有 Data.X,只可惜不是只读的。
{
[NonSerializedAttribute()]
private ExtensionDataObject extensionDataField;
private int xField;
public ExtensionDataObject ExtensionData
{
get
{
return this.extensionDataField;
}
set
{
this.extensionDataField = value;
}
}
[DataMemberAttribute(IsRequired = true)]
public int x
{
get
{
return this.xField;
}
set
{
this.xField = value;
}
}
}
看来,生成客户端代理文件这条路走不通了。我们必须把开发模式转回到 ChannelFactory ,也就是说共享元数据。即便是这样,DataContract 依然行不通,只好再次试试 XmlSerializer。
{
private int x;
public Data()
{
Console.WriteLine("Constructor:{0}", x);
x = new Random(DateTime.Now.Millisecond).Next(10000);
}
public int X
{
get { return x; }
}
}
[ServiceContract, XmlSerializerFormat]
public interface IService
{
[OperationContract]
void Test(Data d);
}
public class Service : IService
{
[OperationBehavior]
public void Test(Data d)
{
Console.WriteLine("Server:{0}", d.X);
}
}
public class WcfTest
{
public static void Test()
{
AppDomain.CreateDomain("Server").DoCallBack(delegate
{
ServiceHost host = new ServiceHost(typeof(Service), new Uri("http://localhost:8080"));
host.AddServiceEndpoint(typeof(IService), new BasicHttpBinding(), "");
host.Open();
});
IService client = ChannelFactory<IService>.CreateChannel(new BasicHttpBinding(),
new EndpointAddress("http://localhost:8080"));
using (client as IDisposable)
{
Data d = new Data();
Console.WriteLine("Client:{0}", d.X);
client.Test(d);
}
}
}
输出结果:
Constructor:0
Client:2819
Constructor:0
Server:1939
通过输出结果,我们会发现 XmlSerializer 引擎在反序列化时会再次调用构造方法。通过消息跟踪,我们得到的结果会更失望。因为 d.X 的值根本没有传递到服务器。 原因就是上面我们所提到 MSDN 里面的那句话,XmlSerializer 是不会 "搭理" 只读属性的。
<s:Envelope xmlns:s="...">
<s:Header>
<Action s:mustUnderstand="1" xmlns="...">http://tempuri.org/IService/Test</Action>
</s:Header>
<s:Body xmlns:xsi="..." xmlns:xsd="...">
<Test xmlns="http://tempuri.org/">
<d></d>
</Test>
</s:Body>
</s:Envelope>
</MessageLogTraceRecord>
最后一条路,就是 Serializable。
public class Data
{
private int x;
public Data()
{
Console.WriteLine("Constructor:{0}", x);
x = new Random(DateTime.Now.Millisecond).Next(10000);
}
public int X
{
get { return x; }
}
}
[ServiceContract]
public interface IService
{
[OperationContract]
void Test(Data d);
}
public class Service : IService
{
[OperationBehavior]
public void Test(Data d)
{
Console.WriteLine("Server:{0}", d.X);
}
}
public class WcfTest
{
public static void Test()
{
AppDomain.CreateDomain("Server").DoCallBack(delegate
{
ServiceHost host = new ServiceHost(typeof(Service), new Uri("http://localhost:8080"));
host.AddServiceEndpoint(typeof(IService), new BasicHttpBinding(), "");
host.Open();
});
IService client = ChannelFactory<IService>.CreateChannel(new BasicHttpBinding(),
new EndpointAddress("http://localhost:8080"));
using (client as IDisposable)
{
Data d = new Data();
Console.WriteLine("Client:{0}", d.X);
client.Test(d);
}
}
}
输出:
Constructor:0
Client:7644
Server:7644
反序列化时没有再次调用构造方法,消息跟踪结果也正常。
<s:Envelope xmlns:s="...">
<s:Header>
<Action s:mustUnderstand="1" xmlns="...">http://tempuri.org/IService/Test</Action>
</s:Header>
<s:Body>
<Test xmlns="http://tempuri.org/">
<d xmlns:d4p1="..." xmlns:i="...">
<d4p1:x>7644</d4p1:x>
</d>
</Test>
</s:Body>
</s:Envelope>
</MessageLogTraceRecord>