Focus on biztalk -- chnking

心无旁骛,专注于biztalk......

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

1、 DataContractSerializer支持的类型.................................................................................................................. 2

1.1. 用[DataContract]属性标记的类型........................................................................................................................ 2

1.2. .net 原生类型............................................................................................................................................................... 2

1.3. 用[Serializable]属性标记的类型........................................................................................................................... 2

1.4. 枚举类型.......................................................................................................................................................................... 3

1.5. 呈现为xml的类型....................................................................................................................................................... 3

1.6. 集合类型.......................................................................................................................................................................... 3

2、 定义需要使用DataContractSerializer序列化的类....................................................................................... 3

2.1. DataContract属性的参数........................................................................................................................................ 3

2.2. DataMember属性的参数........................................................................................................................................ 4

2.3. [DataContract]属性标识的类型跟XmlSerializer序列化器的类型的不同............................................ 4

2.4. 准备待序列化的用 DataContract标识类............................................................................................................ 5

2.5. 准备待序列化的用DataContract标识类型的对象........................................................................................... 7

3、 使用DataContractSerializer类不同的方法序列化用DataContract标识类型的对象................... 7

3.1. 使用WriteObject(Stream stream, object graph)方法序列化............................................................. 8

3.2. 使用WriteObject(XmlDictionaryWriter writer, object graph)方法序列化................................ 10

3.2.1. CreateTextWriter方法..................................................................................................................................... 10

3.2.2. CreateMtomWriter方法................................................................................................................................. 13

3.2.3. CreateBinaryWriter方法................................................................................................................................ 18

3.3. DataContractSerializer序列化总结................................................................................................................ 19

 

 

WCF中,DataContractSerializer是默认的序列化器,不过WCF中还有一个叫NetDataContractSerializer的序列化器,它跟DataContractSerializer一样也是从XmlObjectSerializer类继承。NetDataContractSerializerDataContractSerializer一个主要的不同是:NetDataContractSerializer序列化后的xml中包含了.net的类型信息,反序列化时必须要被反序列化为同样类型的对象,这点跟BinaryFormatterSoapFormatter这两个序列化器类似。DataContractSerializer序列化后的xml中则不包含.net的类型信息,通用性和交互性更好。在实际应用中DataContractSerializerWCF的默认序列化器,绝大多数情况下都是使用DataContractSerializer,下面我们只对DataContractSerializer做详细介绍。


      本文完整测试源代码下载: DataContractSerializer.rar 

一般情况下WCF的基础结构(infrastructure)会调用DataContractSerializer对要传输的数据对象就行序列化,不过也有特殊情况,WCF会调用别的序列化器序列化数据对象。

 

当遇到数据类成员中标记有类似[XmlAttribute]属性这样的细致设置生成xml格式的属性时,这时WCF infrastructure需要调用XmlSerializer序列化器来序列化这样的数据对象,通过在一个方法前加上[XmlSerializerFormat]属性值是WCF infrastructure调用XmlSerializer序列化器。

[XmlSerializerFormat]属性可以标记一个方法,表示这个方法使用XmlSerializer序列化器。

[XmlSerializerFormat]属性也可以用来标记一个ServiceContract,表示整个ServiceContract都用XmlSerializer序列化器。

比如:

[ServiceContract]

public interface IAirfareQuoteService

{

    [OperationContract]

    [XmlSerializerFormat]

    float GetAirfare(Itinerary itinerary, DateTime date);

}

public class Itinerary

{

    public string fromCity;

    public string toCity;

    [XmlAttribute]

    public bool isFirstClass;

}

 

1、 DataContractSerializer支持的类型

DataContractSerializer支持绝大多数的可序列化的类型的序列化,具体包括:

1.1.   [DataContract]属性标记的类型

这种类型是为DataContractSerializer定制的可序列化类型,DataContractSerializer对这种了类型提供最全面的支持。

1.2.   .net 原生类型

