谈谈WCF中的Data Contract(4):WCF Data Contract Versioning

软件工程是一门独特的工程艺术,需要解决的是不断改变的需求变化。而对于WCF,对于SOA,由于涉及的是对多个系统之间的交互问题,如何有效地解决不断改变的需求所带来的问题就显得更为重要:Service端版本的变化能否保持现有Consumer的正常调用,Consumer端的改变不至于影响对Service 的正常调用。对于Data Contract来说就是要解决这样的问题:Service端或者Client对Data Type的改变不会影响Service的正常调用。

在系统开发过程中,通过对Data Type添加额外的字段进而对其进行扩展,是一个种很常见的场景。本部分就作中介绍Data Contract的这种变化,Service或者Client的Data Contract在本地添加一个新的Data Member会造成怎样的影响,WCF可以采用怎样的机制来解决这种单方面Data Contract版本的改变。

我们同样通过Dome来说话。在这个Demo中,我使用上面介绍的Order Processing的场景,下面是整个Solution的结构(需要说明的是,本片文章提供的Code片断和Source Code都是基于VS 2008的)。

1. Service端: Artech.DataContractVersioning.Service

Data Contract

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

namespace Artech.DataContractVersioning.Service
{
    [DataContract(Namespace="http://artech.datacontractversioning")]
    public class Order
    {
        [DataMember(Order = 0)]
        public Guid OrderID
        {get;set;}

        [DataMember(Order = 1)]
        public DateTime OrderDate
        { get; set; }

        [DataMember(Order = 2)]
        public Guid SupplierID
        { get; set; }
    }
}

Service Contract 和Service Implementation: Process方法简单地将Order对象返回到客户端,当Client接受到Service返回的Order对象后,可以检测和由它传递给Service的Order对象有什么不同。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;

namespace Artech.DataContractVersioning.Service
{
    [ServiceContract]
    public interface IOrderManager
    {
        [OperationContract]
        Order Process(Order order);
    }
}

namespace Artech.DataContractVersioning.Service
{
    public class OrderManagerService:IOrderManager
    {
        IOrderManager Members#region IOrderManager Members

        public Order Process(Order order)
        {
            return order;
        }

        #endregion
    }
}

2. Client端:

Data Contract

[DataContract(Name="Order",Namespace="http://artech.datacontractversioning")]
    public class CustomOrder
    {
        [DataMember(Order = 0, Name="OrderID")]
        public Guid OrderNo
        { get; set; }

        [DataMember(Order = 2, Name = "SupplierID")]
        public Guid SupplierNo
        { get; set; }

        [DataMember(Order = 1)]
        public DateTime OrderDate
        { get; set; }        
    }
}

Program:先创建一个Order对象,向Console打印出Order的信息,随后以此作为参数调用Service,最后将返回的Order对象的信息打印出来,看看两者之间的有何区别。

namespace Artech.DataContractVersioning.Client
{
    class Program
    {
        static void Main(string[] args)
        {
            ChannelFactory<IOrderManager> channelFactory = new ChannelFactory<IOrderManager>("orderManager.http");
            IOrderManager orderManager = channelFactory.CreateChannel();

            try
            {
                CustomOrder order = new CustomOrder { OrderNo = Guid.NewGuid(), SupplierNo = Guid.NewGuid(), OrderDate = DateTime.Today, ShippingAddress="Room E101, Airport Rd #328, Suzhou Jiangsu Province" };
                Console.WriteLine("The original order: \n{0}", order.ToString());
                order = orderManager.Process(order);
                Console.WriteLine("\n\nThe order processed by service: \n{0}", order.ToString());
            }
            finally
            {
                (orderManager as IDisposable).Dispose();
            }

            Console.Read();
        }
    }
}

通过上面的分析,我们可以知道,尽管就CLR Type的定义来讲,Service端的Order和Client端的CustomOrder具有很大的差异,但是通过WCF Datacontract Attribute的适配,他们是相互匹配的。

现在我们在Client端为Custom添加一个新的成员,ShippingAddress,通过重写ToString方法:

namespace Artech.DataContractVersioning.Client
{
    [DataContract(Name="Order",Namespace="http://artech.datacontractversioning")]
    public class CustomOrder
    {
        [DataMember(Order = 0, Name="OrderID")]
        public Guid OrderNo
        { get; set; }

        [DataMember(Order = 2, Name = "SupplierID")]
        public Guid SupplierNo
        { get; set; }

        [DataMember(Order = 1)]
        public DateTime OrderDate
        { get; set; }

        [DataMember(Order = 3)]
        public string ShippingAddress
        { get; set; }
        
        public override string ToString()
        {
            return string.Format("Order No.\t: {0}\nSupplier No.\t: {1}\nOrder Date:\t: {2}\nShipping Address: {3}", this.OrderNo, this.SupplierNo, this.OrderDate, this.ShippingAddress);
        }
    }
}

我们来看看Client端程序运行的输出结果:

通过上面的结果,我们发现Shipping Address的信息在经过Service处理后丢失了。原因很简单,Service端的Data Contract根本就没有ShippingAddress成员,所有在反序列化生成Order对象的时候将会忽略ShippingAddress的信息。

