WCF学习(二):契约
(原創(chuàng):灰灰蟲的家http://hi.baidu.com/grayworm)
契約是WCF中很重要的概念。它是用一種與平臺(tái)無關(guān)的標(biāo)準(zhǔn)語法來描述WCF服務(wù)的功能。當(dāng)客戶端獲取服務(wù)端WCF服務(wù)的時(shí)候,會(huì)根據(jù)服務(wù)端聲明的契約生成客戶端契約的復(fù)本,客戶端和服務(wù)端通過契約來實(shí)現(xiàn)溝通。
一個(gè)生活中的例子:
比如KFC,它是一家快餐品牌。假設(shè)我想通過加盟的方式在我家附近開一家KFC快餐店。首先,我們要向KFC加盟代理提交加盟申請(qǐng),經(jīng)過資格申查后,和KFC簽述加盟協(xié)議。然后,我根據(jù)協(xié)議中規(guī)定的條款在我家附開了一家KFC快餐店。KFC總部為我提供方法、技術(shù)和原材料等,我每年向KFC總部交加盟費(fèi),這樣附近的朋就可以從我的KFC快餐店中獲得KFC的產(chǎn)品和服務(wù)了。
“KFC總部”就相當(dāng)于我們的WCF服務(wù)
“來吃KFC附近的朋友”相當(dāng)于要獲取WCF服務(wù)的客戶端代碼
“我開的KFC快餐店”相當(dāng)于客戶端的代理類
“我與KFC簽定的加盟協(xié)議”相當(dāng)于WCF的契約。
通過這個(gè)例子我們可以看到契約在WCF中的重要性,它就像服務(wù)端提供的“加盟協(xié)議”一樣,客戶端根據(jù)“加盟協(xié)議”中規(guī)定的要求在客戶端生成代理類(開辦加盟店),并根據(jù)加盟協(xié)議規(guī)定的權(quán)利從服務(wù)端獲取服務(wù)(獲取方法、技術(shù)和原材料等),這樣客戶端在我的加盟店里就可以直接得到KFC服務(wù)。
所以說契約是服務(wù)端與客戶端進(jìn)行信息交流的基礎(chǔ)。
在WCF中包括了四種契約:服務(wù)契約,數(shù)據(jù)契約,錯(cuò)誤契約和消息契約。在這里我們重點(diǎn)來看服務(wù)契約和數(shù)據(jù)契約。
在WCF中契約是以Attribute型式進(jìn)行聲明的。
1.用來定義服務(wù)契約的兩個(gè)Attribute:
[AttributeUsage(AttributeTargets.Interface|AttributeTargets.Class,Inherited = false)]
public sealed class ServiceContractAttribute : Attribute
{
?? public string Name
?? {get;set;}
?? public string Namespace
?? {get;set;}
?? //More members
}
[AttributeUsage(AttributeTargets.Method)]
public sealed class OperationContractAttribute : Attribute
{
?? public string Name
?? {get;set;}
?? //More members
}
2.用來定義數(shù)據(jù)契約的兩個(gè)Attribute:
[AttributeUsage(AttributeTargets.Enum | AttributeTargets.Struct | AttributeTargets.Class, Inherited = false)]
public sealed class DataContractAttribute : Attribute
{
?? public string Name
?? {get;set;}
?? public string Namespace
?? {get;set;}
}
[AttributeUsage(AttributeTargets.Field|AttributeTargets.Property, Inherited = false)]
public sealed class DataMemberAttribute : Attribute
{
?? public bool IsRequired
?? {get;set;}
?? public string Name
?? {get;set;}
?? public int Order
?? {get;set;}
}
一、服務(wù)契約:
1.服務(wù)契約的概念
我們可以在接口或者類上聲明[ServiceContract]和[OperationContract]來定義服務(wù)契約。
[ServiceContract]
interface IMyContract
{
?? [OperationContract]
?? string MyMethod(string text);
?? //MyOtherMethod方法沒有聲明[OperationContract],不會(huì)成為契約的一部份
?? string MyOtherMethod(string text);
}
class MyService : IMyContract
{
?? public string MyMethod(string text)
?? {
????? return "Hello " + text;
?? }
?? public string MyOtherMethod(string text)
?? {
????? return "Cannot call this method over WCF";
?? }
}
ServiceContract聲明用來把.NET中的接口聲明(CLR格式)映射為與平臺(tái)無關(guān)的契約聲明(XML格式),以向外界暴露服務(wù)訪問入口。ServiceContract聲明與類的訪問修飾符無關(guān),即不管接口(類)的訪問修飾符是public/private/protected/internal,只要把該接口(類)聲明為ServiceContract,該接口(類)總會(huì)變成服務(wù)契約暴露給客戶端。因?yàn)樵L問修飾符(public/private/protected/internal)定義的是在CLR中的訪問邊界,而ServiceContract定義的是在WCF中的訪問邊界。
在WCF中服務(wù)契約接口都需要顯示聲明為ServiceContract,否則,接口不會(huì)被當(dāng)成WCF契約向外界暴露。
即使我們把接口聲明為ServiceContract了,但該服務(wù)契約現(xiàn)在并不包含任何成員,我們還要在需要作為契約成員的方法上面加上OperationContractAttribute聲明。像上面的代碼中,MyMethod方法會(huì)作為IMyContract契約的成員向外界暴露,而MyOtherMethod方法則不會(huì)成為IMyContract契約的成員。
OperationContract 可以應(yīng)用在成員方法、屬性、索引器和事件上面。
2.ServiceContract的NameSpace屬性和Name屬性
在編寫WCF服務(wù)的時(shí)候,我們應(yīng)當(dāng)為每個(gè)服務(wù)契約設(shè)置NameSpace屬性,如果為服務(wù)契約指定NameSpace屬性的話,那該服務(wù)契約會(huì)默認(rèn)NameSpace="http://tempuri.org"。這里NameSpace的作用與原來CLR中NameSpace的作用一樣,都是為了定義一個(gè)命名空間,防止命名的沖突。
如:
[ServiceContract(Namespace = "http://hi.baidu.com/grayworm")]
interface IMyContract
{...}
對(duì)Internet發(fā)布的服務(wù)契約,命名空間一般使用公司的網(wǎng)址進(jìn)行命名,對(duì)于局域網(wǎng)內(nèi)發(fā)布的服務(wù)契約則沒有必要按照這種方式進(jìn)行命名,我們可以使用更有意義單詞作為NameSpace。
[ServiceContract(Namespace = "MyNamespace")]
interface IMyContract
{...}
我們還可以為服務(wù)契約指定別名。在默認(rèn)的情況下,服務(wù)契約的名稱與接口的名稱一樣,我們可以在ServiceContract聲明中使用Name屬性為服務(wù)契約指定別名。
[ServiceContract(Namespace="http://hi.baidu.com/grayworm",Name="GrayWormCaculate")]
public interface ICaculator
{
??? [OperationContract(Name="AddInt")]
??? int Add(int arg1, int arg2);
??? [OperationContract(Name="AddDouble")]
??? double Add(double arg1, double arg2);
}
測(cè)試結(jié)果:
《圖2》
從圖中我們可以看出服務(wù)的名子不再是接口的名子了。
3.服務(wù)契約中的方法重載
在面向?qū)ο蟮乃枷胫?#xff0c;我們有方法重載的概念,所謂的方法重載就是指一個(gè)類中如果兩個(gè)方法的方法名相同而方法參數(shù)不同,那這兩個(gè)參數(shù)就形成了重載
如:
interface ICalculator
{
?? int Add(int arg1,int arg2);
?? double Add(double arg1,double arg2);
}
CLR可以根據(jù)方法的能數(shù)來區(qū)分這兩個(gè)方法。而在WCF世界中這種方法名相同而參數(shù)不同的形式則會(huì)引發(fā)InvalidOperationException異常,即在WCF中不支持面向?qū)ο笾械姆椒ㄖ剌d。
如:
//這是種契約定義是錯(cuò)誤的
[ServiceContract]
interface ICalculator
{
?? [OperationContract]
?? int Add(int arg1,int arg2);
?? [OperationContract]
?? double Add(double arg1,double arg2);
}
上面這個(gè)服務(wù)契約編譯的時(shí)候是沒有問題的,因?yàn)樗螩LR的重載要求,但當(dāng)我們使用HOST發(fā)布服務(wù)的時(shí)候會(huì)產(chǎn)生下面的問題:
《圖1》
下面我們看一下如何解決ClR和WSDL中不統(tǒng)一的情況:
我們可以在OperationContract聲明上通過Name屬性為方法起別名,將來客戶端就會(huì)通過這個(gè)別名來區(qū)分不同方法的。如:
[ServiceContract]
public interface ICaculator
{
??? [OperationContract(Name="AddInt")]
??? int Add(int arg1, int arg2);
??? [OperationContract(Name="AddDouble")]
??? double Add(double arg1, double arg2);
}
這樣在客戶端會(huì)把兩個(gè)Add方法區(qū)分為AddInt和AddDouble兩個(gè)方法。測(cè)試結(jié)果如下圖:
《圖3》
4.服務(wù)契約的繼承
下面我們看一下契約的繼承。
我先聲明一個(gè)簡(jiǎn)單計(jì)算器的契約ISimpleCaculator,只能夠作加法運(yùn)算:
[ServiceContract]
public interface ISimpleCaculator
{
??? [OperationContract]
??? int Add(int arg1, int arg2);
}
我們?cè)俾暶饕粋€(gè)科學(xué)計(jì)算器的契約IScientificCaculator,派生自ISimpleCaculator,在簡(jiǎn)單計(jì)算器的功能之上還能夠做乘法運(yùn)算。
[ServiceContract]
public interface IScientificCaculator:ISimpleCaculator
{
??? [OperationContract]
??? int Mutiply(int arg1, int arg2);
}
然后我們?cè)倬帉懸粋€(gè)類實(shí)現(xiàn)IScientificCaculator接口。
public class MyCaculator : IScientificCaculator
{
??? public int Add(int arg1, int arg2)
??? {
??????? return arg1 + arg2;
??? }
??? public int Mutiply(int arg1, int arg2)
??? {
??????? return arg1 * arg2;
??? }
}
服務(wù)契約編寫完成后,我們?cè)僭谒拗鞒绦蛑信渲媒K結(jié)點(diǎn):
<service name = "MyCalculator">
?? <endpoint
????? address = "http://localhost:8001/MyCalculator/"
????? binding = "basicHttpBinding"
????? contract = "IScientificCalculator"
?? />
</service>
這樣我們就把WCF服務(wù)和宿主程序編寫好了,下一步就在客戶端添加WCF服務(wù)的引用。當(dāng)添加完對(duì)WCF服務(wù)的引用后,客戶端就會(huì)通過元數(shù)據(jù)終結(jié)點(diǎn)獲取服務(wù)契約的信息,并在客戶端生成代理代。
代理類的契約聲明代碼如下:
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="SR.IScientificCaculator")]
public interface IScientificCaculator {
???
??? [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/ISimpleCaculator/Add", ReplyAction="http://tempuri.org/ISimpleCaculator/AddResponse")]
??? int Add(int arg1, int arg2);
???
??? [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IScientificCaculator/Mutiply", ReplyAction="http://tempuri.org/IScientificCaculator/MutiplyResponse")]
??? int Mutiply(int arg1, int arg2);
}
從上面的代碼中我們看出,雖然在服務(wù)端我們編寫了兩個(gè)有繼承關(guān)系的契約,但在客戶端并沒有為我們生成兩個(gè)對(duì)應(yīng)的契約,而是只生成了一個(gè)服務(wù)契約,在這個(gè)服務(wù)契約中包含了兩個(gè)OperationContract。
這兩個(gè)OperationContract分別與服務(wù)端兩個(gè)ServiceContract中的OperationContract相對(duì)應(yīng)。由于這兩個(gè)OperationContract來源于不同的服務(wù)契約,所以在OperationContract的屬性中有Action="http://tempuri.org/ISimpleCaculator/Add"和Action="http://tempuri.org/IScientificCaculator/Mutiply"兩個(gè)屬性聲明,這個(gè)Action屬性就是映射該OperationContract服務(wù)端的OperationContract。
二、數(shù)據(jù)契約
1.數(shù)據(jù)契約的概念。
我們?cè)谶M(jìn)行WCF編程的時(shí)候,服務(wù)端程序難免會(huì)與客戶端程序之間發(fā)生數(shù)據(jù)交換,由于服務(wù)端與客戶端可能是兩種異質(zhì)運(yùn)行環(huán)境,這就需要實(shí)現(xiàn)服務(wù)端的數(shù)據(jù)類型與客戶端代理類數(shù)據(jù)類型的統(tǒng)一。
在服務(wù)器端與客戶端交換數(shù)據(jù)是通過流來實(shí)現(xiàn)的,因此在傳遞對(duì)象的時(shí)候需要我們把對(duì)象轉(zhuǎn)換到流中去,在目的地我們?cè)購牧髦邪褦?shù)據(jù)讀取出來重新生成能相應(yīng)對(duì)象,這個(gè)思想就是我們序列化的思想。在DotNET序列化中是通過Serialization聲明來標(biāo)識(shí)類允許被實(shí)例化的,這種序列化只是把數(shù)據(jù)序列化到流中去,而在WCF中不僅僅要把數(shù)據(jù)序列化到流中去還應(yīng)包含數(shù)據(jù)類型的描述。因?yàn)榭蛻舳说某绦蚩赡芘c服務(wù)器端的程序不樣而無法實(shí)現(xiàn)數(shù)據(jù)準(zhǔn)確的序列化和反序列化,比如服務(wù)器端我是用WCF開發(fā)的,而客戶端是用JavaEE開發(fā)的,現(xiàn)在需要從服務(wù)器端返回一個(gè)Dog對(duì)象給客戶端。如果只使用簡(jiǎn)單的序列化和反序列化的話,可能會(huì)產(chǎn)生問題:服務(wù)器端DotNET序列化的數(shù)據(jù)在客戶端JavaEE不能識(shí)別流的格式,無法實(shí)現(xiàn)返序列化。
數(shù)據(jù)契約的作用就是實(shí)現(xiàn)一種與平臺(tái)無關(guān)的序列化,即在序列化過程中實(shí)現(xiàn)在schema與CLR類型之間轉(zhuǎn)換。
許多內(nèi)置類型都默認(rèn)可以被序列化,但自定義類型我們就需要使用數(shù)據(jù)契約來顯式指明其可被序列化。
數(shù)據(jù)契約使用DataContract和DataMember來聲明。
DataContract:修飾可被序列化的數(shù)據(jù)類型。
DataMember:修飾可被序列化的成員,可以修飾成員變量也可以修飾屬性。
[DataContract]
struct Contact
{
?? [DataMember]
?? public string FirstName;
?? [DataMember]
?? public string LastName;
}
或者
[DataContract]
struct Contact
{
?? string m_FirstName;
?? string m_LastName;
?? [DataMember]
?? public string FirstName
?? {
????? get
????? {...}
????? set
????? {...}
??? }
?? [DataMember]
?? public string LastName
?? {
????? get
????? {...}
????? set
????? {...}
??? }
}
與服務(wù)契約一樣,使用DataContract和DataMember聲明的數(shù)據(jù)契約也與訪問修飾符(public,private,protected...)無關(guān)。
2.數(shù)據(jù)契約的傳遞
a.命名空間
服務(wù)器端定義了一個(gè)數(shù)據(jù)契約,客戶端在生成代理類的時(shí)候也會(huì)生成一個(gè)對(duì)等的數(shù)據(jù)契約的復(fù)本,但這個(gè)契約的復(fù)本和服務(wù)器端的契約還是有稍許的不同,但命名空間默認(rèn)是一樣的。
如:
服務(wù)器端的數(shù)據(jù)契約定義
namespace MyNamespace
{
?? [DataContract]
?? struct Contact
?? {...}
?? [ServiceContract]
?? interface IContactManager
?? {
????? [OperationContract]
????? void AddContact(Contact contact);
????? [OperationContract]
????? Contact[] GetContacts( );
?? }
}
傳遞到客戶端的數(shù)據(jù)契約復(fù)本為:
namespace MyNamespace
{
?? [DataContract]
?? struct Contact
?? {...}
}
[ServiceContract]
interface IContactManager
{
?? [OperationContract]
?? void AddContact(Contact contact);
?? [OperationContract]
?? Contact[] GetContacts( );
}
如果要想改變傳遞到客戶端的數(shù)據(jù)契約的命名空間,我們可以在服務(wù)器端數(shù)據(jù)契約聲明的時(shí)候加上NameSpace屬性
namespace MyNamespace
{
?? [DataContract(Namespace = "MyOtherNamespace")]
?? struct Contact
?? {...}
}
這樣傳遞到客戶端的數(shù)據(jù)契約就會(huì)變?yōu)?br /> namespace MyOtherNamespace
{
?? [DataContract]
?? struct Contact
?? {...}
}
b.DataMember聲明的使用
DataMember聲明可以加在成員變量上,也可以加在屬性上。不管怎樣使用DataMember聲明,總會(huì)在客戶端代理類中生成帶有DataMember聲明的相關(guān)屬性。
當(dāng)服務(wù)端DataContract成員變量加上DataMember聲明的時(shí)候,客戶端代理類會(huì)產(chǎn)生對(duì)應(yīng)的DataMember聲明的屬性。客戶端代理類DataMember屬性的名子與服務(wù)端DataMember成員變量名子相同,并在客戶端動(dòng)態(tài)生成對(duì)應(yīng)的成員變量,成員變量命名是在DataMember屬性名子的后面加Feild的形式。
如服務(wù)端數(shù)據(jù)契約的聲明如下:
[DataContract]
public class Book
{
?? [DataMember]
??? public string BookNO;
?? [DataMember]
??? public string BookName;
?? [DataMember]
??? public decimal BookPrice;
}
客戶端生成代理類中數(shù)據(jù)契約的聲明如下:
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")]
[System.Runtime.Serialization.DataContractAttribute(Name="Book", Namespace="http://schemas.datacontract.org/2004/07/Services")]
[System.SerializableAttribute()]
public partial class Book : object, System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged {
???
??? [System.Runtime.Serialization.OptionalFieldAttribute()]
??? private string BookNOField;
???
??? [System.Runtime.Serialization.OptionalFieldAttribute()]
??? private string BookNameField;
???
??? [System.Runtime.Serialization.OptionalFieldAttribute()]
??? private decimal BookPriceField;
???
?? [System.Runtime.Serialization.DataMemberAttribute()]
??? public string BookNO {
??????? get {...}
??????? set {...}
??????? }
??? }
???
?? [System.Runtime.Serialization.DataMemberAttribute()]
??? public string BookName {
??????? get {...}
??????? set {...}
??????? }
??? }
???
?? [System.Runtime.Serialization.DataMemberAttribute()]
??? public decimal BookPrice {
??????? get {...}
??????? set {...}
??????? }
??? }
??? //其它的屬性和方法??
}
當(dāng)服務(wù)端DataContract屬性上加上DataMember聲明的時(shí)候,客戶端代理類會(huì)產(chǎn)生對(duì)應(yīng)的DataMember聲明的屬性。客戶端代理類DataMember屬性的名子與服務(wù)端DataMember屬性名子相同,并在客戶端動(dòng)態(tài)生成對(duì)應(yīng)的成員變量,成員變量命名是在屬性名子的后面加Feild的形式。
如服務(wù)端數(shù)據(jù)契約的聲明如下:
[DataContract]
public class Book
{
??? private string _BookNO;
?? [DataMember]
??? public string BookNO
??? {
??????? get { return _BookNO; }
??????? set { _BookNO = value; }
??? }
??? private string _BookName;
?? [DataMember]
??? public string BookName
??? {
??????? get { return _BookName; }
??????? set { _BookName = value; }
??? }
??? private decimal _BookPrice;
?? [DataMember]
??? public decimal BookPrice
??? {
??????? get { return _BookPrice; }
??????? set { _BookPrice = value; }
??? }
}
客戶端生成代理類中數(shù)據(jù)契約的聲明如下:
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")]
[System.Runtime.Serialization.DataContractAttribute(Name="Book", Namespace="http://schemas.datacontract.org/2004/07/Services")]
[System.SerializableAttribute()]
public partial class Book : object, System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged {
???
??? [System.Runtime.Serialization.OptionalFieldAttribute()]
??? private string BookNOField;
???
??? [System.Runtime.Serialization.OptionalFieldAttribute()]
??? private string BookNameField;
???
??? [System.Runtime.Serialization.OptionalFieldAttribute()]
??? private decimal BookPriceField;
???
??? [System.Runtime.Serialization.DataMemberAttribute()]
??? public string BookNO {
??????? get {...}
??????? set {...}
??????? }
??? }
???
?? [System.Runtime.Serialization.DataMemberAttribute()]
??? public string BookName {
??????? get {...}
??????? set {...}
??????? }
??? }
???
??? [System.Runtime.Serialization.DataMemberAttribute()]
??? public decimal BookPrice {
??????? get {...}
??????? set {...}
??????? }
??? }
??? //其它的屬性和方法??
}
凡是被DataMember聲明修飾的屬性,必須要有g(shù)et和set訪問器,如果沒有這兩個(gè)訪問器在調(diào)用的時(shí)候會(huì)產(chǎn)生InvalidDataContractException異常信息,因?yàn)樵谛蛄谢倪^程中需要通過get和set訪問器來操作對(duì)象的數(shù)據(jù)。
要注意的是不要在成員變量和對(duì)應(yīng)屬性上都加上DataMember聲明,這樣會(huì)在客戶端代理類中產(chǎn)生重復(fù)的成員。
3.數(shù)據(jù)契約的繼承
如果兩個(gè)數(shù)據(jù)契約類之間有繼承關(guān)系,需要在這兩個(gè)類上面都加上DataContract聲明,因?yàn)镈ataContract不能被繼承。
如服務(wù)端的數(shù)據(jù)契約聲明如下:
[DataContract]
public class Pet
{
??? private string _Name;
?? [DataMember]
??? public string Name
??? {
??????? get { return _Name; }
??????? set { _Name = value; }
??? }
??? private string _Owner;
?? [DataMember]
??? public string Owner
??? {
??????? get { return _Owner; }
??????? set { _Owner = value; }
??? }
}
[DataContract]
public class Dog:Pet
{
??? private string _Race;
??? [DataMember]
??? public string Race
??? {
??????? get { return _Race; }
??????? set { _Race = value; }
??? }
}
如果Dog類中沒有聲明[DataContract]時(shí),在服務(wù)加載運(yùn)行的時(shí)候就會(huì)產(chǎn)生InvalidDataContractException異常信息。
4.已知數(shù)據(jù)類型
在C#面向?qū)ο蟮恼Z法中,可以使用子類對(duì)象來替代父類對(duì)象,但在WCF中則不允許使用這種替代。
如服務(wù)契約PetShop返回Dog數(shù)據(jù)契約來替代Pet數(shù)據(jù)契約。
[ServiceContract]
public interface IPetSop
{
??? [OperationContract]
??? Pet Sell(string owner,string race);
}
public class PetShop : IPetSop
{
??? public Pet Sell(string owner,string race)
??? {
??????? Dog dog = new Dog();
??????? dog.Name = Guid.NewGuid().ToString();
??????? dog.Owner = owner;
??????? dog.Race = race;
??????? return dog;
??? }
}
在面向?qū)ο笳Z法中這種寫法完全正確,但在面向服務(wù)的語法中這種寫法是錯(cuò)誤的,在客戶端調(diào)用的時(shí)候會(huì)產(chǎn)生如下錯(cuò)誤:
《圖4》
在WCF中為什么會(huì)產(chǎn)生這種怪異的顯象呢?因?yàn)樵赪CF服務(wù)中產(chǎn)生的對(duì)象并不是直接被客戶端進(jìn)行使用的,而是根據(jù)數(shù)據(jù)契約,先把服務(wù)端產(chǎn)生的對(duì)象序列化,然后通過信道發(fā)送給客戶端,在客戶端再根據(jù)契約反序列化對(duì)象然后再獲取數(shù)據(jù)。在這個(gè)例子中,在服務(wù)器端序列化的Pet對(duì)象是Dog對(duì)象,而在客戶端卻只了解Pet數(shù)據(jù)契約,不知道Dog數(shù)據(jù)契約,所以在反序列化的時(shí)候會(huì)產(chǎn)生異常。
這個(gè)問題不僅僅是個(gè)返回類型或參數(shù)類型的問題,而是涉及到WCF數(shù)據(jù)契約究竟能否實(shí)現(xiàn)“多態(tài)性”的問題。為了解決這個(gè)問題,我們引入了“已知數(shù)據(jù)類型屬性”這個(gè)概念?!耙阎獢?shù)據(jù)類型屬性”就是在父類中注冊(cè)一下派生自它的子類。
《天龍八部》節(jié)選:
......
段延慶冷笑道:“順我者昌,逆我者亡”!提起鋼杖,便向段譽(yù)胸口戳了下去。
忽聽得一個(gè)女子的聲音說到:“天龍寺外,菩提樹下,化學(xué)邋遢,觀音長發(fā)!”
段延慶聽到“天龍寺外”四字時(shí),鋼杖凝在半空不動(dòng),待聽完這四句話,那鋼杖竟不住顫動(dòng),慢慢縮了回來。他一回頭,與刀白鳳的目光相對(duì),只見她眼色中似有千言萬語欲待吐露。段延慶心頭大震,顫聲道:“觀……觀世音菩薩……”
刀白鳳點(diǎn)了點(diǎn)頭,低聲道:“你……你可知這孩子是誰?”
段延慶腦子中一陣暈眩,瞧出來一片模糊,似乎是回到了二十多年前的一個(gè)月圓之夜......
......
看過《天龍八部》的朋友應(yīng)當(dāng)對(duì)這個(gè)情節(jié)不陌生。這個(gè)例子雖然有些“少兒不宜”,但我感常見它能夠比較好地解釋“已知數(shù)據(jù)類型”的作用。這個(gè)例子中,刀白鳳的那四句話,就是向父類(段延慶)注冊(cè)了一個(gè)子類(段譽(yù))為已知數(shù)據(jù)類型。
a.在父類數(shù)據(jù)契約上使用KnownTypeAttribute
“已知數(shù)據(jù)類型”可以在數(shù)據(jù)契約的父類上加上[KnownType(Type t)]這個(gè)Attribute來實(shí)現(xiàn)的。代碼如下:
[DataContract]
[KnownType(typeof(Dog))]
public class Pet
{
??? ...
}
[DataContract]
public class Dog:Pet
{
??? ...
}
運(yùn)行效果:
《圖5》
這是因?yàn)樵诳蛻舳松纱眍惖臅r(shí)候,會(huì)根據(jù)[KnownType(typeof(Dog))]聲明在客戶端同時(shí)生成Pet契約和Dog契約,這樣客戶端可以識(shí)點(diǎn)Dog對(duì)象,能對(duì)其反序列化。
《圖6》
b.在父類數(shù)據(jù)契約的方法中使用ServiceKnownTypeAttribute
上面使用KnownTypeAttribute可以解決子類對(duì)象替代父類對(duì)象的問題,但它是定義在數(shù)據(jù)契約類的級(jí)別上的,所定義的范疇有些大。
這里我們可以使用ServiceKnownTypeAttribute。ServiceKnownTypeAttribute是定義在服務(wù)契約的方法契約級(jí)別上的,只有當(dāng)前方法契約可以識(shí)別子類Dog,其它方法中無法識(shí)別Dog。
[ServiceContract]
public interface IPetSop
{
??? [OperationContract]
??? [ServiceKnownType(typeof(Dog))]
??? Pet Sell(string owner,string race);
}
public class PetShop : IPetSop
{
??? public Pet Sell(string owner,string race)
??? {
??????? Dog dog = new Dog();
??????? dog.Name = Guid.NewGuid().ToString();
??????? dog.Owner = owner;
??????? dog.Race = race;
??????? return dog;
??? }
}
當(dāng)[ServiceKnownType(Type t)]被聲明在服務(wù)契約的級(jí)別上時(shí),當(dāng)前服務(wù)契約中的任何方法都可以識(shí)別它所指定的子類。
[ServiceContract]
[ServiceKnownType(typeof(Dog))]
public interface IPetSop
{
??? [OperationContract]
??? Pet Sell(string owner,string race);
}
public class PetShop : IPetSop
{
??? public Pet Sell(string owner,string race)
??? {
??????? Dog dog = new Dog();
??????? dog.Name = Guid.NewGuid().ToString();
??????? dog.Owner = owner;
??????? dog.Race = race;
??????? return dog;
??? }
}
注:
1.不要把ServiceKnowntypeAttribute聲明加在服務(wù)類本身上,但可以把ServiceKnowntypeAttribute聲明加在接口服務(wù)契約中。
2.KnownTypeAttribute(Type t)是用在數(shù)據(jù)契約中的,而ServiceKnowntypeAttribute(Type t)是用在服務(wù)契約中的。
c.使用多個(gè)“已知數(shù)據(jù)類型”
我們可以使用多個(gè)KnownTypeAttribute或ServiceKnowntypeAttribute來告訴WCF識(shí)別多個(gè)子類
服務(wù)端代碼:
[DataContract]
class Contact
{...}
[DataContract]
class Customer : Contact
{...}
[DataContract]
class Person : Contact
{...}
[ServiceContract]
[ServiceKnownType(typeof(Customer))]
[ServiceKnownType(typeof(Person))]
interface IContactManager
{...}
在這個(gè)例子中有兩層繼承關(guān)系,在服務(wù)契約IContactManager上我們需要把兩級(jí)子類都聲明為“已知數(shù)據(jù)類型”,否則會(huì)產(chǎn)生異常。
4.使用配置文件指定“已知數(shù)據(jù)類型”
上面我們可以為WCF服務(wù)設(shè)置“已知數(shù)據(jù)類型”,但是當(dāng)需要把一個(gè)新的子類添加為“已知數(shù)據(jù)類型”時(shí),就需要我們對(duì)現(xiàn)有WCF服務(wù)進(jìn)行修改源代碼、重新編譯、重新布署等操作,為我們服務(wù)的可擴(kuò)展性大打折扣。為了避免這種問題的出現(xiàn),WCF允許我們把“已知數(shù)據(jù)類型”配置在宿主程序的配置文件中。
配置代碼如下:
<configuration>
??? <system.runtime.serialization>
??????? <dataContractSerializer>
??????????? <declaredTypes>
??????????????? <add type = "Services.Pet,Services">
??????????????????? <knownType type = "Services.Dog,Services"/>
??????????????? </add>
??????????? </declaredTypes>
??????? </dataContractSerializer>
??? </system.runtime.serialization>
這樣在客戶端生成代理類的時(shí)候會(huì)根據(jù)宿主程序上面的配置文件,在客戶端代理類中生成對(duì)應(yīng)的Pet數(shù)據(jù)契約復(fù)本和Dog數(shù)據(jù)契約復(fù)本,并在Pet數(shù)據(jù)契約復(fù)本上加上了KnownTypeAttribute聲明。
5.枚舉類型
枚舉類型默認(rèn)會(huì)自動(dòng)被序列化,枚舉類型的值也會(huì)自動(dòng)被包含在數(shù)據(jù)契約中,所以沒有必要在枚舉類型上加DataContractAttribute,直接在服務(wù)契約中使用就可以了。
在服務(wù)契約中使用枚舉類型:
enum ContactType
{
?? Customer,
?? Vendor,
?? Partner
}
[DataContract]
struct Contact
{
?? [DataMember]
?? public ContactType ContactType;
?? [DataMember]
?? public string FirstName;
?? [DataMember]
?? public string LastName;
}
當(dāng)然我們也可以顯式地把枚舉類型聲明為數(shù)據(jù)契約,首先需要在枚舉上聲明DataContractAttribute,然后再在枚舉值上加上EnumMemberAttribute 聲明,而沒明顯示聲明為EnumMemberAttribute的枚舉值將不會(huì)包含在該數(shù)據(jù)契約中。
如:
[DataContract]
enum ContactType
{
[EnumMember(Value = "MyCustomer")]
?? Customer,
?? [EnumMember]
?? Vendor,
?? //由于沒加EnumMemberAttribute聲明,Partner不會(huì)是數(shù)據(jù)契約中的成員
?? Partner
}
在客戶端生成的代理類的數(shù)據(jù)契約聲明如下:
enum ContactType
{
?? MyCustomer,
?? Vendor
}
(原創(chuàng):灰灰蟲的家http://hi.baidu.com/grayworm)
總結(jié)
以上是生活随笔為你收集整理的WCF学习(二):契约的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 浅谈Unity中的rotation和Qu
- 下一篇: 计算机博士毕业致谢,这篇博士论文《致谢》