.net内建的一些简单的数据类型,比如:Byte, SByte, Int16,string等,还有一些不是原生类型但是可以视作原声类型的类型,比如:DateTime, TimeSpan, Guid, Uri

1.3.   [Serializable]属性标记的类型

这是为rometing准备的序列化类型,被DataContractSerializer完全支持。

使用DataContractSerializer序列化前面SoapFormatter序列化的那个例子得到的结果:

clip_image002

把这个序列化后的结果跟直接使用SoapFormatter序列化的结果比较一下有以下几点不同:

l  DataContractSerializer序列化是面向数据的,不保留对象之间的引用关系,所以虽然address1address2指向的是同一个对象,但DataContractSerializer还是分别给他们赋了同样的值。

l  不再包含.net的类型相关的信息,类的全限定名、版本、区域、KeyToken等等。

1.4.   枚举类型

1.5.   呈现为xml的类型

类型本身就可以呈现为xml的类型,比如XmlElement ,XmlNode,或者ADO.NET相关的类型,比如DataTable , DataSet。这些类型序列化时会被直接以xml的形式呈现。

1.6.   集合类型

可被序列化的类型组成的集合类型也被支持。

 

2、 定义需要使用DataContractSerializer序列化的类

自定义一个类或结构,这个类或结构:

首先,在类前面加上[DataContract],表示这个类包含有数据契约

其次,在需要对外暴露的成员用[DataMember]属性标记。在这个类中,只有被标记为[DataMember]的成员才会被序列化,不管成员是公有的还是私有的。

 

2.1.   DataContract属性的参数

[DataContract]可以用来描述一个class或者struct,可以带有两个参数:

l  Name

指定这个数据契约对外的类型名,如果没有指定此参数,缺省的就是类名。

l  Namespace

指定这个类被序列化为xml后的Root元素的名称空间,为URI形式。

如果没有指定此参数,缺省名称空间为:“http://schemas.datacontract.org/2004/07/这个类的.net类名称空间”

比如有这样一个标记为DataContract的类型:

namespace DataContractSerializerTest.Myspace

{

    [DataContract]

    class Person

{

    //代码

}

}

这个类型的默认名称空间就是http://schemas.datacontract.org/2004/07/ DataContractSerializerTest.Myspace

 

2.2.   DataMember属性的参数

[DataMember]可以用来描述Propertyfield,不管这个成员是public或者private,只要标记了[DataMember],这个成员就会被序列化。

[DataMember]属性可以带有以下几个参数:

l  Name

指定这个成员对外暴露的名称,如果没有指定此参数,缺省的就是此Propertyfield名。

l  Isrequired

这个参数告诉DataContractSerializer序列化器把xml数据反序列化为这个类型时,这个成员是不是必须有的。此属性在反序列化时起作用。

l  Order

指示这个成员被序列化后的顺序。此属性在序列化时起作用。如果在一个类中既存在用Order参数约束的成员,又有没用Order参数约束的成员,那么序列化后没有Order参数约束的成员排在Order参数约束的成员之前。

l  EmitDefaultValue

此参数告诉DataContractSerializer序列化器,在序列化时如果这个数据成员的值是这个成员类型的缺省值(比如成员是int类型,缺省值为0,如果成员是个引用类型,缺省值为null),是否将此成员序列化。=true时表示要序列化,=false时表示不序列化,此参数的默认值为true

当引用类型的成员为null时,此参数设置为true时,这个成员被序列化后的xml表现为xsi:nil="true",比如:

<Name xsi:nil="true" />

 

2.3.   [DataContract]属性标识的类型跟XmlSerializer序列化器的类型的不同

这两种类型都是面向数据的,都不关心数据的.net类型信息。但是他们还是有所不同:

l  DataContract的类型只能把用DataMember标识的成员序列化成element形式,不能序列化为attribute,如果一定要把数据成员序列化为attribute形式就需要用使用XmlSerializer序列化器。

