zoukankan      html  css  js  c++  java
  • Windows Communication Foundation介绍(四)

    六、定义DataContract



    我在介绍如何定义一个ServiceContract时,举了这样的一个例子,代码如下:
    1. [ServiceContract]  
    2. public class BookTicket  
    3. {  
    4.    [OperationContract]  
    5.    public bool Check(Ticket ticket)  
    6.    {  
    7.       bool flag;  
    8.       //logic to check whether the ticket is none;  
    9.       return flag;  
    10.    }  
    11.    [OperationContract]  
    12.    private bool Book(Ticket ticket)  
    13.    {  
    14.       //logic to book the ticket  
    15.    }  
    16. }  
    在Service类BookTicket中,两个服务方法Check和Book的参数均为Ticket类型。这个类型是自定义类型,根据WCF的要求,该类型必须支持序列化的操作,方才可以在服务方法中作为消息被传递。

    在. Net中,除了基本类型如int,long,double,以及枚举类型和String类型外,一个自定义的类型如要支持序列化操作,应该标记该类型为 [Serializable],或者使该类型实现ISerializable接口。而在WCF中,推荐的一种方式是为这些类型标记 DataContractAttribute。方法如下:
    1. [DataContract]  
    2. public class Ticket  
    3. {  
    4. private string m_movieName;  
    5.   
    6.    [DataMember]  
    7.    public int SeatNo;  
    8.    [DataMember]  
    9.    public string MovieName  
    10.    {  
    11.       get {return m_movieName;}  
    12.       set {m_movieName = value;}  
    13.    }  
    14.    [DataMember]  
    15.    private DateTime Time;  
    16. }  
    其 中,[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成员包含了泛型,那么泛型类型参数必须支持序列化,如下代码所示:
    1. [DataContract]  
    2. public class MyGeneric<T>  
    3. {  
    4.     [DataMember]  
    5.     public T theData;  
    6. }  
    在类MyGeneric中,泛型参数T必须支持序列化。如实例化该对象:
    1. MyGeneric<int> intObject = new MyGeneric();  
    2. MyGeneric<Custom> customObject = new MyGeneric();  
    对象intObject由于传入的泛型参数为int基本类型,因此可以被序列化;而对象customObject是否能被序列化,则要看传入的泛型参数CustomType类型是否支持序列化。

    DataContract 以Namespace和Name来唯一标识,我们可以在DataContractAttribute的Namespace属性、Name属性中进行设置。 如未设置DataContract的Name属性,则默认的名字为定义的类型名。DataMember也可以通过设置Name属性,默认的名字为定义的成 员名。如下代码所示:
    1. namespace MyCompany.OrderProc  
    2. {  
    3.     [DataContract]  
    4.     public class PurchaseOrder  
    5.     {  
    6.         // DataMember名字为默认的Amount;  
    7.         [DataMember]  
    8.         public double Amount;  
    9.          
    10.         // Name属性将重写默认的名字Ship_to,此时DataMember名为Address;  
    11.         [DataMember(Name = "Address")]  
    12.         public string Ship_to;  
    13.     }  
    14.     //Namespace为默认值:  
    15.     // http:schemas.datacontract.org/2004/07/MyCompany.OrderProc  
    16.     //此时其名为PurchaseOrder而非MyInvoice  
    17.     [DataContract(Name = "PurchaseOrder")]  
    18.     public class MyInvoice  
    19.     {  
    20.         // Code not shown.  
    21.     }  
    22.   
    23.     // 其名为Payment而非MyPayment  
    24.     // Namespace被设置为http:schemas.example.com/  
    25.     [DataContract(Name = "Payment",  
    26.         Namespace = "http:schemas.example.com/")]  
    27.     public class MyPayment  
    28.     {  
    29.         // Code not shown.  
    30.     }  
    31. }  
    // 重写MyCorp.CRM下的所有DataContract的Namespace
    1. // 3.0 的语法?  
    2. [assembly:ContractNamespace(  
    3.       ClrNamespace = "MyCorp.CRM",  
    4.       Namespace= "http:schemas.example.com/crm")]  
    5. namespace MyCorp.CRM  
    6. {  
    7.     // 此时Namespace被设置为http:schemas.example.com/crm.  
    8.     // 名字仍然为默认值Customer  
    9.     [DataContract]  
    10.     public class Customer  
    11.     {  
    12.         // Code not shown.  
    13.     }  
    14. }  
    由于DataContract将被序列化以及反序列化,因此类型中成员的顺序也相当重要,在DataMemberAttribute中,提供了Order属性,用以设置成员的顺序。在WCF中对成员的序列化顺序规定如下:
    1、默认的顺序依照字母顺序;
    2、如成员均通过Order属性指定了顺序,且顺序值相同,则以字母顺序;
    3、未指定Order属性的成员顺序在指定了Order顺序之前;
    4、如果DataContract处于继承体系中,则不管子类中指定的Order值如何,父类的成员顺序优先。

    下面的代码很好的说明了DataMember的顺序:
    1. [DataContract]  
    2. public class BaseType  
    3. {  
    4.     [DataMember] public string zebra;  
    5. }  
    6.   
    7. [DataContract]  
    8. public class DerivedType : BaseType  
    9. {  
    10.     [DataMember(Order = 0)] public string bird;  
    11.     [DataMember(Order = 1)] public string parrot;  
    12.     [DataMember] public string dog;  
    13.     [DataMember(Order = 3)] public string antelope;  
    14.     [DataMember] public string cat;  
    15.     [DataMember(Order = 1)] public string albatross;  
    16. }  
    序列化后的XML内容如下:
    1. <DerivedType>  
    2.     <zebra/>  
    3.     <cat/>  
    4.     <dog/>  
    5.     <bird/>  
    6.     <albatross/>  
    7.     <parrot/>  
    8.     <antelope/>  
    9. </DerivedType>  
    因为成员zebra为父类成员,首先其顺序在最前面。cat和dog未指定Order,故在指定了Order的其它成员之前,两者又按照字母顺序排列。parrot和albatross均指定Order值为1,因此也按照字母顺序排列在Order值为0的bird之后。

    判断两个DataContract是否相同,应该根据DataContract的Namespace和Name,以及DataMember的Name和Order来综合判断。例如下面代码所示的类Customer和Person其实是同一个DataContract:
    1. [DataContract]  
    2. public class Customer  
    3. {  
    4.     [DataMember]  
    5.     public string fullName;  
    6.   
    7.     [DataMember]  
    8.     public string telephoneNumber;  
    9. }  
    10.   
    11. [DataContract(Name=”Customer”)]  
    12. public class Person  
    13. {  
    14.     [DataMember(Name = "fullName")]  
    15.     private string nameOfPerson;  
    16.   
    17.     private string address;  
    18.   
    19.     [DataMember(Name= "telephoneNumber")]  
    20.     private string phoneNumber;  
    21. }  
    再例如下面代码所示的类Coords1、Coords2、Coords3也是相同的DataContract,而类Coords4则因为顺序不同,因而与前面三个类是不同的:
    1. [DataContract(Name= "Coordinates")]  
    2. public class Coords1  
    3. {  
    4.     [DataMember] public int X;  
    5.     [DataMember] public int Y;  
    6. }  
    7.   
    8. [DataContract(Name= "Coordinates")]  
    9. public class Coords2  
    10. {  
    11.      [DataMember] public int Y;  
    12.      [DataMember] public int X;  
    13. }  
    14.   
    15. [DataContract(Name= "Coordinates")]  
    16. public class Coords3  
    17. {  
    18.      [DataMember(Order=2)] public int Y;  
    19.      [DataMember(Order=1)] public int X;  
    20. }  
    21. [DataContract(Name= "Coordinates")]  
    22. public class Coords4  
    23. {  
    24.      [DataMember(Order=1)] public int Y;  
    25.      [DataMember(Order=2)] public int X;  
    26. }  
    当DataContract 处于继承体系时,还需要注意的是对象的“多态”问题。如果在服务端与客户端之间要传递消息,经常会涉及到类型的动态绑定。根据规定,如果消息的类型是子类 类型,那么发送消息一方就不能传递基类类型。相反,如果消息类型是父类类型,那么发送消息一方就可以是父类本身或者其子类。从这一点来看,WCF的规定是 与面向对象思想并行不悖的。但是可能存在的问题是,当消息类型定义为父类类型,而发送消息一方传递其子类时,服务端有可能对该子类类型处于“未知”状态, 从而不能正常地反序列化。所以,WCF为DataContract提供了KnownTypeAttribute,通过设置它来告知服务端可能存在的动态绑 定类类型。

    举例来说,如果我们定义了这样三个类:
    1. [DataContract]  
    2. public class Shape { }  
    3.   
    4. [DataContract(Name = "Circle")]  
    5. public class CircleType : Shape { }  
    6.   
    7. [DataContract(Name = "Triangle")]  
    8. public class TriangleType : Shape { }  
    然后在类CompanyLogo中定义Shape类型的字段,如下所示:
    1. [DataContract]  
    2. public class CompanyLogo  
    3. {  
    4.     [DataMember]  
    5.     private Shape ShapeOfLogo;  
    6.     [DataMember]  
    7.     private int ColorOfLogo;  
    8. }  
    此 时的CompanyLogo类由于正确的设置了[DataContract]和[DataMember],而Shape类型也是支持序列化的,因此该类型 是可以被序列化的。然而一旦客户端在调用CompanyLogo类型的对象时,为字段ShapeOfLogo设置其值为CircleType或 TriangleType类型的对象,就会发生反序列化错误,因为服务端并不知道CircleType或TriangleType类型,从而无法进行正确 的匹配。所以上述的CompanyLogo类的定义应修改如下:
    1. [DataContract]  
    2. [KnownType(typeof(CircleType))]  
    3. [KnownType(typeof(TriangleType))]  
    4. public class CompanyLogo  
    5. {  
    6.     [DataMember]  
    7.     private Shape ShapeOfLogo;  
    8.     [DataMember]  
    9.     private int ColorOfLogo;  
    10. }  
    类的继承如此,接口的实现也是同样的道理,如下例所示:
    1. public interface ICustomerInfo  
    2. {  
    3.     string ReturnCustomerName();  
    4. }  
    5.   
    6. [DataContract(Name = "Customer")]  
    7. public class CustomerType : ICustomerInfo  
    8. {  
    9.     public string ReturnCustomerName()  
    10.     {  
    11.         return "no name";  
    12.     }  
    13. }  
    14.   
    15. [DataContract]  
    16. [KnownType(typeof(CustomerType))]  
    17. public class PurchaseOrder  
    18. {  
    19.     [DataMember]  
    20.     ICustomerInfo buyer;  
    21.   
    22.     [DataMember]  
    23.     int amount;  
    24. }  
    由于PurchaseOrder中定义了ICustomerInfo接口类型的字段,如要该类能被正确的反序列化,就必须为类PurchaseOrder加上[KnownType(typeof(CustomerType))]的标注。

    对 于集合类型也有相似的规定。例如Hashtable类型,其内存储的均为object对象,但实际设置的值可能是一些自定义类型,此时也许要通过 KnownType进行标注。例如在类LibraryCatalog中,定义了Hashtable类型的字段theCatalog。该字段可能会设置为 Book类型和Magazine类型,假定Book类型和Magazine类型均被定义为DataContract,则类LibraryCatalog的 正确定义应如下所示:
    1. [DataContract]  
    2. [KnownType(typeof(Book))]  
    3. [KnownType(typeof(Magazine))]  
    4. public class LibraryCatalog  
    5. {  
    6.     [DataMember]  
    7.     System.Collections.Hashtable theCatalog;  
    8. }  
    如果在一个DataContract中,定义一个object类型的字段。由于object类型是所有类型的父类,所以需要我们利用KnownType标明客户端允许设置的类型。例如类MathOperationData:
    1. [DataContract]  
    2. [KnownType(typeof(int[]))]  
    3. public class MathOperationData  
    4. {  
    5.     private object numberValue;  
    6.     [DataMember]  
    7.     public object Numbers  
    8.     {  
    9.         get { return numberValue; }  
    10.         set { numberValue = value; }  
    11.     }  
    12.     //[DataMember]  
    13.     //public Operation Operation;  
    14. }  
    属性Numbers其类型为object,而KnownType设置的类型是int[],因此可以接受的类型就包括:整型,整型数组以及List类型。如下的调用都是正确的:
    1. static void Run()  
    2. {  
    3.     MathOperationData md = new MathOperationData();  
    4.   
    5.     int a = 100;  
    6.     md.Numbers = a;  
    7.   
    8.     int[] b = new int[100];  
    9.     md.Numbers = b;  
    10.      
    11.     List c = new List();  
    12.     md.Numbers = c;     
    13. }  
    但如果设置Number属性为ArrayList,即使该ArrayList对象中元素均为int对象,也是错误的:
    1. static void Run()  
    2. {  
    3.     MathOperationData md = new MathOperationData();  
    4.   
    5. ArrayList d = new ArrayList();  
    6.     md.Numbers = d;  
    7. }  
    一旦一个DataContract类型标注了KnownTypeAttribute,则该Attribute的作用域可以施加到其子类中,如下所示:
    1. [DataContract]  
    2. [KnownType(typeof(CircleType))]  
    3. [KnownType(typeof(TriangleType))]  
    4. public class MyDrawing  
    5. {  
    6.     [DataMember]  
    7.     private object Shape;  
    8.     [DataMember]  
    9.     private int Color;  
    10. }  
    11.   
    12. [DataContract]  
    13. public class DoubleDrawing : MyDrawing  
    14. {  
    15.     [DataMember]  
    16.     private object additionalShape;  
    17. }  
    虽然DoubleDrawing没有标注KnowTypeAttribute,但其字段additionalShape仍然可以被设置为CircleType类型或TriangleType类型,因为其父类已经被设置为KnowTypeAttribute。

    注: KnowTypeAttribute可以标注类和结构,但不能标注接口。此外,DataContract同样不能标注接口,仅可以标注类、结构和枚举。要 使用DataContractAttribute、DataMemberAttribute和KnownTypeAttribute,需要添加WinFx 版本的System.Runtime.Serialization程序集的引用。
  • 相关阅读:
    2019 Java 第四周总结
    2019第三周总结
    Java 第二周总结
    2019春第十二周作业
    Day3
    Day3
    Day3
    Day3
    Day2
    Day2
  • 原文地址:https://www.cnblogs.com/sunjt/p/1109557.html
Copyright © 2011-2022 走看看