三、WCF的技術(shù)要素
作為基于SOA(Service Oriented Architecture)的一個(gè)框架產(chǎn)品,WCF最重要的就是能夠快捷的創(chuàng )建一個(gè)服務(wù)(Service)。如下圖所示,一個(gè)WCF Service由下面三部分構成:

1、Service Class:一個(gè)標記了[ServiceContract]Attribute的類(lèi),在其中可能包含多個(gè)方法。除了標記了一些WCF特有的Attribute外,這個(gè)類(lèi)與一般的類(lèi)沒(méi)有什么區別。
2、Host(宿主):可以是應用程序,進(jìn)程如Windows Service等,它是WCF Service運行的環(huán)境。
3、Endpoints:可以是一個(gè),也可以是一組,它是WCF實(shí)現通信的核心要素。
WCF Service由一個(gè)Endpoints集合組成,每個(gè)Endpoint就是用于通信的入口,客戶(hù)端和服務(wù)端通過(guò)Endpoint交換信息,如下圖所示:

從圖中我們可以看到一個(gè)Endpoint由三部分組成:Address,Binding,Contract。便于記憶,我們往往將這三部分稱(chēng)為是Endpoint的ABCs。
Address是Endpoint的網(wǎng)絡(luò )地址,它標記了消息發(fā)送的目的地。Binding描述的是如何發(fā)送消息,例如消息發(fā)送的傳輸協(xié)議(如TCP,HTTP),安全(如SSL,SOAP消息安全)。Contract則描述的是消息所包含的內容,以及消息的組織和操作方式,例如是one-way,duplex和request/reply。所以Endpoint中的ABCs分別代表的含義就是:where,how,what。當WCF發(fā)送消息時(shí),通過(guò)address知道消息發(fā)送的地址,通過(guò)binding知道怎樣來(lái)發(fā)送它,通過(guò)contract則知道發(fā)送的消息是什么。
在WCF中,類(lèi)ServiceEndpoint代表了一個(gè)Endpoint,在類(lèi)中包含的EndpointAddress,Binding,ContractDescription類(lèi)型分別對應Endpoint的Address,Binding,Contract,如下圖:

EndpointAddress類(lèi)又包含URI,Identity和可選的headers集合組成,如下圖:

Endpoint安全的唯一性識別通常是通過(guò)其URI的值,但為了避免一些特殊情況造成URI的重復,又引入了Identity附加到URI上,保證了Endpoint地址的唯一性。至于可選的AddressHeader則提供了一些附加的信息,尤其是當多個(gè)Endpoint在使用同樣的URI地址信息時(shí),AddressHeader就非常必要了。
Binding類(lèi)包含Name,Namespace和BindingElement集合,如下圖:

Binding的Name以及Namespace是服務(wù)元數據(service’s metadata)的唯一標識。BindingElement描述的是WCF通信時(shí)binding的方式。例如,SecurityBindingElement表示Endpoint使用SOAP消息安全方式,而ReliableSessionBindingElement表示Endpoint利用可信賴(lài)消息確保消息的傳送。TcpTransportBindingElement則表示Endpoint利用TCP作為通信的傳輸協(xié)議。每種BindingElement還有相應的屬性值,進(jìn)一步詳細的描述WCF通信的方式。
BindingElement的順序也非常重要。BindingElement集合通常會(huì )創(chuàng )建一個(gè)用于通信的堆棧,其順序與BindingElement集合中元素順序一致。集合中最后一個(gè)binding element對應于通信堆棧的底部,而集合中的第一個(gè)binding element則對應于堆棧的頂端。入消息流的方向是從底部經(jīng)過(guò)堆棧向上,而出消息流的方向則從頂端向下。因此,BindingElement集合中的binding element順序直接影響了通信堆棧處理消息的順序。幸運的是,WCF已經(jīng)提供了一系列預定義的Binding,能夠滿(mǎn)足大多數情況,而不需要我們自定義Binding,殫精竭慮地考慮binding element的順序。
Contract是一組操作(Operations)的集合,該操作定義了Endpoint通信的內容,每個(gè)Operation都是一個(gè)簡(jiǎn)單的消息交換(message exchange),例如one-way或者request/reply消息交換。
類(lèi)ContractDescription用于描述WCF的Contracts以及它們的操作operations。在ContractDescription類(lèi)中,每個(gè)Contract的operation都有相對應的OperationDescription,用于描述operation的類(lèi)型,例如是one-way,還是request/reply。在OperationDescription中還包含了MessageDecription集合用于描述message。
在WCF編程模型中,ContractDescription通常是在定義Contract的接口或類(lèi)中創(chuàng )建。對于這個(gè)接口或類(lèi)類(lèi)型,標記以ServiceContractAttribute,而其Operation方法則標記以OperationContractAttribute。當然我們也可以不利用CLR的attribute,而采用手工創(chuàng )建。
與Binding一樣,每個(gè)Contract也包含有Name和Namespace,用于在Service的元數據中作為唯一性識別。此外,Contract中還包含了ContractBehavior的集合,ContractBehavior類(lèi)型可以用于修改或擴展contract的行為。類(lèi)ContractDescription的組成如下圖所示:

正如在ContractDescription中包含的IContractBehavior一樣,WCF專(zhuān)門(mén)提供了行為Behavior,它可以對客戶(hù)端和服務(wù)端的一些功能進(jìn)行修改或者擴展。例如ServiceMetadataBehavior用于控制Service是否發(fā)布元數據。相似的,security behavior用于控制安全與授權,transaction behavior則控制事務(wù)。
除了前面提到的ContractBehavior,還包括ServiceBehavior和ChannelBehaivor。ServiceBehavior實(shí)現了IServiceBehavior接口,ChannelBehaivor則實(shí)現了IChannleBehavior接口。
由于WCF需要管理的是服務(wù)端與客戶(hù)端的通信。對于服務(wù)端,WCF提供了類(lèi)ServiceDescription用于描述一個(gè)WCF Service,;而針對客戶(hù)端,WCF管理的是發(fā)送消息時(shí)需要使用到的通道Channel,類(lèi)ChannelDescription描述了這樣的客戶(hù)端通道。
ServiceDescription類(lèi)的組成如下圖所示:

我們可以利用代碼的方式創(chuàng )建ServiceDescription對象,也可以利用WCF的Attribute,或者使用工具SvcUtil.exe。雖然可以顯式的創(chuàng )建它,但通常情況下,它會(huì )作為運行中的Service一部分而被隱藏于后(我在后面會(huì )提到)。
ChannelDescription類(lèi)的組成與ServiceDescription大致相同,但它僅僅包含了一個(gè)ServiceEndpoint,用于表示客戶(hù)端通過(guò)通道通信的目標Endpoint。當然,施加到ChannelDescription的Behavior也相應的為IChannelBehavior接口類(lèi)型,如圖所示:

定義一個(gè)WCF Service非常簡(jiǎn)單,以Hello World為例,定義的Service可能如下:
using System.ServiceModel
[ServiceContract]
public class HelloWorld
{
[OperationContract]
public void Hello()
{
Console.WriteLine(“Hello World!”);
}
}
System.ServiceModel是微軟為WCF提供的一個(gè)新的類(lèi)庫,以用于面向服務(wù)的程序設計。在開(kāi)發(fā)WCF應用程序時(shí),需要先添加對System.ServiceModel的引用。WCF中的大部分類(lèi)和接口也都是在命名空間System.ServiceModel下。
我們?yōu)镠elloWorld類(lèi)標記了[ServiceContract],這就使得該類(lèi)成為了一個(gè)WCF Service,而其中的方法Hello()則因為標記了[OperationContract],而成為該Service的一個(gè)Operation。
不過(guò)WCF推薦的做法是將接口定義為一個(gè)Service,這使得WCF Service具有更好的靈活性,畢竟對于一個(gè)接口而言,可以在同時(shí)有多個(gè)類(lèi)實(shí)現該接口,這也就意味著(zhù)可以有多個(gè)Service Contract的實(shí)現。那么上面的例子就可以修改為:
[ServiceContract]
public interface IHello
{
[OperationContract]
void Hello();
}
而類(lèi)HelloWorld則實(shí)現該IHello接口:
public class HelloWorld:IHello
{
public void Hello()
{
Console.WriteLine(“Hello World!”);
}
}
注意在實(shí)現了IHello接口的類(lèi)HelloWorld中,不再需要在類(lèi)和方法中標注ServiceContractAttribute和OperationContractAttribute了。
前面我已經(jīng)提過(guò),一個(gè)WCF Service必須有host作為它運行的環(huán)境。這個(gè)host可以是ASP.Net,可以是Windows Service,也可以是一個(gè)普通的應用程序,例如控制臺程序。下面就是一個(gè)Host的實(shí)現:
using System.ServiceModel
public class HostApp
{
static void Main(string[] args)
{
ServiceHost host = new ServiceHost(typeof(HelloWorld), new Uri(“http://localhost:8080/HelloService”));
host.AddServiceEndpoint(typeof(IHello), new BasicHttpBinding(),”Svc”);
host.Open();
Console.WriteLine(“Start Your Service.”);
Console.ReadKey();
host.Close();
}
}
在這個(gè)HostApp中,我們?yōu)镠elloWorld創(chuàng )建了一個(gè)ServiceHost對象。通過(guò)它就可以創(chuàng )建WCF運行時(shí)(Runtime),WCF Runtime是一組負責接收和發(fā)送消息的對象。ServiceHost可以創(chuàng )建SerivceDescription對象,利用SerivceDescription,SercieHost為每一個(gè)ServiceEndpoint創(chuàng )建一個(gè)EndpointListener。ServiceHost的組成如下圖:

EndpointListener偵聽(tīng)器包含了listening address,message filtering和dispatch,它們對應ServiceEndpoint中的EndpointAddress,Contract和Binding。在EndpointListener中,還包含了一個(gè)Channel Stack,專(zhuān)門(mén)負責發(fā)送和接收消息。
注意在創(chuàng )建ServiceHost時(shí),傳遞的type類(lèi)型參數,不能是interface。因此,我在這里傳入的是typeof(HelloWorld)。ServiceHost類(lèi)的AddServiceEndpoint()方法實(shí)現了為Host添加Endpoint的功能,其參數正好是Endpoint的三部分:Address,Bingding和Contract。(此時(shí)的IHello即為ServiceContract,其方法Hello為OperationContract)。
ServiceHost的Open()方法用于創(chuàng )建和打開(kāi)Service運行時(shí),而在程序結束后我又調用了Close()方法,來(lái)關(guān)閉這個(gè)運行時(shí)。實(shí)際上以本例而言,該方法可以不調用,因為在應用程序結束后,系統會(huì )自動(dòng)關(guān)閉該host。但作為一種良好的編程習慣,WCF仍然要求顯式調用Close()方法,因為Service運行時(shí)其本質(zhì)是利用Channel來(lái)完成消息的傳遞,當打開(kāi)一個(gè)Service運行時(shí)的時(shí)候,系統會(huì )占用一個(gè)Channel,調用完后,我們就需要釋放對該通道的占用。當然我們也可以用using語(yǔ)句來(lái)管理ServiceHost資源的釋放。
定義好了一個(gè)WCF Service,并將其運行在Host上后,如何實(shí)現它與客戶(hù)端的通信呢?典型的情況下,服務(wù)端與客戶(hù)端均采用了Web Service Description Language(WSDL),客戶(hù)端可以通過(guò)工具SvcUtil.exe生成對應于該WCF Service的Proxy代碼,以完成之間的消息傳遞,如圖所示:

SvcUtil.exe是由WinFx Runtime Component SDK所提供的,如果安裝SDK正確,可以在其中找到該應用工具。生成客戶(hù)端Proxy代碼的方法很簡(jiǎn)單,首先需要運行服務(wù)端Service。然后再命令行模式下運行下面的命令:
SvcUtil http://localhost:8080/HelloService
這樣會(huì )在當前目錄下產(chǎn)生兩個(gè)文件output.cs和output.config。前者最主要的就是包含了一個(gè)實(shí)現了IHello接口的Proxy對象,這個(gè)代理對象名為HelloProxy,代碼生成的結果如下:
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
public partial class HelloProxy : System.ServiceModel.ClientBase<IHello>, IHello
{
public HelloProxy()
{
}
public HelloProxy(string endpointConfigurationName) :
base(endpointConfigurationName)
{
}
public HelloProxy(string endpointConfigurationName, string remoteAddress) :
base(endpointConfigurationName, remoteAddress)
{
}
public HelloProxy(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
base(endpointConfigurationName, remoteAddress)
{
}
public HelloProxy(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
base(binding, remoteAddress)
{
}
public void Hello()
{
base.InnerProxy.Hello();
}
}
(注:本程序在WinFx 2006 February CTP版本下運行通過(guò))
至于后者,則是WCF Service的配置信息,主要包含的是Endpoint中Address,Binding以及Contract的配置(在后續文章我會(huì )詳細介紹)。
現在客戶(hù)端就可以直接使用HelloProxy對象,來(lái)完成與服務(wù)端的通信了:
public class ClientApp
{
static void Main(string[] args)
{
using (HelloProxy proxy = new HelloProxy())
{
proxy.Hello();
}
Console.ReadKey();
}
}
除了可以使用SvcUtil工具產(chǎn)生客戶(hù)端代碼,同樣我們也可以利用代碼的方式來(lái)完成客戶(hù)端??蛻?hù)端在發(fā)送消息給服務(wù)端時(shí),其通信的基礎是Service的Endpoint,WCF提供了System.ServiceModel.Description.ServiceEndpoint類(lèi),通過(guò)創(chuàng )建它來(lái)實(shí)現兩端的通信。在前面,我還提到“對于客戶(hù)端而言,WCF管理的是發(fā)送消息時(shí)需要使用到的通道Channel”,為此,WCF提供了ChannelFactory(其命名空間為System.ServiceModel.Channel),專(zhuān)門(mén)用于創(chuàng )建客戶(hù)端運行時(shí)(runtime)。ChannelFactory與ServiceHost相對應,它可以創(chuàng )建ChannelDescription對象。與服務(wù)端ServiceHost不同的是,客戶(hù)端并不需要偵聽(tīng)器,因為客戶(hù)端往往是建立連接的“發(fā)起方”,并不需要偵聽(tīng)進(jìn)來(lái)的連接。因此客戶(hù)端的Channel Stack會(huì )由ChannelDescription創(chuàng )建。
ChannelFactory和ServiceHost都具有Channel Stack,而服務(wù)端與客戶(hù)端的通信又是通過(guò)channel來(lái)完成,這就意味著(zhù),利用ChannelFactory,客戶(hù)端可以發(fā)送消息到服務(wù)端。而客戶(hù)端本身并不存在Service對象,因此該Service的Proxy,是可以通過(guò)Channel來(lái)得到的。所以客戶(hù)端的代碼可以修改如下:
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Channel
public class ClientApp
{
static void Main(string[] args)
{
ServiceEndpoint httpEndpoint = new ServiceEndpoint(ContractDescription.GetContract(typeof(IHello)), new BasicHttpBinding(), new EndpointAddress(“http://localhost:8080/HelloService/Svc”));
using (ChannelFactory<IHello> factory = new ChannelFactory<IHello>(httpEndPoint))
{
//創(chuàng )建IHello服務(wù)的代理對象;
IHello service = factory.CreateChannel();
service.Hello();
}
Console.ReadKey();
}
}
(注:本程序在WinFx 2006 February CTP版本下運行通過(guò))
對于上面的代碼,我們有兩點(diǎn)需要注意:
1、采用這種方式,前提條件是客戶(hù)端能夠訪(fǎng)問(wèn)IHello接口。這也印證了之前我所敘述的最好使用interface來(lái)定義Service的好處。此外,為了保證部署的方便,有關(guān)Service的interface最好單獨編譯為一個(gè)程序集,便于更好的部署到客戶(hù)端。
2、客戶(hù)端必須知道服務(wù)端binding的方式以及address。
對于服務(wù)端而言,我們也可以直接在瀏覽器中打開(kāi)該Service,在地址欄中輸入http://localhost:8080/HelloService,如下圖:

點(diǎn)擊鏈接:http://localhost:8080/HelloService?wsdl,我們可以直接看到HelloService的WSDL。注意到在這里我并沒(méi)有使用IIS,實(shí)際上WCF內建了對httpsys的集成,允許任何應用程序自動(dòng)成為HTTP listener。
參考:
1、David Chappell,Introducing Windows Communication Foundation
2、Aaron Skonnard,Learn The ABCs Of Programming Windows Communication Foundation
3、Microsoft Corporation,Windows Communication Foundation Architecture Overview
<未完待續>
posted on 2006-04-10 08:37 張逸 閱讀(5251) 評論(4) 編輯 收藏 所屬分類(lèi): WCF & SOA
聯(lián)系客服