l  DataMember属性可以通过order参数指定此成员序列化后的出现顺序,用于XmlSerializer序列化器的XmlElement属性无此功能。

l  DataContract的类型只要在成员前加上DataMember标记,不管成员是publicprivate的,都会被序列化,XmlSerializer序列化器只序列化public的成员。

l  DataContract的类型的成员的名称空间跟整个类的一致,成员不能独立设置自己的名称空间(DataMember属性没有名称空间参数),XmlSerializer序列化器允许成员拥有自己的名称空间(XmlAttributXmlElement都有namespage参数设置成员独立的名称空间)。

 

2.4.   准备待序列化的用 DataContract标识类

设计一个的Person类型,其中包含五个成员,binaryBuffer1binaryBuffer2nameaddressage,其中nameaddress又分别是DataContract标识的简单类型。

binaryBuffer1 binaryBuffer2byte[]类型的数据,表示二进制的数据,可能包含任何非二进制的对象,比如图片、声音等等。这里放两个二进制的数据为了测试不同长度的二进制数据在DataContractSerializer不同的方法中会被如何处理。

 

[DataContract(Namespace = "htttp://chnking")]

class Person

{

    private Name name;

    private Address address;

    private int age;

 

    [DataMember]

    public byte[] binaryBuffer1;

    [DataMember]

    public byte[] binaryBuffer2;

 

    public Person(Name name, Address address, int age)

    {

        this.name = name;

        this.address = address;

        this.age = age;

    }

    [DataMember(Order = 2)]

    public int Age

    {

        get { return age; }

        set { age = value; }

    }

    [DataMember(EmitDefaultValue = true, Order = 0)]

    public Name Name

    {

        get { return name; }

        set { name = value; }

    }

 

    [DataMember(Order = 1)]

    public Address Address

    {

        get { return address; }

        set { address = value; }

    }

}

 

[DataContract(Namespace = "htttp://chnking")]

class Name

{

    private string firstname;

    private string lastname;

    public Name(string firstname, string lastname)

    {

        this.firstname = firstname;

        this.lastname = lastname;

    }

    [DataMember]

    public string Lastname

    {

        get { return lastname; }

        set { lastname = value; }

    }

    [DataMember]

    public string Firstname

    {

        get { return firstname; }

        set { firstname = value; }

    }

 

}

[DataContract(Namespace = "htttp://chnking")]

class Address

{

    private string city;

    private string postcode;

    private string street;

    public Address(string city, string postcode, string street)

    {

        this.city = city;

        this.postcode = postcode;

        this.street = street;

    }

    [DataMember]

    public string Street

    {

        get { return street; }

        set { street = value; }

    }

    [DataMember]

    public string Postcode

    {

        get { return postcode; }

        set { postcode = value; }

    }

    [DataMember]

    public string City

    {

        get { return city; }

        set { city = value; }

    }

}

 

2.5.   准备待序列化的用DataContract标识类型的对象

实例化一个Person的对象,后续步骤将要序列化这个对象。

注意,这里给第一个二进制数据赋了一个20字节长数据,第二个二进制数据赋了一个768字节长度的数据,为什么这么赋值,后面会有说明。

 

//构造一个需要序列化的DataContract的对象

Person person = new Person(new Name("比尔", "盖茨"),

    new Address("shenzhen", "518000", "fuqiang road"),

    40);

 

//用一个重复的值填充第一个二进制数据bufferbytes1

int size1 = 20;

byte[] bufferbytes1 = new byte[size1];

for (int i = 0; i < size1; i++)

{

    //65表示ASCII码的A

    bufferbytes1[i] = 65;

}

person.binaryBuffer1 = bufferbytes1;

 

//用一个重复的值填充第二个二进制数据bufferbytes2

int size2 = 768;

byte[] bufferbytes2 = new byte[size2];

for (int i = 0; i < size2; i++)

