一起学WCF【6】

来源:互联网 发布:openwrt php 环境搭建 编辑:程序博客网 时间:2024/05/01 17:27

服务契约和数据契约的版本控制

软件开发过程中需求是不断变化的,因此软件的功能和交换数据的结构也在变化。操作和数据的变化会引起WSDL文档的变化,因此产生服务的一个新版本。实际上从结构或技术上都不大可能连续地更新组件,不大可能对服务的每个变化都作出响应。

对于服务契约和数据契约,内容的修改不会引起客户端的不兼容,因此在它们发生变化时要保证交换信息的兼容性。

有些情况则需要添加新契约,如一个方法的名称发生变化,或需要添加一个数据成员时,这会导致契约的中断,因而需要创建一个新的版本。这样最好修改命名空间,使它适应新版本,然后再把服务托管在新的终结点上。

数据契约的版本控制

WCF和DataContractSerializer序列化器对数据契约的结构变化非常宽松。可以添加新的数据成员,或删除IsRequired属性为false的数据成员。这样做的一个优点是无须考虑不重要的数据类型。但是在面向服务的环境里,必须遵守契约,即当必须修改契约时,必须正式向外公开修改后的契约。

有以下结论:方法的参数可增可减;数据成员可增可减;数据类型可以改变,前提是必须保持兼容;可以增加方法。这些修改不会带来任何技术上的问题。

双向版本控制

一个经常出现的与数据契约版本控制有关的问题是新增特性,WCF可能会忽略把它反序列化为一个旧的数据契约。

现将前面的示例的功能完整表述如下:客户端向服务发送一个价格查询请求,并接收一个包含一个标志和价格的响应对象。客户端需要再次调用ConfirmPrice方法确认收到的价格。

接口部分的完整代码如下:

[ServiceContract]

    publicinterface ICarRentalService

    {

       [OperationContract]

        PriceCalculationResponseCalculatePrice(PriceCalculationRequest resp);

 

       [OperationContract]

        boolConfirmPrice(PriceCalculationRequest resp);

    }

 

   [DataContract]

    publicclass PriceCalculationRequest

    {

       [DataMember(Name = "PickupDate", Order = 1, IsRequired =true)]

        public DateTime PickupDate { get; set; }

       [DataMember(Name = "ReturnDate", Order = 3)]

        publicDateTime ReturnDate { get; set; }

       [DataMember(Order = 2)]

        publicstring PickupLocation { get; set; }

       [DataMember(Order = 4)]

        publicstring ReturnLocation { get; set; }

       [DataMember]

        privatestring VehicleType { get; set; }

        publicstring Color { get; set; }

    }

 

   [DataContract]

    publicclass PriceCalculationResponse

    {

        [DataMember]

        publicint Flag { get; set; }

       [DataMember]

        publicstring Price { get; set; }

}

如果在对软件进行国际化时为数据添加一个Currency特性,它会在客户端丢失,因为客户端还不知道这个刚添加的特性,也没有办法对它的内容进行反序列化。

解决这个双向问题的方法是实现IExtensionDataObject接口并定义必不可少的ExtensionDataObject成员。如果DataContractSerializer序列化器在XML文档中检测到未知元素,在序列化时,会把它写入ExtensionDataObject属性包。当再欠引用ExtensionDataObject对象的内容时,它里面的内容还将保留,因此在数据契约的不同版本之间,数据不会丢失。

如果使用Add Service Reference对话框,数据类会自动实现此接口。如果希望把结果返回给服务器端,就必须在数据类中用手工方法实现此接口。示例代码如下:

[DataContract]

    publicclass PriceCalculationResponse : IExtensibleDataObject

    {

        publicExtensionDataObject ExtensionData { get; set; }

 

       [DataMember]

        publicint Flag { get; set; }

       [DataMember]

        publicstring Price { get; set; }

}

服务契约版本控制的最佳实践

如果不同模式之间必须严格保持一致,则每个修改实现后,必须给契约一个新的版本。

如果不同模式之间没有必要严格保持一致,则需要注意:可以在任何时候添加新方法;可以不删除任何已存在的方法;参数的数据类型必须保持兼容。

数据契约版本控制的最佳实践

如果不同模式之间必须保持严格一致,则在每个修改实现后,必须赋给契约一个新的版本。

如果不同模式之间没有必要严格保持一致,需要注意:

不要由于继承的原因给数据契约赋予一个新版本,而是要创建一个独立的数据类;为了方便双向版本控制,必须一开始就实现IExtensionDataObject接口;如果确实需要改变数据类型或数据成员的名称,使用DataContract或DataMember特性生成兼容的数据结构;不要随便对数据类型进行修改;不要改变[DataMember(Order=?)]属性确定的数据成员的顺序;保持IsRequired的默认值(false)不变;可以在任何时候添加数据成员,但要注意会改变序列化的顺序。可把Order属性值设置为当前版本值;不要删除数据成员;不要对IsRequired属性做后续的修改。

 

消息契约

客户端与服务器之间需要交换SOAP信息。但到目前我们只是影响了SOAP体的内容。在大多数情况下,我们只能控制数据契约和操作契约。然而对于某些情形,需要对SOAP消息(包括消息体和消息头)进行控制。SOAP消息头正好适合这样的情形:需要传送额外的数据,不需要扩展数据类(如安全令牌)。

消息契约既可完全控制消息头的内容,也可控制消息体的结构。我们已经把数据契约看成是传递的参数和返回值,而它们完全可以替换为消息契约。如果选择消息契约则必须为所有参数使用消息契约,方法中不能混用数据结构和消息契约。

如下代码使用了[MessageContract]、[MessageHeader]和[MessageBody]这三个特性的用法:

[MessageContract]

    publicclass PriceCalculation

    {

       [MessageHeader]

        publicCustomHeader SoapHeader { get; set; }

       [MessageBodyMember]

        publicPriceCalculationRequest PriceRequest { get; set; }

    }

 

   [DataContract]

    publicclass CustomHeader

    {

       [DataMember]

        publicstring Username { get; set; }

}

除了增加一个传递价格请求对象外,还在SOAP消息头中传递了一个用户名。

XML序列化

WCF支持多种类型的序列化。默认使用DataContractSerializer类序列化CLR对象。前例已说到[DataContract]和[DataMember]特性的几个不同属性。在某种程度上,我们可控制生成的XML信息集的结构。

但对创建文档的影响而言DataContractSerializer类提供的功能较少,如不能满足要求我们可使用XML序列化器。

为了指示WCF使用XML序列化器,必须把自己的服务契约设置为[XmlSerializer-Format]特性,如果只是想某个操作使用XML序列化器,可把此特性应用于这个操作。默认时,XML序列化器会对所有公开的属性进行序列化。

如下例我们可以对PriceCalculationRequest类进行修改:

[XmlRoot("PriceRequest")]

    publicclass PriceCalculationRequest

    {

       [XmlAttribute()]       

        publicstring PickupLocation { get; set; }

       [XmlElement(ElementName="ReturnLoc")]

        publicstring ReturnLocation { get; set; }

       [XmlIgnore()]

        publicDateTime PickupDate { get; set; }

        privateDateTime ReturnDate { get; set; }

}

可对比生成的XSD文档查看效果。

原创粉丝点击