《Windows Communication Foundation之旅》系列之四 )

來源:互聯網
上載者:User

六、定義DataContract

我在介紹如何定義一個ServiceContract時,舉了這樣的一個例子,代碼如下:
[ServiceContract]
public class BookTicket
{
 [OperationContract]
 public bool Check(Ticket ticket)
 {
  bool flag;
  //logic to check whether the ticket is none;
  return flag;
 }
 [OperationContract]
 private bool Book(Ticket ticket)
 {
  //logic to book the ticket
 }
}

在Service類BookTicket中,兩個服務方法Check和Book的參數均為Ticket類型。這個類型是自訂類型,根據WCF的要求,該類型必須支援序列化的操作,方才可以在服務方法中作為訊息被傳遞。

在.Net中,除了基本類型如int,long,double,以及枚舉類型和String類型外,一個自訂的類型如要支援序列化操作,應該標記該類型為[Serializable],或者使該類型實現ISerializable介面。而在WCF中,推薦的一種方式是為這些類型標記DataContractAttribute。方法如下:
[DataContract]
public class Ticket
{
private string m_movieName;

 [DataMember]
 public int SeatNo;
 [DataMember]
 public string MovieName
 {
  get {return m_movieName;}
  set {m_movieName = value;}
 }
 [DataMember]
 private DateTime Time;
}

其中,[DataMember]是針對DataContract類的成員所標記的Attribute。與服務類中的OperationContractAttribute相同,DataMemberAttribute與對象的訪問限制修飾符(public,internal,private等)沒有直接關係。即使該成員為private,只要標記了[DataMember],仍然可以被序列化。雖然DataMemberAttribute可以被施加給類型的欄位和屬性,但如果被施加到static成員時,WCF會忽略該DataMemberAttribute。

當我們為一個類型標註DataContractAttribute時,只有被顯式標註了DataMemberAttribute的成員方才支援序列化操作。這一點與SerializableAttribute大相徑庭。一個被標記了SerializableAttribute的類型,在預設情況下,其內部的成員,不管是public還是private都支援序列化,除非是那些被施加了NonSerializedAttribute的成員。DataContractAttribute採用這種顯式標註法,使得我們更加專註於服務訊息的定義,只有需要被傳遞的服務訊息成員,方才被標註DataMemberAttribute。

如果DataContract類中的DataMember成員包含了泛型,那麼泛型型別參數必須支援序列化,如下代碼所示:
[DataContract]
public class MyGeneric
{
 [DataMember]
 T theData;
}

在類MyGeneric中,泛型參數T必須支援序列化。如執行個體化該對象:
MyGeneric intObject = new MyGeneric();
MyGeneric customObject = new MyGeneric();

對象intObject由於傳入的泛型參數為int基本類型,因此可以被序列化;而對象customObject是否能被序列化,則要看傳入的泛型參數CustomType類型是否支援序列化。

DataContract以Namespace和Name來唯一標識,我們可以在DataContractAttribute的Namespace屬性、Name屬性中進行設定。如未設定DataContract的Name屬性,則預設的名字為定義的類型名。DataMember也可以通過設定Name屬性,預設的名字為定義的成員名。如下代碼所示:
namespace MyCompany.OrderProc
{
    [DataContract]
    public class PurchaseOrder
    {
        // DataMember名字為預設的Amount;
        [DataMember]
        public double Amount;
       
        // Name屬性將重寫預設的名字Ship_to,此時DataMember名為Address;
        [DataMember(Name = “Address”)]
        public string Ship_to;
    }
    //Namespace為預設值:
    // http://schemas.datacontract.org/2004/07/MyCompany.OrderProc
    //此時其名為PurchaseOrder而非MyInvoice
    [DataContract(Name = “PurchaseOrder”)]
    public class MyInvoice
    {
        // Code not shown.
    }