{

    //66表示ASCII码的B

    bufferbytes2[i] = 66;

}

person.binaryBuffer2 = bufferbytes2;

 

3、 使用DataContractSerializer类不同的方法序列化用DataContract标识类型的对象

实例化一个DataContractSerializer序列化器,DataContractSerializer序列化器跟XmlSerializer序列化器一样,没有缺省构造方法,至少需要提供要序列化对象的类型参数。

//准备DataContractSerializer序列化器

DataContractSerializer dcs = new DataContractSerializer(typeof(Person));

DataContractSerializer序列化对象的方法是WriteObject,这个WriteObject主要有两个重载,WriteObject(Stream stream, object graph)WriteObject(XmlDictionaryWriter writer, object graph)

下面对这两种序列化方法分别讨论。

 

3.1.   使用WriteObject(Stream stream, object graph)方法序列化

这个方法跟XmlSerializer序列化器的Serialize方法类似,是把序列化的对象的xml字符串直接编码序列化为字节流,而且跟XmlSerializer一样,从字符串到字节流的使用UTF8进行编码。

 

Stream(dcs, person);

private static void Stream(DataContractSerializer dcs, Person person)

{

    //构造用于保存序列化后的DataContract对象的流

    MemoryStream contractDataStream = new MemoryStream();

    //DataContract对象序列化到流

    dcs.WriteObject(contractDataStream, person);

 

    //为了查看序列化到流中的内容,将流内容读取出来并用UTF8解码为字符串

    byte[] contractDataByte = new byte[contractDataStream.Length];

    contractDataStream.Position = 0;

    contractDataStream.Read(contractDataByte, 0, (int)contractDataStream.Length);

    string contractDataString = Encoding.UTF8.GetString(contractDataByte);

 

    //将流反序列化为DataContract对象

    contractDataStream.Position = 0;

    person = (Person)dcs.ReadObject(contractDataStream);

}

这部分代码跟XmlSerializer部分的类似,不加更多的说明了。

序列化后的xml这样的:

clip_image004

 

WriteObject(Stream stream, object graph)方法序列化有以下特点:

l  DataContractSerializer首先把对象序列化为一个xml 形式的字符串。

l  对于其中的二进制数据,不管多大长度,一律转成base64编码的字符串。本例中bufferbytes2被设置为768字节长度,序列化结果是一串base64的编码,又测试了把bufferbytes2长度增加为7680个字节,序列化的结果仍然是一串更长的base64的编码。

l  DataContractSerializer然后把序列化后的xml形式的字符串以UTF8编码(这是默认的编码,并且没有提供可以使用别的编码的接口,所以对其解码也必须使用UTF8,这点跟XmlSerializerSoapFormatter序列化器一样)对其进行编码,转换到字节流,最终保存到一个流对象中,本例中就是MemoryStream。可以通过对字节流进行UTF8解码得到xml的字符串,就是上图看到的结果。

 

反序列化后的Person对象是这样的:

clip_image006

 

3.2.   使用WriteObject(XmlDictionaryWriter writer, object graph)方法序列化

这个方法是DataContractSerializer序列化器把对象序列化为xml字符串后,不直接对xml进行编码转换成字节流,而是把xml写入到XmlDictionaryWriterXmlDictionaryWriter提供了多种对xml进行编码的选择,最终再由XmlDictionaryWriter把编码后的xml写入到目标流中。

XmlDictionaryWriter提供了三类静态方法,构造三种不同类型的XmlDictionaryWriter,分别对xml进行不同的编码操作。

它们分别是:

3.2.1.   CreateTextWriter方法

CreateTextWriter方法构建的XmlDictionaryWriter写入器,这样的写入器WriteObject(Stream stream, object graph)方法的作用类似,也是直接把xml的内容直接编码为字节流,但是允许选择编码时使用哪种编码。

XmlDictionaryWriterCreateTextWriter方法主要下面两种重载形式。

public static XmlDictionaryWriter CreateTextWriter (Stream stream)

public static XmlDictionaryWriter CreateTextWriter (Stream stream, Encoding encoding)

第一个重载方法没有Encoding参数,默认采用UTF8进行编码,这样第一个方法的作用其实跟WriteObject(Stream stream, object graph)方法一样。

第二个重载方法有Encoding参数,可以指定在编码时采用何种编码,是Unicode还是UTF8等。下面的示例代码使用第二个重载方法,指定UTF8编码。

XmlDictionaryCreateTextWriter(dcs, person);

private static void XmlDictionaryCreateTextWriter(DataContractSerializer dcs, Person person)

{

    //构造用于保存序列化并编码后的DataContract对象的流

    MemoryStream contractDataStream = new MemoryStream();

    //新建一个CreateTextWriter方法构建的指定UnicodeXmlDictionaryWriter对象

    XmlDictionaryWriter xdw = XmlDictionaryWriter.CreateTextWriter(contractDataStream, Encoding.UTF8);

    //调用WriteObject方法将对象通过XmlDictionaryWriter序列化并编码

    dcs.WriteObject(xdw, person);

    //将序列化并编码后的字节流写入到原始流对象

    xdw.Flush();

    contractDataStream.Position = 0;

 

    //为了查看序列化到流中的内容,将流内容读取出来并用Unicode解码为字符串

    byte[] contractDataByte = new byte[contractDataStream.Length];

    contractDataStream.Read(contractDataByte, 0, (int)contractDataStream.Length);

    string contractDataString = Encoding.UTF8.GetString(contractDataByte);

 

    //将流反序列化为DataContract对象

    contractDataStream.Position = 0;

    XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(contractDataStream, Encoding.UTF8, new XmlDictionaryReaderQuotas(), new OnXmlDictionaryReaderClose(OnReaderClose));

    person = (Person)dcs.ReadObject(reader);

    reader.Close();

}

public static void OnReaderClose(XmlDictionaryReader reader)

{

    return;

}

 

序列化后,contractDataStream中获得的序列化后的数据长度为1385字节。

contractDataStream中的字节流用UTF8解码,得到的内容跟前面的WriteObject(Stream stream, object graph)方法序列化后的结果一模一样:

clip_image008

 

再用Encoding.Unicode 编码测试,序列化后,contractDataStream中获得的序列化后的数据长度为2756字节,明显比前面使用UTF8编码得到的结果长的多。这是很正常的结果,因为一般ASCII可表示的字符,使用UTF8编码也是用一个字节表示,并跟ASCII编码兼容,而UTF-16编码会把所有字符都编码为2个字节,所以使用Encoding.Unicode编码后的结果会大很多,几乎是UTF2倍(因为测试的内容字符绝大多数是ASCII可表示的字符)

 

contractDataStream中的字节流用Encoding.Unicode解码,查看内容为,解码后的内容跟UTF8解码后的内容也是一致的:

clip_image010

 

实际测试CreateTextWriter方法中的Encoding参数只能为:Encoding.UnicodeEncoding.UTF8Encoding.BigEndianUnicode 三种。

 

CreateTextWriter方法构建的XmlDictionaryWriter写入器有以下特点:

l  DataContractSerializer首先把对象序列化为xml的各个元素写入到XmlDictionaryWriter对象。

l  XmlDictionaryWriter把内容最后写入到基础流的时候,对于二进制数据类型的元素,不管多大长度,一律转成base64编码的字符串。对于字符串类类型的元素以指定的编码对其进行编码,转换到字节流,最终保存到一个流对象中,本例中就是MemoryStream

 

序列化时,使用XmlDictionaryWriterCreateTextWriter方法建立的XmlDictionaryWriter对象作为写入器,反序列化时,相应的就要用XmlDictionaryReaderCreateTextReader建立的XmlDictionaryReader对象来作为读取器。

