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

談談WCF中的Data Contract(2):WCF Data Contract對Generic的支持

編輯:關於.NET

通過第一部分的介紹,我們可以體會到,WCF 的Data Contract在CLR Type和Neutral Contract之間搭建了一座橋梁,彌合了.NET世界和廠商中立世界的差異。通過WCF Data Contract我們將CLR Data Type暴露成一個廠商中立的數據結構的描述,同樣通過WCF Data Contract我們將一個現有的CLR Data Type和既定的Neutral contract進行適配。

在.NET中,基於Primary Type,比如Int32,String等等,他們具有一個簡單的默認的序列化方式和結構,可以說他們不需要Data Contract。接下來我們主要討論的是一些相對比較特殊的、完全基於.NET的Data Type,比如Generic、Collection,和Dictionary。首先,我們結合例子來談談基於Generic的Data Type的Data Contract。

假設我們需要創建一個用於處理一些單據(Bill)的Service,比如如Order Bill、Sales Bill等。一般的單據都有一個單據頭(Header)和明細(Detail)列表,為此我們創建了一個Generic的Bill。並

namespace Artech.SpecialDataContract.Contract
{
  [DataContract]
  public class Bill<THeader, TDetail>
  {
    [DataMember]
    public THeader Header
    { get; set; }

    [DataMember]
    public IList<TDetail> DetailList
    { get; set; }
  }

  [DataContract]
  public class OrderHeader
  {
    [DataMember]
    public Guid OrderID
    { get; set; }

    [DataMember]
    public DateTime OrderDate
    { get; set; }
  }

  [DataContract]
  public class OrderDetail
  {
    [DataMember]
    public Guid ProductID
    { get; set; }

    [DataMember]
    public int Quantity
    { get; set; }
  }

}

為處理訂單單據創建了機遇訂單的Header和Detail。

對於一個Neutral Service Contract和Neutral Data Contract本身是不可能支持Generic的,也就是Neutral Contract只能是對一個具體的CLR Type的體現。所以在定義Service Contract的時候,對於那些包含Generic Type作為參數或者返回值得Operation,我們必須指定一個具體的Data Type。所以我們創建了如下一個IBillManager Service Contract:

namespace Artech.SpecialDataContract.Contract
{
  [ServiceContract]
  public interface IBillManager
  {
    [OperationContract]
    void Procss(Bill<OrderHeader, OrderDetail> orderBill);
  }
}

如何我們現在

Host基於這樣一個Contract的Service,你猜我們作為參數的數據類型將會如何體現的。

通過WSDL,我們會發現該Service的Data Contract將會以下面一段XSD的方式來呈現:

<?xml version="1.0" encoding="utf-8" ?>
<xs:schema elementFormDefault="qualified" targetNamespace="http://schemas.datacontract.org/2004/07/Artech.SpecialDataContract.Contract"
   xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://schemas.datacontract.org/2004/07/Artech.SpecialDataContract.Contract"
   xmlns:ser="http://schemas.microsoft.com/2003/10/Serialization/">
  <xs:import schemaLocation="http://artech/Artech.SpecialDataContract/BillManagerService.svc?xsd=xsd1"
    namespace="http://schemas.microsoft.com/2003/10/Serialization/" />
  <xs:complexType name="BillOfOrderHeaderOrderDetailLZ9Dq20o">
  <xs:annotation>
   <xs:appinfo>
    <GenericType Name="BillOf{0}{1}{#}" Namespace="http://schemas.datacontract.org/2004/07/Artech.SpecialDataContract.Contract"
       xmlns="http://schemas.microsoft.com/2003/10/Serialization/">
      <GenericParameter Name="OrderHeader" Namespace="http://schemas.datacontract.org/2004/07/Artech.SpecialDataContract.Contract"/>
      <GenericParameter Name="OrderDetail" Namespace="http://schemas.datacontract.org/2004/07/Artech.SpecialDataContract.Contract"/>
     </GenericType>
   </xs:appinfo>
  </xs:annotation>
  <xs:sequence>
   <xs:element minOccurs="0" name="DetailList" nillable="true" type="tns:ArrayOfOrderDetail"/>
   <xs:element minOccurs="0" name="Header" nillable="true" type="tns:OrderHeader"/>
  </xs:sequence>
 </xs:complexType>
 <xs:element name="BillOfOrderHeaderOrderDetailLZ9Dq20o" nillable="true" type="tns:BillOfOrderHeaderOrderDetailLZ9Dq20o"/>
 <xs:complexType name="ArrayOfOrderDetail">
  <xs:sequence>
   <xs:element minOccurs="0" maxOccurs="unbounded" name="OrderDetail" nillable="true" type="tns:OrderDetail"/>
  </xs:sequence>
 </xs:complexType>
 <xs:element name="ArrayOfOrderDetail" nillable="true" type="tns:ArrayOfOrderDetail"/>
 <xs:complexType name="OrderDetail">
  <xs:sequence>
   <xs:element minOccurs="0" name="ProductID" type="ser:guid"/>
   <xs:element minOccurs="0" name="Quantity" type="xs:int"/>
  </xs:sequence>
 </xs:complexType>
 <xs:element name="OrderDetail" nillable="true" type="tns:OrderDetail"/>
 <xs:complexType name="OrderHeader">
  <xs:sequence>
   <xs:element minOccurs="0" name="OrderDate" type="xs:dateTime"/>
   <xs:element minOccurs="0" name="OrderID" type="ser:guid"/>
  </xs:sequence>
 </xs:complexType>
 <xs:element name="OrderHeader" nillable="true" type="tns:OrderHeader"/>
</xs:schema>

對於不習慣看XSD的朋友,我們可以通過Add Service Reference的方式創建本地的Proxy file,借助生成的與之對應的Class來看看這個XSD最終呈現的結構:

[System.Diagnostics.DebuggerStepThroughAttribute()]
  [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")]
  [System.Runtime.Serialization.DataContractAttribute(Name="BillOfOrderHeaderOrderDetailLZ9Dq20o", Namespace="http://schemas.datacontract.org/2004/07/Artech.SpecialDataContract.Contract")]
   [System.SerializableAttribute()]
  public partial class BillOfOrderHeaderOrderDetailLZ9Dq20o : object, System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged {

    [System.NonSerializedAttribute()]
    private System.Runtime.Serialization.ExtensionDataObject extensionDataField;

    [System.Runtime.Serialization.OptionalFieldAttribute()]
    private Artech.SpecialDataContract.Client.BillManagerService.OrderDetail[] DetailListField;

    [System.Runtime.Serialization.OptionalFieldAttribute()]
    private Artech.SpecialDataContract.Client.BillManagerService.OrderHeader HeaderField;

    [global::System.ComponentModel.BrowsableAttribute(false)]
    public System.Runtime.Serialization.ExtensionDataObject ExtensionData {
      get {
        return this.extensionDataField;
      }
      set {
        this.extensionDataField = value;
      }
    }

    [System.Runtime.Serialization.DataMemberAttribute()]
    public Artech.SpecialDataContract.Client.BillManagerService.OrderDetail[] DetailList {
      get {
        return this.DetailListField;
      }
      set {
        if ((object.ReferenceEquals(this.DetailListField, value) != true)) {
          this.DetailListField = value;
          this.RaisePropertyChanged("DetailList");
        }
      }
    }

    [System.Runtime.Serialization.DataMemberAttribute()]
    public Artech.SpecialDataContract.Client.BillManagerService.OrderHeader Header {
      get {
        return this.HeaderField;
      }
      set {
        if ((object.ReferenceEquals(this.HeaderField, value) != true)) {
          this.HeaderField = value;
          this.RaisePropertyChanged("Header");
        }
      }
    }

    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

    protected void RaisePropertyChanged(string propertyName) {
      System.ComponentModel.PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
      if ((propertyChanged != null)) {
        propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
      }
    }
  }

  [System.Diagnostics.DebuggerStepThroughAttribute()]
  [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")]
  [System.Runtime.Serialization.DataContractAttribute(Name="OrderHeader", Namespace="http://schemas.datacontract.org/2004/07/Artech.SpecialDataContract.Contract")]
   [System.SerializableAttribute()]
  public partial class OrderHeader : object, System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged {

    [System.NonSerializedAttribute()]
    private System.Runtime.Serialization.ExtensionDataObject extensionDataField;

    [System.Runtime.Serialization.OptionalFieldAttribute()]
    private System.DateTime OrderDateField;

    [System.Runtime.Serialization.OptionalFieldAttribute()]
    private System.Guid OrderIDField;

    [global::System.ComponentModel.BrowsableAttribute(false)]
    public System.Runtime.Serialization.ExtensionDataObject ExtensionData {
      get {
        return this.extensionDataField;
      }
      set {
        this.extensionDataField = value;
      }
    }

    [System.Runtime.Serialization.DataMemberAttribute()]
    public System.DateTime OrderDate {
      get {
        return this.OrderDateField;
      }
      set {
        if ((this.OrderDateField.Equals(value) != true)) {
          this.OrderDateField = value;
          this.RaisePropertyChanged("OrderDate");
        }
      }
    }

    [System.Runtime.Serialization.DataMemberAttribute()]
    public System.Guid OrderID {
      get {
        return this.OrderIDField;
      }
      set {
        if ((this.OrderIDField.Equals(value) != true)) {
          this.OrderIDField = value;
          this.RaisePropertyChanged("OrderID");
        }
      }
    }

    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

    protected void RaisePropertyChanged(string propertyName) {
      System.ComponentModel.PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
      if ((propertyChanged != null)) {
        propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
      }
    }
  }

  [System.Diagnostics.DebuggerStepThroughAttribute()]
  [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")]
  [System.Runtime.Serialization.DataContractAttribute(Name="OrderDetail", Namespace="http://schemas.datacontract.org/2004/07/Artech.SpecialDataContract.Contract")]
   [System.SerializableAttribute()]
  public partial class OrderDetail : object, System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged {

    [System.NonSerializedAttribute()]
    private System.Runtime.Serialization.ExtensionDataObject extensionDataField;

    [System.Runtime.Serialization.OptionalFieldAttribute()]
    private System.Guid ProductIDField;

    [System.Runtime.Serialization.OptionalFieldAttribute()]
    private int QuantityField;

    [global::System.ComponentModel.BrowsableAttribute(false)]
    public System.Runtime.Serialization.ExtensionDataObject ExtensionData {
      get {
        return this.extensionDataField;
      }
      set {
        this.extensionDataField = value;
      }
    }

    [System.Runtime.Serialization.DataMemberAttribute()]
    public System.Guid ProductID {
      get {
        return this.ProductIDField;
      }
      set {
        if ((this.ProductIDField.Equals(value) != true)) {
          this.ProductIDField = value;
          this.RaisePropertyChanged("ProductID");
        }
      }
    }

    [System.Runtime.Serialization.DataMemberAttribute()]
    public int Quantity {
      get {
        return this.QuantityField;
      }
      set {
        if ((this.QuantityField.Equals(value) != true)) {
          this.QuantityField = value;
          this.RaisePropertyChanged("Quantity");
        }
      }
    }

    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

    protected void RaisePropertyChanged(string propertyName) {
      System.ComponentModel.PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
      if ((propertyChanged != null)) {
        propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
      }
    }
  }

為了使大家一眼就能了解整個結構,我對上面的代碼進行簡化:

namespace Artech.SpecialDataContract.Client.BillManagerService
{
  [DataContract]
  public class BillOfOrderHeaderOrderDetailLZ9Dq20o
  {
    [DataMember]
    public OrderHeader Header
    { get; set; }

    [DataMember]
    public OrderDetail[] DetailList
    { get; set; }
  }

  [DataContract]
  public class OrderHeader
  {
    [DataMember]
    public Guid OrderID
    { get; set; }

    [DataMember]
    public DateTime OrderDate
    { get; set; }
  }

  [DataContract]
  public class OrderDetail
  {
    [DataMember]
    public Guid ProductID
    { get; set; }

    [DataMember]
    public int Quantity
    { get; set; }
  }

}

我們可以通過上面的code,注意到下面的細節:

·     Generic class Bill<THeader, TDetail>沒有了,取而代之的是使用了具體OrderHeader和OrderDetial的新的非Generic class:BillOfOrderHeaderOrderDetailLZ9Dq20o。正如我們在上面所說,Neutral Contract根本就不知道Generic為何物。

·     新的Class name的名稱很難看,它有下面幾個部分組成:Bill(Generic Type Name)+ Of + OrderHeader(Generic Type的第一個類型參數對應的具體類型名稱)+OrderDetail(Generic Type的第二個類型參數對應的具體類型名稱)+lLZ9Dq20o(Generic Type參數類型Namespace的Hash Value)。

·     原本使用IList表示的DetailList變成了Array(public OrderDetail[] DetailList),這個將在和面的部分介紹。

我想你也不能容忍生成的如此冗長、甚至沒有太大意義的Class name。我們有辦法生成一個友好的名稱。那就是顯示指定Data Contract的Name:

[DataContract(Name="OrderBill")]
  public class Bill<THeader, TDetail>
  {
    [DataMember]
    public THeader Header
    { get; set; }

    [DataMember]
    public IList<TDetail> DetailList
    { get; set; }
}

現在對應的Data Contract Name將變成我們指定的名稱。

public partial class OrderBill : object, System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged {}

但是上面的做法是不對的。原因很簡單,如果我們在Service中添加一個處理Sales Bill的Operation(當然我們會添加兩個額外的Header和Detail:SalesHeader和SalesDetail):

[ServiceContract]
  public interface IBillManager
  {
    [OperationContract(Name=”ProcessOrderBill”)]
    void Procss(Bill<OrderHeader, OrderDetail> orderBill);

    [OperationContract(Name =”ProcessSalesBill”)]
    void Procss(Bill<SalesHeader, SalesDetail> salesBill);
  }

很顯然,WCF需要為Order Bill和Sales Bill創建兩個Data Contract,但是現在你卻把他們的名稱顯示地限定到一個固定的名稱,很顯然這會造成命名的沖突。如果你通過Browser試圖訪問Service,你會得到如下的Error:

The server was unable to process the request due to an internal error. For more information about the error, either turn on IncludeExceptionDetailInFaults (either from ServiceBehaviorAttribute or from the <serviceDebug> configuration behavior) on the server in order to send the exception information back to the client, or turn on tracing as per the Microsoft .NET Framework 3.0 SDK documentation and inspect the server trace logs.

所以WCF需要為此提供這樣的機制:基於不同的泛型類型參數生成不同Data Contract Name,這樣才能解決命名沖突。我們可以稍微修改一下Data Contract 的定義就可以了:

[DataContract(Name="Bill_{0}_{1}")]
  public class Bill<THeader, TDetail>
  {
    [DataMember]
    public THeader Header
    { get; set; }

    [DataMember]
    public IList<TDetail> DetailList
    { get; set; }
}

其中{0}和{1}分別代表第一個泛型類型參數和第二個泛型類型參數的名稱,一次類推,你可以根據參數類型的個數設置{2}{3}…

這樣我們生成的兩個DataContract的名稱為:Bill_OrderHeader_OrderDetail和Bill_SalesHeader_SalesDetail。

但是這並沒有根本解決問題,如果在我現在不同的Namespace中創建了兩個OrderHeader和OrderDetail呢?這無疑在.NET中是合法的,但是對於DataContract有有可能出現命名沖突。

這也就是為什麼WCF默認機制下會為Data Contract Name添加一個額外hash value的原因。其實你也可以以你自己的方式添加這個Hash value:

[DataContract(Name="Bill_{0}_{1}_{#}")]
  public class Bill<THeader, TDetail>
  {
    [DataMember]
    public THeader Header
    { get; set; }

    [DataMember]
    public IList<TDetail> DetailList
    { get; set; }
}

上面的

{#}就代表這樣一個Hash Value, 我想你會想到現在生成的Data Contract Name象什麼樣子:Bill_OrderHeader_OrderDetail_LZ9Dq20o 和Bill_SalesHeader_SalesDetail_LZ9Dq20o

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