其实这是一个不太合理的状况,对于Client来说,我指定了对象的某个对象的某个成员的值,结果Service处理返回后,却无缘无故(对于Client来说是无缘无故)丢失了。其实这种情况还出来在另一种场景之中:Client先调用Service A,Service B再将相同的对象作为参数调用Service C,现在假设Client和Service B的Data Contract是CustomOrder,Service A的Data Contract是少一个ShippingAddress的Order,那么经过Service A反序列化的对象将会是缺少Shipping Address的Order对象,然后这个Order对象又由Service A传导Service B,虽然Service B能过识别Shipping Address成员,但是现在却没有改成员的值了,这显然是有问题的。我们把这样的问题称为Round trip问题,我们必须解决这样一个问题。

其实在WCF中解决这样一个问题的方案简单而直接,那就是在Data Contract中定义一个额外的成员来存储没有在成员列表中定义的信息。我们可以让Data Contract的Data Type实现System.Runtime.Serialization.IExtensibleDataObject Interface来解决Round trip的版本问题。Interface的定义如下,他仅仅有一个Property成员:ExtensionData。

namespace System.Runtime.Serialization
{
    // Summary:
    //     Provides a data structure to store extra data encountered by the System.Runtime.Serialization.XmlObjectSerializer
    //     during deserialization of a type marked with the System.Runtime.Serialization.DataContractAttribute
    //     attribute.
    public interface IExtensibleDataObject
    {
        // Summary:
        //     Gets or sets the structure that contains extra data.
        //
        // Returns:
        //     An System.Runtime.Serialization.ExtensionDataObject that contains data that
        //     is not recognized as belonging to the data contract.
        ExtensionDataObject ExtensionData { get; set; }
    }
}

现在我们来重新定义Service的Order Data Contract:

namespace Artech.DataContractVersioning.Service
{
    [DataContract(Namespace="http://artech.datacontractversioning")]
    public class Order:IExtensibleDataObject
    {
        [DataMember(Order = 0)]
        public Guid OrderID
        {get;set;}

        [DataMember(Order = 1)]
        public DateTime OrderDate
        { get; set; }

        [DataMember(Order = 2)]
        public Guid SupplierID
        { get; set; }


        public ExtensionDataObject ExtensionData
        {
            get;
            set;
        }
    }
}

我们再来运行一下client端程序,我们发现现在没有数据丢失了:

这就是实现了IExtensibleDataObject Interface的效果。就其本质,很简单,对于实现了该Interface的Data contract,将通过一个ExtensionDataObject 类型的对象来保存和获取那些没有在Data Contract定义的成员。为了一窥Order的ExtensionData属性中保存的内容,我们在Service进行Debug,在QuickWatch中看看它是不是真的保存了不能识别的ShippingAddress:

[原创]谈谈WCF中的Data Contract(1):Data Contract Overview [原创]谈谈WCF中的Data Contract(2):WCF Data Contract对Generic的支持 [原创]谈谈WCF中的Data Contract(3):WCF Data Contract对Collection & Dictionary的支持 [原创]谈谈WCF中的Data Contract(4):WCF Data Contract Versioning

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏木宛城主

两天完成一个小型工程报价系统(三层架构)

花了两天,整理了一下三层架构,顺便练了一个小型三层架构——工程报价系统。 功能很简单,完成基本的增删改查 搭建项目三层结构 ? 界面的设计以及美化 ...

35890
来自专栏C#

C#的网络适配器操作

     网络的相关设置在项目开发中有较多的应用,有时候需要在项目中对网络信息进行相关设置。      现在提供提供几种相关的辅助方法类。 (1).IP地址 ...

21570
来自专栏丑胖侠

《Drools7.0.0.Final规则引擎教程》第4章 4.3 日历

日历 日历可以单独应用于规则中,也可以和timer结合使用在规则中使用。通过属性calendars来定义日历。如果是多个日历,则不同日历之间用逗号进行分割。 在...

290100
来自专栏乐沙弥的世界

不可或缺的 sendEmail

    还在为Linux下没有便捷的邮件程序苦恼,还在为复杂的邮件服务器架设Google N多网页? 对于小型,便捷的Linux下命令行邮件程序,sendEma...

9120
来自专栏飞扬的花生

Html5上传插件封装

      前段时间将flash的上传控件替换成使用纯js实现的,在此记录 1.创建标签 <div class="camera-area" style="dis...

41180
来自专栏草根专栏

用VSCode开发一个asp.net core2.0+angular5项目(5): Angular5+asp.net core 2.0 web api文件上传

这部分就讲从angular5的客户端上传图片到asp.net core 2.0的 web api. 这是需要的源码: https://pan.baidu.com...

42150
来自专栏ASP.NET MVC5 后台权限管理系统

ASP.NET MVC5+EF6+EasyUI 后台管理系统(58)-DAL层重构

前言:这是对本文系统一次重要的革新,很久就想要重构数据访问层了,数据访问层重复代码太多。主要集中增删该查每个模块都有,所以本次是为封装相同接口方法    如果你...

33060
来自专栏张高兴的博客

张高兴的 Windows 10 IoT 开发笔记:串口红外编解码模块 YS-IRTM

14230
来自专栏菩提树下的杨过

msmq发送速度的测试

在一些并发量比较高的"中小型"应用中,如果短期内有大量的数据插入,利用msmq中转是一个不错的选择(petshop就是这么干的),想知道msmq一秒钟内到底能...

25890
来自专栏hbbliyong

C# ini文件读写类

VC中提供了API函数进行INI文件的读写操作,但是微软推出的C#编程语言中却没有相应的方法,下面是一个C# ini文件读写类, 从网上收集的,很全,就是没有对...

34460

扫码关注云+社区

领取腾讯云代金券