CreateTextReader方法签名如下:

CreateTextReader(Stream stream, Encoding encoding, XmlDictionaryReaderQuotas quotas, OnXmlDictionaryReaderClose onClose);

参数说明:

stream – 保存序列化后数据的流对象。

encoding – 反序列化时使用的编码,应该跟序列化时使用的编码一致。

quotas – 指定XmlDictionaryReader对象的一些极限值,比如可以读取的stream的最长长度等等。XmlDictionaryReaderQuotas的默认构造方法对这些极限值构造了一个比较安全的缺省值,比如MaxArrayLength = 16384,可以反序列化数据的最长长度。当序列化后的stream中的字节长度长于MaxArrayLength设定的值,反序列化时将会抛出异常。

onClose – delegate类型,指定一个方法,在XmlDictionaryReader关闭时触发

 

3.2.2.   CreateMtomWriter方法

前面CreateTextWriter方法构造的XmlDictionaryWriter写入器,处理二进制数据的方式是把二进制数据转成base64编码的字符串,这个处理方法在一般情况下是个不错的方案,但是base64编码会增加编码后的长度,在二进制数据比较长的情况下,base64编码带来的长度增加将不能忽视。

Base64编码将每3个字节的数据拆散重组为4字节可表示简单字符的数据,编码后的数据长度比为34,如果数据长度不长,增加的长度影响不大,但是如果原来的二进制数据就比较大,比如300K,编码后的数据将会是400K,再如果原来是30M,编码后为40M,多出来的数据量难以忽视。

鉴于这种情况,W3C制定了XOPXML-binary Optimized PackagesXOP)协议来解决二进制问题,XOP 提供一个机制,可选择性地提取要优化的信息,将其添加到多部分 MIME 消息中(其中也包括您的 SOAP 消息)并显式地对其进行引用。使用 XOP 的过程称为 MTOMSOAP 消息传输优化机制——Message Transmission Optimization Mechanism)。

经过MTOM处理的数据,形式上为MIME(邮件的内容形式),XML 数据组成第一部分,而二进制数据视其长度的不同或者被编码成base64放在xml中,或者作为附加部分添加到xml部分的后面。

 

CreateMtomWriter用来构造XmlDictionaryWriter最常用的方法重载是:

CreateMtomWriter(Stream stream, Encoding encoding, int maxSizeInBytes, string startInfo);

参数说明:

stream – 保存序列化后数据的流对象。

encoding – 序列化时使用的编码。

maxSizeInBytes – 将被缓冲到XmlDictionaryWriter写入器的数据最大字节数

startInfo – 在生成的MIME消息的ContentType增加一个名称为start-infoheaderheader的值就是这个参数提供的。

 

看一下用CreateMtomWriter构造的XmlDictionaryWriter序列化前面例子的代码:

 

XmlDictionaryCreateMtomWriter(dcs, person);

private static void XmlDictionaryCreateMtomWriter(DataContractSerializer dcs, Person person)

{

    //构造用于保存序列化并编码后的DataContract对象的流

    MemoryStream contractDataStream = new MemoryStream();

    //新建一个CreateMtomWriter方法构建的指定UnicodeXmlDictionaryWriter对象

    XmlDictionaryWriter xdw = XmlDictionaryWriter.CreateMtomWriter(contractDataStream, Encoding.UTF8, 2048000, "text/xml");

    //调用WriteObject方法将对象通过XmlDictionaryWriter序列化并编码

    dcs.WriteObject(xdw, person);

    //将序列化并编码后的字节流写入到原始流对象

    xdw.Flush();

    contractDataStream.Position = 0;

    //为了查看序列化到流中的内容,将流内容读取出来并用Unicode解码为字符串

    byte[] contractDataByte = new byte[contractDataStream.Length];

    contractDataStream.Read(contractDataByte, 0, (int)contractDataStream.Length);

    string contractDataString = Encoding.UTF8.GetString(contractDataByte);

    //将流反序列化为DataContract对象

    contractDataStream.Position = 0;

    XmlDictionaryReader reader = XmlDictionaryReader.CreateMtomReader(contractDataStream, Encoding.UTF8, XmlDictionaryReaderQuotas.Max);

    person = (Person)dcs.ReadObject(reader);

    reader.Close();

}

 

