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

WCF揭秘学习笔记(2):数据表示

Posted on 2014-05-19 19:08  米粒3  阅读(253)  评论(0编辑  收藏  举报

背景知识

  WCF提供了一种语言为软件通信建模,称作服务模型。使用更底层的编程架构提供的类可以从这种语言建立的模型中生成可用的通信软件。

  在服务模型使用的语言中,负责通信的软件部分称为服务(service)。一个服务具有一个或多个通信的终结点,终结点包括地址、绑定和契约。

  地址的作用比较简单,它通过使用URL指定服务的唯一地址。

  绑定指定了客户端与服务器间的通信协议。绑定至少需要分别提供一种编码消息和传输消息的协议。

  契约指定了在一个终结点可以执行的操作。

  以上节中的契约为例:

复制代码
//契约
[ServiceContract]
publicinterface IDerivativesCalculator
{
[OperationContract]
decimal CalculateDerivative(
string[] symbols,
decimal[] parameters,
string[] functions);
......
}

//客户端调用服务代码
string[] symbols =newstring[] {"MSFT"};
decimal[] parameters =newdecimal[]{3};
string[] functions =newstring[]{ "TechStockProjections" };

IDerivativesCalculator proxy
=new ChannelFactory<IDerivativesCalculator>
(
"CalculatorEnpoint").CreateChannel();
proxy.CalculateDerivative(symbols, parameters, functions);
((IChannel)proxy).Close();
复制代码

  当执行代码proxy.CalculateDerivative(symbols, parameters, functions)时,作为输入传给操作的数据会被添加到一个Message类的实例中。Message是WCF信道层提供的一个类。在Message 类里,数据被标识成一个XML信息集(XML Information Set),通常称为InfoSet。当数据准备从客户端传输给服务器端时,绑定里指定的消息编码协议将决定包含客户端所提供数据的Message对象将以 何种形式表示给服务。消息编码协议可能会将消息转换成逗号隔开的字符串值,或JavaScript Object-Notation Format,或任何其他格式。然而,所有标准的绑定都会将Message对象表示成XML InfoSet的编码协议。根据预定义绑定的编码协议,XML InfoSet可能会使用各种标准的XML文本编码格式、标准的MTOM协议或使用WCF自己的二进制格式。

  当WCF服务接收到传输数据时,不管客户端是怎么样的编码格式,消息编码绑定元素会重新将它组装成一个Message对象,客户端发送的数据就 会在Message对象中以XML InfoSet的形式表示。这个Message对象会被传给WCF的调度器(dispatcher)组件。调度器组件从XML InfoSet中抽取出客户端的数据项,然后它会调用服务中实现了客户端所请求的操作的方法,而这些数据将作为方法的参数。

XmlSerializer和DataContractSerializer

  从客户端发给服务的数据会被序列化为XML,然后在服务器端又会从XML反序列化为原始的数据。WCF提供了两种XML序列化的工具来完成这项任务。

  一种就是已包含在.NET Framework类库的System.XML程序集中的System.Xml.Serialization.XmlSerializer。另一种是由 WCF提供的,即在System.Runtime.Ser-ialization程序集中的 System.Runtime.Serialization.DataContractSerializer。后者是Windows默认情况下使用的 XML序列化程序。

  我们可以通过添加System.Runtime.Serialization.DataContract和 System.Runtime.Serialization.DataMember特性使指定类能被 System.Runtime.Serialization.-DataContractSerializer所序列化。通过为类添加 System.Runtime.Serialization.DataContract特性、为类的成员添加 System.Runtime.Serialization.DataMember特性而将一个类的实例以XML表示,在WCF的术语里称为数据契约 (data contract)。

复制代码
[DataContract]
publicclass DerivativesCalculation
{
[DataMember]
privatestring[] symbols;
[DataMember]
privatedecimal[] parameters;
[DataMember]
privatestring[] functions;

publicstring[] Symbols
{
get { returnthis.symbols; }
set { this.symbols = value; }
}
......
}
复制代码

  虽然DataContractSerializer是WCF默认的XML序列化程序,但通过配置也可使用System.Xml.Serialization.XmlSerializer类进行XML序列化。如下所示:

复制代码
[ServiceContract]
[XmlSerializerFormat]
publicinterface IDerivativesCalculator
{
[OperationContract]
decimal CalculateDerivative(string[] symbols, decimal[] parameters, string[] functions);
......
}
复制代码

  也可以只为某个操作指定使用XmlSerializer类作为XML序列化程序:

复制代码
[ServiceContract]
[XmlSerializerFormat]
publicinterface IDerivativesCalculator
{
[OperationContract]
[XmlSerializerFormat]
decimal CalculateDerivative(string[] symbols, decimal[] parameters, string[] functions);
......
}
复制代码

  XmlSerializer为如何将数据表示成XML提供了精确控制,而DataContractSerializer在这方面只提供了很少的控制。它只允许指定在XML中用来引用数据的命名空间和名称,及数据项在XML里出现的顺序。如下所示:

复制代码
[DataContract(Namespace="Derivatives", Name="Calculation")]
publicclass DerivativesCalculation
{
[DataMember(Namespace
="Derivatives", Name="Symbols", Order=0)]
privatestring[] symbols;
[DataMember(Namespace
="Derivatives", Name="Parameters", Order=1)]
privatedecimal[] parameters;
......
}
复制代码

  System.Xml.Serialization.XmlSerializer默认将所有公有数据项都序列化成XML,与此不 同,System.Runtime.Serialization.DataContractSerializer要求通过添加 System.Runtime.Serialization.DataMember显式地指定哪些数据是需要序列化的。

  由于不允许控制数据项如何被序列化表示成XML,对于 System.Runtime.Serialization.DataContractSerializer来说序列化的过程是完全可预知的,从而也更加 容易进行优化。所以DataContratSerializer的一个很实际的好处就是有更好的性能--大概比 System.Xml.Serialization.XmlSerializer提升了10%。

XML物神

  以面向服务编程为主要特征的开发方式通常称为契约优先开发(contract-first development)。契约优先开发在构建软件时,一般会首先为外部接口进行交换的数据结构指定与平台无关的表示方法,以及与平台无关的数据交换协议。

  使用契约优先开发方式可以帮助人们避免这样的错误:本来打算创建一个可以在不同平台间交互的软件,但最后它却使用了只在某个平台上才使用的数据 格式,如.NET DataSet格式。然而,这种开发实践常被一种特别的契约优先方式所混淆,在这种特别的开发方式里,人们特别关注XML格式。这种方式会在编辑器里用 XML Schema语言定义数据格式,并保证最后所有的复杂数据类型都定义成XML Schema的数据类型。这样容易分心把考虑的重点转移到数据在XML里空竟是如何表示的,最后,人们开始争论各种XML编码方式的优点,而且对那些可能 妨碍他们直接看到或接触XML的东西表示怀疑。这样,XML在他们眼里变成一个物神(fetish),错误的认为它是契约优先开发的优点。

  使用System.Runtime.Serialization.DataContractSerializer,WCF不仅将软件开发者的注意力重新拉回到本应为最重要的东西上,而且也将数据表示的控制定位在合适的地方,也就是代码之外,即管理员的工作范围内。

创建服务

  WCF开发者使用System.Runtime.Serialization.DataContract特性和 System.Runtime.Serialization.DataMember特性定义服务需要交换的数据结构,然后他们可以使用第2章介绍的服务元 数据工具将这些数据结构用XML Schema表示。他们将这些XML Schema语言的表示提供给需要使用他们的服务的开发者。WCF的设计者们已经做出很大的努力以保证 System.Runtime.Serialization.DataContractSerializer将数据所序列化的XML结构可被现有的各种第 三方提供的反序列化XML数据的工具读入。

  但是,如果开发者希望限制作为输入的值的有效范围该怎么办呢?XML Schema语言提供了比.NET编程语言更丰富的功能来做这样的限制,而System.Runtime.Serializa- tion.DataContract和System.Runtime.Serialization.DataMember甚至根本没有提供类似的功能。 XML Schema语言不仅可让开发者告诉想使用他们服务的开发者输入值的有效范围,而且还可根据XML Schema定义检查XML表示的输入值是不是越出了有效范围。

  一个可行的办法是对输入值命名时使用一些有意义的名字,如下所示:

[DataContract]
publicclass BookOrderType
{
[DataMember]
publicint QuantityValueBetween100And1000;
}

  然后在代码里检查值的范围,并且在值越界时返回相应的异常。

创建客户端

  如果服务输入和输出数据的XML表示与 System.Runtime.Serialization.DataContractSerializer将数据表示成XML的方式不一致,服务元数据 工具所生成代码里的开关代码会选择使用XmlSerializer而不是DataContractSerializer去将数据序列化成XML。这样的代 码可使WCF开发者不需查看或操纵任何XML就可使用服务了。

  有些人对XML非常感兴趣,总希望看看XML Schema,看看类在XML中是怎么表达的。WCF也提供了满足他们的工具。按下面的方式执行服务元数据工具:

  svcutil /datacontractonly SomeAssembly.dll

  其中SomeAssembly.dll是包含了一个类的数据契约定义的程序集名称。这个命令会生成定义了这个类的实例被序列成的XML的XML Schema。

  现在我们来考虑:DataContractSerializer通过牺牲对数据如何表示成XML的控制,换来性能的改善值得吗?其实控制数据如何表示成XML对于开发者来说通常是没有用的,所以任何通过这种控制换来的性能提升都是受欢迎的。