在服务的内部,应用程序的功能用代码来实现.服务的外部,应用程序由WSDL来定义。WCF服务内部,应用程序数据是一些简单和复杂的类型,而在外部,服务的数据表现为XML结构定义(XSD)。WCF数据契约提供一个在.NET的CLR类型和XML结构定义之间的影射方法。
使用WCF时,开发者要花费更多的时间在代码和接口上,而只需要较少的时间考虑XSD和WSDL的定义语法。这并不是说XSD和WSDL语法不重要,反而他们是搭建跨平台系统的关键因素。但是事实证明,编译器更擅长讲.NET语言的数据结构转换为他们的XSD和WSDL形势,以满足跨平台互操作的要求。
设计时,[DataContract]属性标示哪个类应该以XSD显式并包含在WSDL中抛出。[DataMember属性]进一步明确在那个类的成员会包含在抛出的结构中。运行时,DataContractSerializer类根据[DataContract]属性和[DataMember]属性定义的规则来讲对象序列化为一个XML的形式。左下图中表示一个本地的类被转化为一个XML结构。这种转化是互操作性的标志。右下图中以C#代码和XSD的形式展示了同样的转化。
如果类型满足如下条件,则DataContractSerializer将会序列化类型,并导出他们到WSDL中:
1. 类型以[DataContract]和[DataMember]属性标记。 2. 类型具有[CollectionDataContract]属性。 3. 类型继承了IXMLSerializable接口。 4. 类型以[Serializable]属性标记,而且它的成员没有标记为[NonSerializable]。 5. 类型以[Serializable]属性标记,并且实现了ISerializable接口。 6. 类型是CLR内建的预定义的类型,如int32和string。
7. Bytes数组,日期类型,时间片,GUID,Uri,XmlQualifiedName,XmlElement和XMLNote。
8. 数组和集合,如List<T>,Dictionary<K,V>和HashTable。9. 枚举。
为.NET类定义XML结构
定义在System.Runtime.Serialization中的[DataContract]属性标示了一个类可以以XSD格式被导出到服务的WSDL中。如果一个类没有[DataContract]属性,它将不会出现在WSDL中。默认情况下,XML结构的名字与类的名字相同。结构的目标命名空间是http://schemas.datacontract.org/2004/07/ 再串联上.NET类的命名空间。这些都可以被重写。你可以通过重写,来控制暴露在服务中的名字。例如,一个内部类名字是regOrderIn,在XSD中可以被导出为Order。下面代码展示了如何重写XSD中的名字和命名空间。
1: <?xml version="1.0" encoding="utf-8"?>2: <xs:schema xmlns:tns="http://WcfServiceLibraryDataContract"3: elementFormDefault="qualified"4: targetNamespace="http://WcfServiceLibraryDataContract"5: xmlns:xs="http://www.w3.org/2001/XMLSchema">6: <xs:complexType name="StockPrice">7: <xs:sequence>8: <xs:element name="CurrentPrice" type="xs:double" />9: <xs:element name="CurrentTime" type="xs:dateTime" />10: <xs:element name="Ticker" nillable="true" type="xs:string" />11: <xs:element minOccurs="0" name="DailyVolume" type="xs:long" />12: <xs:element minOccurs="0" name="DailyChange" type="xs:double" />13: </xs:sequence>14: </xs:complexType>15: <xs:element name="StockPrice" nillable="true" type="tns:StockPrice" />16: </xs:schema>[DataMember]属性,也是定义在System.Runtime.Serialization中的。它标示了一个标记为[DataContract]的.NET类的成员是否包含在XML结构中。如果一个类的成员没有标记[DataMember]属性,它将不会包含在XML结构中。默认的,类成员不会包含在XML结构的定义中。这就使启用模式更严格。.NET类成员的访问限制,即共有或私有,不会影响它包含在XML结构中;是否包含在XML结构中,严格取决于是否有[DataMember]属性的限制。
下面代码显示一个类的定义,StockPrice。它具有5个成员。其中三个,ticker,theCurrentPrice,和theCurrentTime,是必须的。因为他们都标记为isRequired=true。其它的关于[DataMember]的技巧如下:
• 类成员的名字在代码中都具有前缀m_。类成员被重写,使m_前缀没有出现在XSD定义中。 • [DataMember]属性中定义了类成员的顺序。如果没有定义。元素会按照首字母的排列顺序出现在XSD中。顺序通常并不重要,但是互操作中的可控性是必要的。如果你发送消息到服务端,并且该服务的元素以一种期望的顺序排列着。那么这个属性可以控制元素在XML中的顺序。 • 类成员m_CurrentPrice,m_CurrentType 和 m_ticker标记为必须的。但是m_dailyVolume和m_dailyChange不是。非必要的类成员,可以不出现在XML实例中,但仍然可被视为有效的XSD依据。 1: using System;2: using System.Collections.Generic;3: using System.Linq;4: using System.Text;5: using System.Runtime.Serialization;6:
7: namespace WcfServiceLibraryDataContract8: {
9: [DataContract(Namespace = "http://WcfServiceLibraryDataContract",Name="StockPrice")]10: public class clsStockPrice11: {
12: [DataMember(Name = "CurrentPrice", Order = 0, IsRequired = true)]13: public double theCurrentPriceNow;14: [DataMember(Name = "CurrentTime", Order = 1, IsRequired = true)]15: public DateTime theCurrentTimeNow;16: [DataMember(Name = "Ticker", Order = 2, IsRequired = true)]17: public string theTickerSymbol;18: [DataMember(Name = "DailyVolume", Order = 3, IsRequired = false)]19: public long theDailyVolumeSoFar;20: [DataMember(Name = "DailyChange", Order = 4, IsRequired = false)]21: public double theDailyChangeSoFar;22: }
23: }
1: using System;2: using System.Collections.Generic;3: using System.Linq;4: using System.Runtime.Serialization;5: using System.ServiceModel;6: using System.Text;7:
8: namespace WcfServiceLibraryDataContract9: {
10: // NOTE: If you change the interface name "IStockService" here, you must also update the reference to "IStockService"// in App.config.
11: [ServiceContract]
12: public interface IStockService13: {
14: [OperationContract]
15: clsStockPrice GetPrice(string ticker);16: }
17: }
1: using System;2: using System.Collections.Generic;3: using System.Linq;4: using System.Runtime.Serialization;5: using System.ServiceModel;6: using System.Text;7:
8: namespace WcfServiceLibraryDataContract9: {
10: // NOTE: If you change the class name "StockService" here, you must also update the reference to "StockService"// in App.config.
11: public class StockService : IStockService12: {
13: #region IStockService Members14:
15: public clsStockPrice GetPrice(string ticker)16: {
17: clsStockPrice s = new clsStockPrice();18: s.theTickerSymbol = ticker;
19: s.theCurrentPriceNow = 100.00;
20: s.theCurrentTimeNow = DateTime.Now;
21: s.theDailyVolumeSoFar = 450000;
22: s.theDailyChangeSoFar = .0123456;
23: return s;24: }
25:
26: #endregion27: }
28: }
svcutil.exe –t:metadata 命令可以根据[DataMember]元素定义生成XSD。前面的XSD定义已经显示了生成的XSD格式。注意元素的名字是按照定义的顺序显示的。也要注意,非必要的类成员在XML结构中被标记为minOccures=0。
定义类的级别
在代码中,通常情况下需要实现一些复杂类型。复杂类型经常通过继承这种方法定义特定的结构。按照这种方法,一个通用的类型,如“price”可以被子类继承为更多的特定类型,如“stock price”或“house price”。WCF通过将类表现到WSDL中、在类和XML结构之间执行序列化和反序列化,以及通过将每个类的属性进一步保存到一个聚合的结构中等方法来支持类的继承。
下面代码中,Price类包含三个元素定义。同时,被一个子类StockPrice继承。我们为每个类都提供一个命名空间,这样使他们可以在XML中通过完整的命名空间名找到。每个元素都具有它自己的命名空间。
1: using System;2: using System.Collections.Generic;3: using System.Linq;4: using System.Text;5: using System.Runtime.Serialization;6:
7: namespace WcfServiceLibraryDataContract8: {
9: [DataContract(Namespace = "http://WcfServiceLibraryDataContract/Price")]10: public class clsPrice11: {
12: [DataMember]
13: public double CurrentPrice;14: [DataMember]
15: public double CurrentTime;16: [DataMember]
17: public string Currency;18: }
19: }
1: using System;2: using System.Collections.Generic;3: using System.Linq;4: using System.Text;5: using System.Runtime.Serialization;6:
7: namespace WcfServiceLibraryDataContract8: {
9: [DataContract(Namespace = "http://WcfServiceLibraryDataContract/StockPrice")]10: public class clsStockPrice:clsPrice11: {
12: [DataMember]
13: public string Ticker;14: [DataMember]
15: public long DailyVolume;16: [DataMember]
17: public double DailyChange;18: }
19: }
下面是生成的两个支持继承的XML结构。首先是Price的XML结构。然后是StockPrice的XML结构。注意,StockPrice引用了Price的结构。注意在XSD中,所有的元素的属性都标记为minOccurs=0。因为在代码中,没有属性被标记为[isRequired=true]。
1: <?xml version="1.0" encoding="utf-8"?>2: <xs:schema xmlns:tns="http://WcfServiceLibraryDataContract/Price"3: elementFormDefault="qualified"4: targetNamespace="http://WcfServiceLibraryDataContract/Price"5: xmlns:xs="http://www.w3.org/2001/XMLSchema">6: <xs:complexType name="clsPrice">7: <xs:sequence>8: <xs:element minOccurs="0" name="Currency" nillable="true" type="xs:string" />9: <xs:element minOccurs="0" name="CurrentPrice" type="xs:double" />10: <xs:element minOccurs="0" name="CurrentTime" type="xs:dateTime" />11: </xs:sequence>12: </xs:complexType>13: <xs:element name="clsPrice" nillable="true" type="tns:clsPrice" />14: </xs:schema>
1: <?xml version="1.0" encoding="utf-8"?>2: <xs:schema xmlns:tns="http://WcfServiceLibraryDataContract/StockPrice"3: elementFormDefault="qualified"4: targetNamespace="http://WcfServiceLibraryDataContract/StockPrice"5: xmlns:xs="http://www.w3.org/2001/XMLSchema">6: <xs:import namespace="http://WcfServiceLibraryDataContract/Price" />7: <xs:complexType name="clsStockPrice">8: <xs:complexContent mixed="false">9: <xs:extension xmlns:q1="http://WcfServiceLibraryDataContract/Price" base="q1:clsPrice">10: <xs:sequence>11: <xs:element minOccurs="0" name="DailyChange" type="xs:double" />12: <xs:element minOccurs="0" name="DailyVolume" type="xs:long" />13: <xs:element minOccurs="0" name="Ticker" nillable="true" type="xs:string" />14: </xs:sequence>15: </xs:extension>16: </xs:complexContent>17: </xs:complexType>18: <xs:element name="clsStockPrice" nillable="true" type="tns:clsStockPrice" />19: </xs:schema>
下面是经过序列化的StockPrice类型的SOAP体。注意,从XML结构中捕获的Price和StockPrice的命名空间被包含到了SOAP体中。
1: <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"2: xmlns:a="http://www.w3.org/2005/08/addressing"3: xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">4: <s:Header>...</s:Header>5: <s:Body u:Id="_0">6: <GetPriceResponse xmlns="http://tempuri.org/">7: <GetPriceResult xmlns:a="http://WcfServiceLibraryDataContract/StockPrice"8: xmlns:i="http://www.w3.org/2001/XMLSchema-instance">9: <Currency i:nil="true" xmlns="http://WcfServiceLibraryDataContract/Price" />10: <CurrentPrice xmlns="http://WcfServiceLibraryDataContract/Price">100</CurrentPrice>11: <CurrentTime xmlns="http://WcfServiceLibraryDataContract/Price">2009-09-08T10:38:58.0067322+08:00</CurrentTime>12: <a:DailyChange>0.0123456</a:DailyChange>13: <a:DailyVolume>450000</a:DailyVolume>14: <a:Ticker>chinasofti</a:Ticker>15: </GetPriceResult>16: </GetPriceResponse>17: </s:Body>18: </s:Envelope>
将已知类型以附加类型的方式抛出到WSDL
如果数据类型符合我们之前描述的条件,则会暴露在WSDL中。但是,有另外一种情况。你可能会强制将一个类型包含在WSDL契约中。
一个例子就是类的继承结构。如果一个序列化的子类,传递到一个期望序列化基类的端点,WCF无法知道如何反序列化这个类。因为子类不是契约的一部分。另一个例子是一个将其他类型存储为它自身的元素的HashTable类WSDL会定义hashTable类,但是不会定义hashTable 中的元素类型。
这种情况下,你必须告诉WCF哪些类应该明确包含在WSDL契约中。可以使用KnownTypes来实现上述功能。可以使用四种方式实现:通过在[DataContract]属性中加入KnownType属性,通过设置[ServiceContract]和[OperationContract]属性,通过在配置文件中添加一个指向它的引用,或者通过在生成WSDL时声明它。
下面代码显示了一个定义了基类型的数据契约——Price,和两个继承这个基类型的子类——StockPrice和MetalPrice。注意数据契约的[KnownType]属性。这就告诉WCF,当抛出契约时,在WSDL中要包含StockPricehe 和 MetalPrice的XSD结构定义。下面代码还包含一个服务的实现。GetPrice操作是多态的,它会依据具体需求返回StockPrice类型或MetalPrice类型。而通过代理调用GetPrice的客户端代码,必须将结果转换为期望的类型以访问返回值类。
1: using System;2: using System.Collections.Generic;3: using System.Linq;4: using System.Text;5: using System.Runtime.Serialization;6:
7: namespace WcfServiceLibraryDataContract8: {
9: [DataContract(Namespace = "http://WcfServiceLibraryDataContract")]10: [KnownType(typeof(clsStockPrice))]11: [KnownType(typeof(clsMetalPrice))]12: public class clsPrice13: {
14: [DataMember]
15: public double CurrentPrice;16: [DataMember]
17: public DateTime CurrentTime;18: [DataMember]
19: public string Currency;20: }
21: }
1: using System;2: using System.Collections.Generic;3: using System.Linq;4: using System.Text;5: using System.Runtime.Serialization;6:
7: namespace WcfServiceLibraryDataContract8: {
9: [DataContract(Namespace = "http://WcfServiceLibraryDataContract")]10: public class clsStockPrice:clsPrice11: {
12: [DataMember]
13: public string Ticker;14: [DataMember]
15: public long DailyVolume;16: }
17: }
1: using System;2: using System.Collections.Generic;3: using System.Linq;4: using System.Text;5: using System.Runtime.Serialization;6:
7: namespace WcfServiceLibraryDataContract8: {
9: [DataContract(Namespace = "http://WcfServiceLibraryDataContract")]10: public class clsMetalPrice:clsPrice11: {
12: [DataMember]
13: public string Metal;14: [DataMember]
15: public string Quality;16: }
17: }
1: using System;2: using System.Collections.Generic;3: using System.Linq;4: using System.Runtime.Serialization;5: using System.ServiceModel;6: using System.Text;7:
8: namespace WcfServiceLibraryDataContract9: {
10: [ServiceContract(Namespace = "http://WcfServiceLibraryDataContract/FinanceService/")]11: public interface IStockService12: {
13: [OperationContract]
14: clsPrice GetPrice(string id,string type);15: }
16: }
1: using System;2: using System.Collections.Generic;3: using System.Linq;4: using System.Runtime.Serialization;5: using System.ServiceModel;6: using System.Text;7:
8: namespace WcfServiceLibraryDataContract9: {
10: // NOTE: If you change the class name "StockService" here, you must also update the reference to "StockService" in App.config.11: [ServiceBehavior(Namespace = "http://WcfServiceLibraryDataContract/FinanceService/")]12: public class StockService : IStockService13: {
14: #region IStockService Members15:
16: clsPrice IStockService.GetPrice(string id, string type)17: {
18: if (type.Contains("Stock"))19: {
20: clsStockPrice s = new clsStockPrice();21: s.Ticker = id;
22: s.DailyVolume = 450000;
23: s.CurrentPrice = 94.15;
24: s.CurrentTime = DateTime.Now;
25: s.Currency = "USD";26: return s;27: }
28: if (type.Contains("Metal"))29: {
30: clsMetalPrice g = new clsMetalPrice();31: g.Metal = id;
32: g.Quality = "0.999";33: g.CurrentPrice = 785.00;
34: g.CurrentTime = DateTime.Now;
35: g.Currency = "USD";36: return g;37: }
38: return new clsPrice();39: }
40:
41: #endregion42: }
43: }
或者你可以在OperationContract级别上使用[ServiceKnownType]属性定义已知类型。当已知类型定义在操作级别,子类型只能在操作中使用。换句话说,不是服务中所有的方法都可以使用子类型。下面代码显示了一个代码段,使用[ServiceKnownType]属性。在这个例子中,一个客户端可以调用GetPrice方法,并且,当消息从服务端返回时,反序列化方法会创建一个StockPrice或MetalPrice对象。但是,客户端只能传递一个Price对象,而不是StockPrice或MetalPrice,来调用SetPrice。因为序列化不知道如何在XML中表现这些子类型。
1: using System;2: using System.Collections.Generic;3: using System.Linq;4: using System.Runtime.Serialization;5: using System.ServiceModel;6: using System.Text;7:
8: namespace WcfServiceLibraryDataContract9: {
10: [ServiceContract(Namespace = "http://WcfServiceLibraryDataContract/FinanceService/")]11: public interface IStockService12: {
13: [ServiceKnownType(typeof(clsStockPrice))]14: [ServiceKnownType(typeof(clsMetalPrice))]15: [OperationContract]
16: clsPrice GetPrice(string id,string type);17: [OperationContract]
18: void SetPrice(clsPrice p);19: }
20: }
在代码中定义已知类型的弊端是,无论在数据契约还是服务契约级别,你必须在编译时就知道子类型的信息。如果一个新的类型追加近来,你就必须重新编译代码。这个问题可以通过两个途径解决。
首先,你可以将已知类型的引用从代码中移到配置文件中,在配置文件的System.runtime.serialization配置节中包含相关信息。为了遵循类的层次结构,你必须添加引用到基类,然后再添加已知类型的引用到基类。如下面所示。当Price是一个基类型,StockPrice和MetalPrice是子类型。WcfServiceLibraryDataContract是宿主这些类型DLL。
1: <?xml version="1.0" encoding="utf-8" ?>2: <configuration>3: <system.web>...</system.web>4: <!-- When deploying the service library project, the content of the config file must be added to the host's5: app.config file. System.Configuration does not support config files for libraries. -->6: <system.serviceModel>...</system.serviceModel>7: <system.runtime.serialization>8: <dataContractSerializer>9: <declaredTypes>10: <add type="WcfServiceLibraryDataContract.clsPrice,11: WcfServiceLibraryDataContract,
12: Version=1.0.0.0,
13: Culture=neutral,
14: PublicKeyToken=null">15: <knownType type="WcfServiceLibraryDataContract.clsStockPrice,16: WcfServiceLibraryDataContract,
17: Version=1.0.0.0,
18: Culture=neutral,
19: PublicKeyToken=null"/>20: <knownType type="WcfServiceLibraryDataContract.clsMetalPrice,21: WcfServiceLibraryDataContract,
22: Version=1.0.0.0,
23: Culture=neutral,
24: PublicKeyToken=null"/>25: </add>26: </declaredTypes>27: </dataContractSerializer>28: </system.runtime.serialization>29: </configuration>
在契约中定义自类的解决方案通常情况下是在运行时产生。这可以归功于暴露在WCF中的挂钩。无论是[KnownType]属性还是[ServiceKnownType]属性都接受一个字符串参数。这个字符串是一个方法名。该方法可以在序列化和反序列化时返回一个已知类型的列表。如果你使用原数据存储库,你可以从原数据存储库或数据库中查找到类型信息,并在运行时暴露他们。下面代码显示一个简单的实现。类型的名字被硬编码到GetKnownTypes方法。而不是从一个外部的库中抛出。
1: using System;2: using System.Collections.Generic;3: using System.Linq;4: using System.Text;5: using System.Runtime.Serialization;6:
7: namespace WcfServiceLibraryDataContract8: {
9: [DataContract(Namespace = "http://WcfServiceLibraryDataContract")]10: public class clsPrice11: {
12: [DataMember]
13: public double CurrentPrice;14: [DataMember]
15: public DateTime CurrentTime;16: [DataMember]
17: public string Currency;18:
19: static Type[] GetKnownType()20: {
21: return new Type[] { typeof(clsStockPrice), typeof(clsMetalPrice) };22: }
23: }
24: }