生成的序列化后的数据用UTF8解码后看:

clip_image011

 

可以看出CreateMtomWriter构造的XmlDictionaryWriterxml的内容转成了MIME的形式,下面仔细的分析一下MIME消息的构成。

XmlMtomWriter生成的MIME的消息一般分为三个部分:

l  MIME

MIME-Version: 1.0  - 表示MIME的版本号

头部最重要的内容是Content-Type中的一些内容,它们是:

Content-Type: multipart/related – 表示是多部分的消息

type="application/xop+xml" – 表示是xop协议的MIME消息格式

Boundary – 部分之间的边界,这个值插在不同的部分之间以分割不同的部分内容

Start – 开始的那个部分的Content-ID,每个部分都有一个唯一的Content-ID标识。这里开始部分就是xml的部分。

l  第一部分(xml部分)

MIME头部跟第一部分之间有个空行,表示头部结束后面是部分的内容。

Boundary指定的字符串前面加上”—“字符是部分开始和结束的标识。

每个部分也分headerbody部分,以空行分割。

Content-ID: <http://tempuri.org/0/633482733255804248> – 此部分的标识id

Content-Transfer-Encoding: 8bit – 表示body部分的内容可能是非ASCII编码的编码内容,这里xml的内容一般以UTF8UTF16编码,使用了每个字节的所有8位。

Content-Type: application/xop+xml;charset=utf-8;type="text/xml" – 表示body的内容类型为xopxml内容。

 

Body部分就是xml的内容,重点关注一下二进制数据binaryBuffer2的表现形式。

二进制binaryBuffer1数据部分被直接编码成base64字符串放在xml中。

binaryBuffer2部分的数据实际上从xml中剥离出来,作为一个单独的部分存在。看一下在xml中的binaryBuffer2

<binaryBuffer2><xop:Include href="cid:http%3A%2F%2Ftempuri.org%2F1%2F633482781614236973" xmlns:xop="http://www.w3.org/2004/08/xop/include"/></binaryBuffer2>

其中href=cid:http%3A%2F%2Ftempuri.org%2F1%2F633482781614236973指向表示这部分二进制数据的部分的标识id,实际的二进制数据被放在指向的那个部分。

 

l  后续部分(二进制数据部分)

没有被直接放在第一部分xml中的内容都会被放置在随后的那些部分中,本例中就只有一个binaryBuffer2部分的数据被放置到后续部分。

同样这个部分有headerbody部分,以空行分割。

Content-ID: http://tempuri.org/1/633482781614236973 - 就是xml部分中binaryBuffer2引用的id

Content-Transfer-Encoding: binary – 表示body部分是二进制的未经编码的。

Content-Type: application/octet-stream – 内容类型为数据流。

 

在测试中我们把binaryBuffer2的数据长度设置为768,如果改成767,看看结果:

clip_image013

可以发现这时binaryBuffer2的数据也被编码成base64直接放入到xml中了。

也就是说,CreateMtomWriter构造的XmlDictionaryWriter是根据二进制数据的长度来决定数据如何表现,如果小于768则把数据编码为base64,如果大于等于768则把数据单独放到一个部分,并保持二进制数据的原样。

 

CreateMtomWriter方法构建的XmlDictionaryWriter写入器有以下特点:

l  DataContractSerializer首先把对象序列化为xml的各个元素写入到XmlDictionaryWriter对象。(这步跟CreateTextWriter方法构建的XmlDictionaryWriter一样,其实DataContractSerializer传到XmlDictionaryWrite的内容是一样的)