    // 其名為Payment而非MyPayment
    // Namespace被設定為http://schemas.example.com
    [DataContract(Name = “Payment”,
        Namespace = “http://schemas.example.com“)]
    public class MyPayment
    {
        // Code not shown.
    }
}

// 重寫MyCorp.CRM下的所有DataContract的Namespace
 [assembly:ContractNamespace(
    ClrNamespace = “MyCorp.CRM”,
    Namespace= “http://schemas.example.com/crm“)]
namespace MyCorp.CRM
{
    // 此時Namespace被設定為http://schemas.example.com/crm.
    // 名字仍然為預設值Customer
    [DataContract]
    public class Customer
    {
        // Code not shown.
    }
}

由於DataContract將被序列化以及還原序列化,因此類型中成員的順序也相當重要,在DataMemberAttribute中,提供了Order屬性,用以設定成員的順序。在WCF中對成員的序列化順序規定如下:
1、預設的順序依照字母順序;
2、如成員均通過Order屬性指定了順序,且順序值相同,則以字母順序;
3、未指定Order屬性的成員順序在指定了Order順序之前;
4、如果DataContract處於繼承體系中,則不管子類中指定的Order值如何,父類的成員順序優先。

下面的代碼很好的說明了DataMember的順序:
[DataContract]
public class BaseType
{
    [DataMember] public string zebra;
}

[DataContract]
public class DerivedType : BaseType
{
    [DataMember(Order = 0)] public string bird;
    [DataMember(Order = 1)] public string parrot;
    [DataMember] public string dog;
    [DataMember(Order = 3)] public string antelope;
    [DataMember] public string cat;
    [DataMember(Order = 1)] public string albatross;
}

序列化後的XML內容如下:
<DerivedType>
    <zebra/> 
    <cat/>
    <dog/> 
    <bird/>
    <albatross/>
    <parrot/>
    <antelope/>
</DerivedType>

因為成員zebra為父類成員,首先其順序在最前面。cat和dog未指定Order,故在指定了Order的其它成員之前,兩者又按照字母順序排列。parrot和albatross均指定Order值為1,因此也按照字母順序排列在Order值為0的bird之後。

判斷兩個DataContract是否相同,應該根據DataContract的Namespace和Name,以及DataMember的Name和Order來綜合判斷。例如下面代碼所示的類Customer和Person其實是同一個DataContract:
[DataContract]
public class Customer
{
    [DataMember]
    public string fullName;

    [DataMember]
    public string telephoneNumber;
}

[DataContract(Name=”Customer”)]
public class Person
{
    [DataMember(Name = “fullName”)]
    private string nameOfPerson;

    private string address;

    [DataMember(Name= “telephoneNumber”)]
    private string phoneNumber;
}

再例如下面代碼所示的類Coords1、Coords2、Coords3也是相同的DataContract,而類Coords4則因為順序不同,因而與前面三個類是不同的:
[DataContract(Name= “Coordinates”)]
public class Coords1
{
    [DataMember] public int X;
    [DataMember] public int Y;
}

[DataContract(Name= “Coordinates”)]
public class Coords2
{
     [DataMember] public int Y;
     [DataMember] public int X;
}

[DataContract(Name= “Coordinates”)]
public class Coords3
{
     [DataMember(Order=2)] public int Y;
     [DataMember(Order=1)] public int X;
}
[DataContract(Name= “Coordinates”)]
public class Coords4
{
     [DataMember(Order=1)] public int Y;
     [DataMember(Order=2)] public int X;
}

當DataContract處於繼承體系時,還需要注意的是對象的“多態”問題。如果在服務端與用戶端之間要傳遞訊息,經常會涉及到類型的動態綁定。根據規定,如果訊息的類型是子類類型,那麼發送訊息一方就不能傳遞基類類型。相反,如果訊息類型是父類類型,那麼發送訊息一方就可以是父類本身或者其子類。從這一點來看,WCF的規定是與物件導向思想並行不悖的。但是可能存在的問題是,當訊息類型定義為父類類型,而發送訊息一方傳遞其子類時,服務端有可能對該子類類型處於“未知”狀態,從而不能正常地還原序列化。所以,WCF為DataContract提供了KnownTypeAttribute,通過設定它來告知服務端可能存在的動態綁定類類型。

舉例來說,如果我們定義了這樣三個類:
[DataContract]
public class Shape { }

[DataContract(Name = “Circle”)]
public class CircleType : Shape { }

[DataContract(Name = “Triangle”)]
public class TriangleType : Shape { }

然後在類CompanyLogo中定義Shape類型的欄位,如下所示:
[DataContract]
public class CompanyLogo
{
    [DataMember]
    private Shape ShapeOfLogo;
    [DataMember]
    private int ColorOfLogo;
}

此時的CompanyLogo類由於正確的設定了[DataContract]和[DataMember],而Shape類型也是支援序列化的,因此該類型是可以被序列化的。然而一旦用戶端在調用CompanyLogo類型的對象時,為欄位ShapeOfLogo設定其值為CircleType或TriangleType類型的對象,就會發生還原序列化錯誤,因為服務端並不知道CircleType或TriangleType類型,從而無法進行正確的匹配。所以上述的CompanyLogo類的定義應修改如下:
[DataContract]
[KnownType(typeof(CircleType))]
[KnownType(typeof(TriangleType))]
public class CompanyLogo
{
    [DataMember]
    private Shape ShapeOfLogo;
    [DataMember]
    private int ColorOfLogo;
}

類的繼承如此,介面的實現也是同樣的道理,如下例所示:
public interface ICustomerInfo
{
    string ReturnCustomerName();
}

[DataContract(Name = “Customer”)]
public class CustomerType : ICustomerInfo
{
    public string ReturnCustomerName()
    {
        return “no name”;
    }
}

[DataContract]
[KnownType(typeof(CustomerType))]
public class PurchaseOrder
{
    [DataMember]
    ICustomerInfo buyer;

    [DataMember]
    int amount;
}

由於PurchaseOrder中定義了ICustomerInfo介面類型的欄位,如要該類能被正確的還原序列化,就必須為類PurchaseOrder加上[KnownType(typeof(CustomerType))]的標註。

對於集合類型也有相似的規定。例如Hashtable類型,其記憶體儲的均為object對象,但實際設定的值可能是一些自訂類型,此時也許要通過KnownType進行標註。例如在類LibraryCatalog中,定義了Hashtable類型的欄位theCatalog。該欄位可能會設定為Book類型和Magazine類型,假定Book類型和Magazine類型均被定義為DataContract,則類LibraryCatalog的正確定義應如下所示:
[DataContract]
[KnownType(typeof(Book))]
[KnownType(typeof(Magazine))]
public class LibraryCatalog
{
    [DataMember]
    System.Collections.Hashtable theCatalog;
}

如果在一個DataContract中,定義一個object類型的欄位。由於object類型是所有類型的父類,所以需要我們利用KnownType標明用戶端允許設定的類型。例如類MathOperationData:
[DataContract]
[KnownType(typeof(int[]))]
public class MathOperationData
{
    private object numberValue;
    [DataMember]
    public object Numbers
    {
        get { return numberValue; }
        set { numberValue = value; }
    }
    //[DataMember]
    //public Operation Operation;
}

屬性Numbers其類型為object,而KnownType設定的類型是int[],因此可以接受的類型就包括:整型,整型數組以及List類型。如下的調用都是正確的:
static void Run()
{
    MathOperationData md = new MathOperationData();

    int a = 100;
    md.Numbers = a;

    int[] b = new int[100];
    md.Numbers = b;
   
    List c = new List();
    md.Numbers = c;   
}

但如果設定Number屬性為ArrayList,即使該ArrayList對象中元素均為int對象,也是錯誤的:
static void Run()
{
    MathOperationData md = new MathOperationData();

ArrayList d = new ArrayList();
    md.Numbers = d;
}

一旦一個DataContract類型標註了KnownTypeAttribute,則該Attribute的範圍可以施加到其子類中,如下所示:
[DataContract]
[KnownType(typeof(CircleType))]
[KnownType(typeof(TriangleType))]
public class MyDrawing
{
    [DataMember]
    private object Shape;
    [DataMember]
    private int Color;
}

[DataContract]
public class DoubleDrawing : MyDrawing
{
    [DataMember]
    private object additionalShape;
}

雖然DoubleDrawing沒有標註KnowTypeAttribute,但其欄位additionalShape仍然可以被設定為CircleType類型或TriangleType類型,因為其父類已經被設定為KnowTypeAttribute。

註:KnowTypeAttribute可以標註類和結構,但不能標註介面。此外,DataContract同樣不能標註介面,僅可以標註類、結構和枚舉。要使用DataContractAttribute、DataMemberAttribute和KnownTypeAttribute,需要添加WinFx版本的System.Runtime.Serialization程式集的引用。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.