本文為《WCF入門》第二部分。主要介紹了有關WCF的技術要素,例如Endpoint,Host,Address,Binding,Contract。介紹了WCF的編程模式,以“Hello World”為例,講解了如何定義一個WCF Service,如何完成用戶端與服務端的通訊,如何通過工具SvcUtil.exe和代碼編寫的方式實現用戶端。三、WCF的技術要素
作為基於SOA(Service Oriented Architecture)的一個架構產品,WCF最重要的就是能夠快捷的建立一個服務(Service)。如所示,一個WCF Service由下面三部分構成:
1、Service Class:一個標記了[ServiceContract]Attribute的類,在其中可能包含多個方法。除了標記了一些WCF特有的Attribute外,這個類與一般的類沒有什麼區別。
2、Host(宿主):可以是應用程式,進程如Windows Service等,它是WCF Service啟動並執行環境。
3、Endpoints:可以是一個,也可以是一組,它是WCF實現通訊的核心要素。
WCF Service由一個Endpoints集合組成,每個Endpoint就是用於通訊的入口,用戶端和服務端通過Endpoint交換資訊,如所示:
我們可以看到一個Endpoint由三部分組成:Address,Binding,Contract。便於記憶,我們往往將這三部分稱為是Endpoint的ABCs。
Address是Endpoint的網路地址,它標記了訊息發送的目的地。Binding描述的是如何發送訊息,例如訊息發送的傳輸協議(如TCP,HTTP),安全(如SSL,SOAP訊息安全)。Contract則描述的是訊息所包含的內容,以及訊息的組織和操作方式,例如是one-way,duplex和request/reply。所以Endpoint中的ABCs分別代表的含義就是:where,how,what。當WCF發送訊息時,通過address知道訊息發送的地址,通過binding知道怎樣來發送它,通過contract則知道發送的訊息是什麼。
在WCF中,類ServiceEndpoint代表了一個Endpoint,在類中包含的EndpointAddress,Binding,ContractDescription類型分別對應Endpoint的Address,Binding,Contract,如:
EndpointAddress類又包含URI,Identity和可選的headers集合組成,如:
Endpoint安全的唯一性識別通常是通過其URI的值,但為了避免一些特殊情況造成URI的重複,又引入了Identity附加到URI上,保證了Endpoint地址的唯一性。至於可選的AddressHeader則提供了一些附加的資訊,尤其是當多個Endpoint在使用同樣的URI地址資訊時,AddressHeader就非常必要了。
Binding類包含Name,Namespace和BindingElement集合,如:
Binding的Name以及Namespace是服務中繼資料(service’s metadata)的唯一標識。BindingElement描述的是WCF通訊時binding的方式。例如,SecurityBindingElement表示Endpoint使用SOAP訊息安全方式,而ReliableSessionBindingElement表示Endpoint利用可信賴訊息確保訊息的傳送。TcpTransportBindingElement則表示Endpoint利用TCP作為通訊的傳輸協議。每種BindingElement還有相應的屬性值,進一步詳細的描述WCF通訊的方式。
BindingElement的順序也非常重要。BindingElement集合通常會建立一個用於通訊的堆棧,其順序與BindingElement集合中元素順序一致。集合中最後一個binding element對應於通訊堆棧的底部,而集合中的第一個binding element則對應於堆棧的頂端。入訊息流程的方向是從底部經過堆棧向上,而出訊息流程的方向則從頂端向下。因此,BindingElement集合中的binding element順序直接影響了通訊堆棧處理訊息的順序。幸運的是,WCF已經提供了一系列預定義的Binding,能夠滿足大多數情況,而不需要我們自訂Binding,殫精竭慮地考慮binding element的順序。
下表是WCF中預定義的Binding:
Class Name
|
Element Name
|
Transport
|
Encoding
|
WS-* Protocols
|
BasicHttpBinding
|
basicHttpBinding
|
HTTP
|
XML 1.0
|
WS-I Basic Profile 1.1
|
WSHttpBinding
|
wsHttpBinding
|
HTTP
|
XML 1.0
|
Message security, reliable sessions, and transactions
|
WSDualHttpBinding
|
wsDualHttpBinding
|
HTTP
|
XML 1.0
|
Message security, reliable sessions, and transactions
|
NetTcpBinding
|
netTcpBinding
|
TCP
|
Binary
|
Transport security, reliable sessions, and transactions
|
NetNamedPipeBinding
|
netNamedPipeBinding
|
Named Pipes
|
Binary
|
Transport security, reliable sessions, and transactions
|
NetMsmqBinding
|
netMsmqBinding
|
MSMQ
|
Binary
|
Transport security and queue transactions
|
Contract是一組操作(Operations)的集合,該操作定義了Endpoint通訊的內容,每個Operation都是一個簡單的訊息交換(message exchange),例如one-way或者request/reply訊息交換。
類ContractDescription用於描述WCF的Contracts以及它們的操作operations。在ContractDescription類中,每個Contract的operation都有相對應的OperationDescription,用於描述operation的類型,例如是one-way,還是request/reply。在OperationDescription中還包含了MessageDecription集合用於描述message。
在WCF編程模型中,ContractDescription通常是在定義Contract的介面或類中建立。對於這個介面或類類型,標記以ServiceContractAttribute,而其Operation方法則標記以OperationContractAttribute。當然我們也可以不利用CLR的attribute,而採用手工建立。
與Binding一樣,每個Contract也包含有Name和Namespace,用於在Service的中繼資料中作為唯一性識別。此外,Contract中還包含了ContractBehavior的集合,ContractBehavior類型可以用於修改或擴充contract的行為。類ContractDescription的組成如所示:
正如在ContractDescription中包含的IContractBehavior一樣,WCF專門提供了行為Behavior,它可以對用戶端和服務端的一些功能進行修改或者擴充。例如ServiceMetadataBehavior用於控制Service是否發布中繼資料。相似的,security behavior用於控制安全與授權,transaction behavior則控制事務。
除了前面提到的ContractBehavior,還包括ServiceBehavior和ChannelBehaivor。ServiceBehavior實現了IServiceBehavior介面,ChannelBehaivor則實現了IChannleBehavior介面。
由於WCF需要管理的是服務端與用戶端的通訊。對於服務端,WCF提供了類ServiceDescription用於描述一個WCF Service,;而針對用戶端,WCF管理的是發送訊息時需要使用到的通道Channel,類ChannelDescription描述了這樣的用戶端通道。
ServiceDescription類的組成如所示:
我們可以利用代碼的方式建立ServiceDescription對象,也可以利用WCF的Attribute,或者使用工具SvcUtil.exe。雖然可以顯式的建立它,但通常情況下,它會作為運行中的Service一部分而被隱藏於後(我在後面會提到)。
ChannelDescription類的組成與ServiceDescription大致相同,但它僅僅包含了一個ServiceEndpoint,用於表示用戶端通過通道通訊的目標Endpoint。當然,施加到ChannelDescription的Behavior也相應的為IChannelBehavior介面類型,:
定義一個WCF Service非常簡單,以Hello World為例,定義的Service可能如下:
using System.ServiceModel
[ServiceContract]
public class HelloWorld
{
[OperationContract]
public void Hello()
{
Console.WriteLine(“Hello World!”);
}
}
System.ServiceModel是微軟為WCF提供的一個新的類庫,以用於面向服務的程式設計。在開發WCF應用程式時,需要先添加對System.ServiceModel的引用。WCF中的大部分類和介面也都是在命名空間System.ServiceModel下。
我們為HelloWorld類標記了[ServiceContract],這就使得該類成為了一個WCF Service,而其中的方法Hello()則因為標記了[OperationContract],而成為該Service的一個Operation。
不過WCF推薦的做法是將介面定義為一個Service,這使得WCF Service具有更好的靈活性,畢竟對於一個介面而言,可以在同時有多個類實現該介面,這也就意味著可以有多個Service Contract的實現。那麼上面的例子就可以修改為:
[ServiceContract]
public interface IHello
{
[OperationContract]
void Hello();
}
而類HelloWorld則實現該IHello介面:
public class HelloWorld:IHello
{
public void Hello()
{
Console.WriteLine(“Hello World!”);
}
}
注意在實現了IHello介面的類HelloWorld中,不再需要在類和方法中標註ServiceContractAttribute和OperationContractAttribute了。
前面我已經提過,一個WCF Service必須有host作為它啟動並執行環境。這個host可以是ASP.Net,可以是Windows Service,也可以是一個普通的應用程式,例如控制台程式。下面就是一個Host的實現:
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();
}
}
在這個HostApp中,我們為HelloWorld建立了一個ServiceHost對象。通過它就可以建立WCF運行時(Runtime),WCF Runtime是一組負責接收和發送訊息的對象。ServiceHost可以建立SerivceDescription對象,利用SerivceDescription,SercieHost為每一個ServiceEndpoint建立一個EndpointListener。ServiceHost的組成如:
EndpointListener接聽程式包含了listening address,message filtering和dispatch,它們對應ServiceEndpoint中的EndpointAddress,Contract和Binding。在EndpointListener中,還包含了一個Channel Stack,專門負責發送和接收訊息。
注意在建立ServiceHost時,傳遞的type型別參數,不能是interface。因此,我在這裡傳入的是typeof(HelloWorld)。ServiceHost類的AddServiceEndpoint()方法實現了為Host添加Endpoint的功能,其參數正好是Endpoint的三部分:Address,Bingding和Contract。(此時的IHello即為ServiceContract,其方法Hello為OperationContract)。
ServiceHost的Open()方法用於建立和開啟Service運行時,而在程式結束後我又調用了Close()方法,來關閉這個運行時。實際上以本例而言,該方法可以不調用,因為在應用程式結束後,系統會自動關閉該host。但作為一種良好的編程習慣,WCF仍然要求顯式調用Close()方法,因為Service運行時其本質是利用Channel來完成訊息的傳遞,當開啟一個Service運行時的時候,系統會佔用一個Channel,調用完後,我們就需要釋放對該通道的佔用。當然我們也可以用using語句來管理ServiceHost資源的釋放。
定義好了一個WCF Service,並將其運行在Host上後,如何?它與用戶端的通訊呢?典型的情況下,服務端與用戶端均採用了Web Service Description Language(WSDL),用戶端可以通過工具SvcUtil.exe產生對應於該WCF Service的Proxy代碼,以完成之間的訊息傳遞,:
SvcUtil.exe是由WinFx Runtime Component SDK所提供的,如果安裝SDK正確,可以在其中找到該應用工具。產生用戶端Proxy代碼的方法很簡單,首先需要運行服務端Service。然後再命令列模式下運行下面的命令:
SvcUtil http://localhost:8080/HelloService
這樣會在目前的目錄下產生兩個檔案output.cs和output.config。前者最主要的就是包含了一個實現了IHello介面的Proxy對象,這個代理對象名為HelloProxy,代碼產生的結果如下:
[System.CodeDom.Compiler.GeneratedCodeAttribute(”System.ServiceModel”, “3.0.0.0″)]
public partial class HelloProxy : System.ServiceModel.ClientBase, 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版本下運行通過)
至於後者,則是WCF Service的配置資訊,主要包含的是Endpoint中Address,Binding以及Contract的配置(在後續文章我會詳細介紹)。
現在用戶端就可以直接使用HelloProxy對象,來完成與服務端的通訊了:
public class ClientApp
{
static void Main(string[] args)
{
using (HelloProxy proxy = new HelloProxy())
{
proxy.Hello();
}
Console.ReadKey();
}
}
除了可以使用SvcUtil工具產生用戶端代碼,同樣我們也可以利用代碼的方式來完成用戶端。用戶端在發送訊息給服務端時,其通訊的基礎是Service的Endpoint,WCF提供了System.ServiceModel.Description.ServiceEndpoint類,通過建立它來實現兩端的通訊。在前面,我還提到“對於用戶端而言,WCF管理的是發送訊息時需要使用到的通道Channel”,為此,WCF提供了ChannelFactory(其命名空間為System.ServiceModel.Channel),專門用於建立用戶端運行時(runtime)。ChannelFactory與ServiceHost相對應,它可以建立ChannelDescription對象。與服務端ServiceHost不同的是,用戶端並不需要接聽程式,因為用戶端往往是建立串連的“發起方”,並不需要偵聽進來的串連。因此用戶端的Channel Stack會由ChannelDescription建立。
ChannelFactory和ServiceHost都具有Channel Stack,而服務端與用戶端的通訊又是通過channel來完成,這就意味著,利用ChannelFactory,用戶端可以發送訊息到服務端。而用戶端本身並不存在Service對象,因此該Service的Proxy,是可以通過Channel來得到的。所以用戶端的代碼可以修改如下:
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 factory = new ChannelFactory(httpEndPoint))
{
//建立IHello服務的代理對象;
IHello service = factory.CreateChannel();
service.Hello();
}
Console.ReadKey();
}
}
(註:本程式在WinFx 2006 February CTP版本下運行通過)
對於上面的代碼,我們有兩點需要注意:
1、採用這種方式,前提條件是用戶端能夠訪問IHello介面。這也印證了之前我所敘述的最好使用interface來定義Service的好處。此外,為了保證部署的方便,有關Service的interface最好單獨編譯為一個程式集,便於更好的部署到用戶端。
2、用戶端必須知道服務端binding的方式以及address。
對於服務端而言,我們也可以直接在瀏覽器中開啟該Service,在地址欄中輸入http://localhost:8080/HelloService,如:
點選連結:http://localhost:8080/HelloService?wsdl,我們可以直接看到HelloService的WSDL。注意到在這裡我並沒有使用IIS,實際上WCF內建了對httpsys的整合,允許任何應用程式自動成為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