l  CreateMtomWriter方法构建的XmlDictionaryWriter写入器分析DataContractSerializer传进来的xml元素的内容,如果包含二进制的内容,则判断如果二进制的数据大于等于768字节的就把它放到一个单独部分保持二进制,如果小于依然保存在xml中以base64编码。

l  MTOMheaderBoundary部分边界符都使用UTF8编码,xml部分的编码由CreateMtomWriter方法中的encoding参数决定,最终将MTOM的内容根据各自的编码转换到字节流,保存到一个流对象中,本例中就是MemoryStream

 

3.2.3.   CreateBinaryWriter方法

DataContractSerializer还提供了一种最高效的序列化方式,二进制序列化,把xml内容直接转成二进制的数据,不过这样的转换是微软自己的定义的,只能在.net环境下使用,跟别的技术不具有交互性。

 

看一下用CreateBinaryWriter构造的XmlDictionaryWriter序列化前面例子的代码:

 

XmlDictionaryCreateBinaryWriter(dcs, person);

private static void XmlDictionaryCreateBinaryWriter(DataContractSerializer dcs, Person person)

{

    //构造用于保存序列化并编码后的DataContract对象的流

    MemoryStream contractDataStream = new MemoryStream();

    //新建一个CreateTextWriter方法构建的指定UnicodeXmlDictionaryWriter对象

    XmlDictionaryWriter xdw = XmlDictionaryWriter.CreateBinaryWriter(contractDataStream);

    //调用WriteObject方法将对象通过XmlDictionaryWriter序列化并编码

    dcs.WriteObject(xdw, person);

    //将序列化并编码后的字节流写入到原始流对象

    xdw.Flush();

    contractDataStream.Position = 0;

    //为了查看序列化到流中的内容,将流内容读取出来并用UTF8解码为字符串

    byte[] contractDataByte = new byte[contractDataStream.Length];

    contractDataStream.Read(contractDataByte, 0, (int)contractDataStream.Length);

    string contractDataString = Encoding.UTF8.GetString(contractDataByte);

    //将流反序列化为DataContract对象

    contractDataStream.Position = 0;

    XmlDictionaryReader reader = XmlDictionaryReader.CreateBinaryReader(contractDataStream, new XmlDictionaryReaderQuotas());

    person = (Person)dcs.ReadObject(reader);

    reader.Close();

}

 

序列化后的结果是二进制的,不具可读性,下面试着用UTF8编码强行把二进制的数据转成字符串看一下:

clip_image015

 

本例中,序列化后的二进制数据长度为1010字节,前面使用CreateTextWriter方法并使用UTF8编码的序列化后的数据长度为1385。可见二进制序列化后的数据长度最短。

 

3.3.   DataContractSerializer序列化总结

DataContractSerializer本身完成了把要序列化的对象序列化为xml的形式,之后再由不同的编码方案对这个xml进行编码,最后形成完全序列化的数据流。

DataContractSerializer实际上提供了四种编码方案,其中WriteObject(Stream stream, object graph)直接序列化到流和CreateTextWriter方法构建的XmlDictionaryWriter这两种,是直接把xml字符进行编码,只是WriteObject(Stream stream, object graph)使用固定的UTF8编码,CreateTextWriter方法构建的XmlDictionaryWriter可以选择序列化使用的编码。

CreateMtomWriter方法构建的XmlDictionaryWriter是把对象的xml编码为MTOM的形式。

CreateBinaryWriter构造的XmlDictionaryWriter是把对象的xml编码为二进制的形式。

 

Xml形式具有最佳的互操作性,适应面最广。

MTOM的形式适合传输含有二进制大数据的对象。

二进制的形式传输效率最高,但是不具互操作性,只能用于.net环境的交换。

posted on 2008-06-06 22:57  chnking  阅读(9058)  评论(19编辑  收藏  举报