程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 談談WCF中的Data Contract(4):WCF Data Contract Versioning

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

編輯:關於.NET

軟件工程是一門獨特的工程藝術,需要解決的是不斷改變的需求變化。而對於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:

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved