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成员

Sample 代码
using System;
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:

Demo 代码
using System;
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来说就是负担,所以在项目设计的阶段就要充分考虑各种情况来平衡利弊。(完)

posted @ 2010-02-09 16:23  烟鬼  阅读(3513)  评论(1编辑  收藏  举报