WCF开发之IExtensibleDataObject
由于数据契约成员的移除导致在发送-回传(Round Trip)过程中数据的丢失问题。如图5-9所示,客户端基于数据契约CustomerV1进行服务调用,而服务的实现却是基于CustomerV2的。那么序列化的CustomerV1对象生成的XML通过消息传到服务端,服务端会按照CustomerV2进行反序列化,毫无疑问Address的数据会被丢弃。如果Customer的信息需要返回到客户端,服务需要对CustomerV2对象进行序列化,则序列化生成的XML肯定已无Address数据成员存在。当回复消息返回到客户端时,客户端按照CustomerV1进行反序列化生成CustomerV1对象,会发现原本赋了值的Address属性现在变成null了。对于客户端来说,这是一件很奇怪、也是不可接受的事情:"为何数据经过发送-回传后会无缘无故丢失呢?"
为了解决这类问题,WCF定义了一个特殊的接口System.Runtime.Serialization. IExtensibleDataObject,IExtensibleDataObject中仅仅定义了一个ExtensionDataObject类型属性成员。对于实现了IExtensibleDataObject的数据契约,DataContractSerializer在进行序列化时会将ExtensionData属性的值也序列化到XML中;在反序列化过程中,如果发现XML包含有数据契约中没有的数据,会将多余的数据进行反序列化,并将其放入ExtensionData属性中保存起来,由此解决数据丢失的问题。
它的作用是有以下几点:
• 在序列化数据契约的时候,保存未知的元素
• 在反序列化的时候,多余的数据将被放入到一个字典中
• 在序列化过程中,相同的数据按照其原始提供的样子以XML的形式写入
它的使用方法如下:
• 在适当的数据契约上实现IExtensibleDataObject
• 提供一个ExtensionData成员
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
namespace ContentTypes
{
[DataContract(Name="Person", Namespace="http://www.cnblogs.com/Charlesliu")]
public class Person : IExtensibleDataObject
{
private ExtensionDataObject extensionData;
public ExtensionDataObject ExtensionData
{
get
{
return extensionData;
}
set
{
extensionData = value;
}
}
[DataMember]
public string Name;
}
}
下面来看一个具体的Demo:
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.IO;
using System.Xml;
namespace ConsoleApplicationTest
{
// Implement the IExtensibleDataObject interface
// to store the extra data for future versions.
[DataContract(Name = "Person", Namespace = "http://www.cnblogs.com/Charlesliu")]
class Person : IExtensibleDataObject
{
// To implement the IExtensibleDataObject interface,
// you must implement the ExtensionData property. The property
// holds data from future versions of the class for backward
// compatibility.
private ExtensionDataObject extensionDataObject_value;
public ExtensionDataObject ExtensionData
{
get
{
return extensionDataObject_value;
}
set
{
extensionDataObject_value = value;
}
}
[DataMember]
public string Name;
}
// The second version of the class adds a new field. The field's
// data is stored in the ExtensionDataObject field of
// the first version (Person). You must also set the Name property
// of the DataContractAttribute to match the first version.
// If necessary, also set the Namespace property so that the
// name of the contracts is the same.
[DataContract(Name = "Person", Namespace = "http://www.cnblogs.com/Charlesliu")]
class PersonVersion2 : IExtensibleDataObject
{
// Best practice: add an Order number to new members.
[DataMember(Order = 2)]
public int ID;
[DataMember]
public string Name;
private ExtensionDataObject extensionDataObject_value;
public ExtensionDataObject ExtensionData
{
get
{
return extensionDataObject_value;
}
set
{
extensionDataObject_value = value;
}
}
}
public class Program
{
static void Main(string[] args)
{
string path = @"e:\PersonV2.xml";
try
{
if (File.Exists(path))
{
File.Delete(path);
}
WriteVersion2(path);
Console.ReadLine();
WriteToVersion1(path);
Console.ReadLine();
ReadVersion2(path);
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
// Create an instance of the version 2.0 class. It has
// extra data (ID field) that version 1.0 does
// not understand.
static void WriteVersion2(string path)
{
Console.WriteLine("Creating a version 2 object");
PersonVersion2 p2 = new PersonVersion2();
p2.Name = "Charles";
p2.ID = 2010;
Console.WriteLine("Object data includes an ID");
Console.WriteLine("\t Name: {0}", p2.Name);
Console.WriteLine("\t ID: {0} \n", p2.ID);
// Create an instance of the DataContractSerializer.
DataContractSerializer ser =new DataContractSerializer(typeof(PersonVersion2));
Console.WriteLine("Serializing the v2 object to a file. \n\n");
FileStream fs = new FileStream(path, FileMode.Create);
ser.WriteObject(fs, p2);
fs.Close();
}
// Deserialize version 2 data to a version 1 object.
static void WriteToVersion1(string path)
{
// Create the serializer using the version 1 type.
DataContractSerializer ser = new DataContractSerializer(typeof(Person));
FileStream fs = new FileStream(path, FileMode.Open);
XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(fs, new XmlDictionaryReaderQuotas());
Console.WriteLine("Deserializing v2 data to v1 object. \n\n");
Person p1 = (Person)ser.ReadObject(reader, false);
Console.WriteLine("V1 data has only a Name field.");
Console.WriteLine("But the v2 data is stored in the ");
Console.WriteLine("ExtensionData property. \n\n");
Console.WriteLine("\t Name: {0} \n", p1.Name);
fs.Close();
// Change data in the object.
p1.Name = "John";
Console.WriteLine("Changed the Name value to 'John' ");
Console.Write("and reserializing the object to version 2 \n\n");
// Reserialize the object.
fs = new FileStream(path, FileMode.Create);
ser.WriteObject(fs, p1);
fs.Close();
}
// Deserialize a version 2.0 object.
public static void ReadVersion2(string path)
{
FileStream fs = new FileStream(path, FileMode.Open);
DataContractSerializer ser = new DataContractSerializer(typeof(PersonVersion2));
Console.WriteLine("Deserializing new data to version 2 \n\n");
PersonVersion2 p2 = (PersonVersion2)ser.ReadObject(fs);
fs.Close();
Console.WriteLine("The data includes the old ID field value. \n");
Console.WriteLine("\t (New) Name: {0}", p2.Name);
Console.WriteLine("\t ID: {0} \n", p2.ID);
}
}
}
上面的代码的意思是先用V2的Person序列化到xml里,然后反序列化V2的Person的xml,给V1的Person赋值,然后再把V1的Person序列化到xml里,(按找道理来说P1是没有ID属性的,那么序列化后应该丢失),然后再反序列化V1的Person的xml给V2的Person,输出V2的Person后发现ID属性的数据仍然存在,说明ID是保存在ExtensionData中了。注意,Person和PseronV2这两个数据契约成员的Name和NameSpace是一样的,也就是说WCF会把他当成同一个契约成员来处理,当版本升级时很有可能发生这样的事情。所以,ExtensionData的作用就是防止数据丢失和保存冗余数据。
在使用中应该注意:
• 客户端代理缺省对其进行了实现:
– 在1.0版本的客户端上保存从2.0版本的服务端所获得的数据
– 确保在更新时能够包含原始的数据
• 服务端可以对其进行实现:
– 保存从客户端获得的未知数据
– 用于支持其他调用了2.0版本的服务但仍需要1.0版本的服务进行实际处理的情况
– 会承担拒绝服务攻击和非必要的使用服务器资源的风险
关闭IExtensibleDataObject
• 能够禁止对IExtensibleDataObject的支持
• 可配置服务的行为
有两种方法关闭:
配置方式(当然是这个好些,不用编译code)
<serviceBehaviors>
<behavior name="serviceBehavior">
<dataContractSerializer ignoreExtensionDataObject="true"/>
</behavior>
</serviceBehaviors>
代码方式
[ServiceBehavior(IgnoreExtensionDataObject=true))]
public class ContentManagerService :
IContentManagerService
{
}
由于使用了ExtensionData为了不丢失数据,有可能增加数据的传输量,所以在使用时还要平衡他的利弊具体情况具体分析。举个例子吧:
一个客户端有可能同时需要访问俩个Service来处理问题,那么如果访问Sevice1需要传输1KB,访问Service2需要传输1MB。如果开启了ExtensionData,那么访问Service1的时候会同时把1KB+1MB的数据同时发给Service1,这对于Service1来说就是负担,所以在项目设计的阶段就要充分考虑各种情况来平衡利弊。(完)