六、定义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
- }
- }
[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;
- }
[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<T>
- {
- [DataMember]
- public T theData;
- }
[DataContract] public class MyGeneric<T> { [DataMember] public T theData; }在类MyGeneric中,泛型参数T必须支持序列化。如实例化该对象:
- MyGeneric<int> intObject = new MyGeneric();
- MyGeneric<Custom> customObject = new MyGeneric();
MyGeneric<int> intObject = new MyGeneric(); MyGeneric<Custom> 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.
- }
- }
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
- // 3.0 的语法?
- [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.
- }
- }
// 3.0 的语法? [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;
- }
[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>
<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;
- }
[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(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 { }
[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;
- }
[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;
- }
[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;
- }
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] [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;
- }
[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;
- }
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;
- }
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;
- }
[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程序集的引用。