WCF系列(五) -- 也谈序列化(下)
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类继承。NetDataContractSerializer跟DataContractSerializer一个主要的不同是:NetDataContractSerializer序列化后的xml中包含了.net的类型信息,反序列化时必须要被反序列化为同样类型的对象,这点跟BinaryFormatter和SoapFormatter这两个序列化器类似。DataContractSerializer序列化后的xml中则不包含.net的类型信息,通用性和交互性更好。在实际应用中DataContractSerializer是WCF的默认序列化器,绝大多数情况下都是使用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序列化的那个例子得到的结果:
把这个序列化后的结果跟直接使用SoapFormatter序列化的结果比较一下有以下几点不同:
l DataContractSerializer序列化是面向数据的,不保留对象之间的引用关系,所以虽然address1和address2指向的是同一个对象,但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]可以用来描述Property和field,不管这个成员是public或者private,只要标记了[DataMember],这个成员就会被序列化。
[DataMember]属性可以带有以下几个参数:
l Name
指定这个成员对外暴露的名称,如果没有指定此参数,缺省的就是此Property或field名。
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标记,不管成员是public或private的,都会被序列化,XmlSerializer序列化器只序列化public的成员。
l DataContract的类型的成员的名称空间跟整个类的一致,成员不能独立设置自己的名称空间(DataMember属性没有名称空间参数),XmlSerializer序列化器允许成员拥有自己的名称空间(XmlAttribut和XmlElement都有namespage参数设置成员独立的名称空间)。
2.4. 准备待序列化的用 DataContract标识类
设计一个的Person类型,其中包含五个成员,binaryBuffer1、binaryBuffer2、name、address、age,其中name和address又分别是DataContract标识的简单类型。
binaryBuffer1和 binaryBuffer2是byte[]类型的数据,表示二进制的数据,可能包含任何非二进制的对象,比如图片、声音等等。这里放两个二进制的数据为了测试不同长度的二进制数据在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这样的:
WriteObject(Stream stream, object graph)方法序列化有以下特点:
l DataContractSerializer首先把对象序列化为一个xml 形式的字符串。
l 对于其中的二进制数据,不管多大长度,一律转成base64编码的字符串。本例中bufferbytes2被设置为768字节长度,序列化结果是一串base64的编码,又测试了把bufferbytes2长度增加为7680个字节,序列化的结果仍然是一串更长的base64的编码。
l DataContractSerializer然后把序列化后的xml形式的字符串以UTF8编码(这是默认的编码,并且没有提供可以使用别的编码的接口,所以对其解码也必须使用UTF8,这点跟XmlSerializer和SoapFormatter序列化器一样)对其进行编码,转换到字节流,最终保存到一个流对象中,本例中就是MemoryStream。可以通过对字节流进行UTF8解码得到xml的字符串,就是上图看到的结果。
反序列化后的Person对象是这样的:
3.2. 使用WriteObject(XmlDictionaryWriter writer, object graph)方法序列化
这个方法是DataContractSerializer序列化器把对象序列化为xml字符串后,不直接对xml进行编码转换成字节流,而是把xml写入到XmlDictionaryWriter,XmlDictionaryWriter提供了多种对xml进行编码的选择,最终再由XmlDictionaryWriter把编码后的xml写入到目标流中。
XmlDictionaryWriter提供了三类静态方法,构造三种不同类型的XmlDictionaryWriter,分别对xml进行不同的编码操作。
它们分别是:
3.2.1. CreateTextWriter方法
CreateTextWriter方法构建的XmlDictionaryWriter写入器,这样的写入器跟WriteObject(Stream stream, object graph)方法的作用类似,也是直接把xml的内容直接编码为字节流,但是允许选择编码时使用哪种编码。
XmlDictionaryWriter的CreateTextWriter方法主要下面两种重载形式。
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方法构建的指定Unicode的XmlDictionaryWriter对象
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)方法序列化后的结果一模一样:
再用Encoding.Unicode 编码测试,序列化后,contractDataStream中获得的序列化后的数据长度为2756字节,明显比前面使用UTF8编码得到的结果长的多。这是很正常的结果,因为一般ASCII可表示的字符,使用UTF8编码也是用一个字节表示,并跟ASCII编码兼容,而UTF-16编码会把所有字符都编码为2个字节,所以使用Encoding.Unicode编码后的结果会大很多,几乎是UTF的2倍(因为测试的内容字符绝大多数是ASCII可表示的字符)
将contractDataStream中的字节流用Encoding.Unicode解码,查看内容为,解码后的内容跟UTF8解码后的内容也是一致的:
实际测试CreateTextWriter方法中的Encoding参数只能为:Encoding.Unicode、Encoding.UTF8和Encoding.BigEndianUnicode 三种。
CreateTextWriter方法构建的XmlDictionaryWriter写入器有以下特点:
l DataContractSerializer首先把对象序列化为一个xml 形式的字符串。对于其中的二进制数据,不管多大长度,一律转成base64编码的字符串。
l DataContractSerializer把序列化后的xml字符串传给XmlDictionaryWriter。
l XmlDictionaryWriter把序列化后的xml形式的字符串以指定的编码对其进行编码,转换到字节流,最终保存到一个流对象中,本例中就是MemoryStream。
序列化时,使用XmlDictionaryWriter的CreateTextWriter方法建立的XmlDictionaryWriter对象作为写入器,反序列化时,相应的就要用XmlDictionaryReader的CreateTextReader建立的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字节可表示简单字符的数据,编码后的数据长度比为3:4,如果数据长度不长,增加的长度影响不大,但是如果原来的二进制数据就比较大,比如300K,编码后的数据将会是400K,再如果原来是30M,编码后为40M,多出来的数据量难以忽视。
鉴于这种情况,W3C制定了XOP(XML-binary Optimized Packages,XOP)协议来解决二进制问题,XOP 提供一个机制,可选择性地提取要优化的信息,将其添加到多部分 MIME 消息中(其中也包括您的 SOAP 消息)并显式地对其进行引用。使用 XOP 的过程称为 MTOM(SOAP 消息传输优化机制——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-info的header,header的值就是这个参数提供的。
看一下用CreateMtomWriter构造的XmlDictionaryWriter序列化前面例子的代码:
XmlDictionaryCreateMtomWriter(dcs, person);
private static void XmlDictionaryCreateMtomWriter(DataContractSerializer dcs, Person person)
{
//构造用于保存序列化并编码后的DataContract对象的流
MemoryStream contractDataStream = new MemoryStream();
//新建一个CreateMtomWriter方法构建的指定Unicode的XmlDictionaryWriter对象
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解码后看:
可以看出CreateMtomWriter构造的XmlDictionaryWriter将xml的内容转成了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指定的字符串前面加上”—“字符是部分开始和结束的标识。
每个部分也分header和body部分,以空行分割。
Content-ID: <http://tempuri.org/0/633482733255804248> – 此部分的标识id
Content-Transfer-Encoding: 8bit – 表示body部分的内容可能是非ASCII编码的编码内容,这里xml的内容一般以UTF8或UTF16编码,使用了每个字节的所有8位。
Content-Type: application/xop+xml;charset=utf-8;type="text/xml" – 表示body的内容类型为xop的xml内容。
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部分的数据被放置到后续部分。
同样这个部分有header和body部分,以空行分割。
Content-ID: http://tempuri.org/1/633482781614236973 - 就是xml部分中binaryBuffer2引用的id。
Content-Transfer-Encoding: binary – 表示body部分是二进制的未经编码的。
Content-Type: application/octet-stream – 内容类型为数据流。
在测试中我们把binaryBuffer2的数据长度设置为768,如果改成767,看看结果:
可以发现这时binaryBuffer2的数据也被编码成base64直接放入到xml中了。
也就是说,CreateMtomWriter构造的XmlDictionaryWriter是根据二进制数据的长度来决定数据如何表现,如果小于768则把数据编码为base64,如果大于等于768则把数据单独放到一个部分,并保持二进制数据的原样。
CreateMtomWriter方法构建的XmlDictionaryWriter写入器有以下特点:
l DataContractSerializer首先把对象序列化为一个xml 形式的字符串。对于其中的二进制数据,不管多大长度,一律转成base64编码的字符串。
l DataContractSerializer把序列化后的xml字符串传给XmlDictionaryWriter。
(这前两步跟CreateTextWriter方法构建的XmlDictionaryWriter一样,其实DataContractSerializer传到XmlDictionaryWrite的内容是一样的)
l CreateMtomWriter方法构建的XmlDictionaryWriter写入器分析DataContractSerializer传进来的xml的内容,如果包含二进制的内容,则判断如果二进制的数据大于等于768字节的就把它放到一个单独部分,如果小于依然保存在xml中。
l MTOM的header和Boundary部分边界符都使用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方法构建的指定Unicode的XmlDictionaryWriter对象
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编码强行把二进制的数据转成字符串看一下:
本例中,序列化后的二进制数据长度为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环境的交换。
TrackBack:http://www.cnblogs.com/chnking/archive/2008/06/06/1215411.html