WCF宿主與服務託管
若要公開WCF服務,需要提供一個運行服務的宿主環境。就像.NET CLR需要建立宿主環境以Managed 程式碼一般,WCF的宿主環境同樣運行在進程的應用程式定義域中。在應用程式定義域中可以建立一個或多個ServiceHost執行個體,其關係一所示:
WCF並不推薦在應用程式定義域中建立多個ServiceHost執行個體。如果要託管多個服務,完全可以在一個宿主中通過多個Endpoint公開多個WCF服務。由於應用程式定義域對安全進行了隔離,如果需要提供不同的安全上下文,則有必要建立多個ServiceHost執行個體。
WCF的典型宿主包括以下四種:
1、"Self-Hosting" in a Managed Application(自託管宿主)
2、Managed Windows Services(Windows Services宿主)
3、Internet Information Services(IIS宿主)
4、Windows Process Activation Service(WAS宿主)
以下將通過一個具體的執行個體分別介紹這幾種宿主的託管方式及其相關的注意事項。在這樣的一個執行個體中,我們定義了如下的服務契約:
namespace BruceZhang.WCF.DocumentsExplorerServiceContract { [ServiceContract] public interface IDocumentsExplorerService { [OperationContract] [FaultContract(typeof(DirectoryNotFoundException))] DocumentList FetchDocuments(string homeDir); [OperationContract] Stream TransferDocument(Document document); } }
|
服務的實現則如下所示:
namespace BruceZhang.WCF.DocumentsExplorerServiceImplementation { [ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)] public class DocumentsExplorerService : IDocumentsExplorerService { #region IDocumentsExplorerService Members public DocumentList FetchDocuments(string homeDir) { //implementation code } public Stream TransferDocument(Document document) { //implementation code } #endregion } }
|
在服務契約的操作中,DocumentList與Document則為自己定義的資料契約:
namespace BruceZhang.WCF.DocumentsExplorerDataContract { [DataContract] public class Document { //DataMembers } } namespace BruceZhang.WCF.DocumentsExplorerDataContract { [KnownType(typeof(Document))] [CollectionDataContract] public class DocumentList:IList { //IList Methods } }
|
注意以上定義的服務契約、服務類與資料契約的命名空間。
1、自託管宿主
利用WCF提供的ServiceHost提供的Open()和Close()方法,可以便於開發人員在控制台應用程式,Windows應用程式乃至於ASP.NET應用程式中託管服務。不管自宿主的環境是何種應用程式,實質上託管服務的方式都是一致的。例如在控制台應用程式中:
using (ServiceHost host = new ServiceHost(typeof(DocumentsExplorerService))) { host.Open(); Console.WriteLine("The Service had been launched."); Console.Read(); }
|
由於ServiceHost執行個體是被建立在應用程式定義域中,因此我們必須保證宿主進程在調用服務期間不會被關閉,因此我們利用Console.Read()來阻塞進程,以使得控制台應用程式能夠一直運行,直到認為地關閉應用程式。如果是Windows應用程式,則可以將建立ServiceHost執行個體的代碼放在主表單的相關代碼中,保證服務宿主不會被關閉。
相應地,我們需要配置應用程式的app.config設定檔:
<configuration> <system.serviceModel> <services> <service name= "BruceZhang.WCF.DocumentsExplorerServiceImplementation.DocumentsExplorerService" behaviorConfiguration="DocumentExplorerServiceBehavior"> <host> <baseAddresses> <add baseAddress="http://localhost:8008/DocumentExplorerService"/> </baseAddresses> </host> <endpoint address="" binding="basicHttpBinding" bindingConfiguration="DocumentExplorerServiceBinding" contract="BruceZhang.WCF.DocumentsExplorerServiceContract.IDocumentsExplorerService"/> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/> </service> </services> <bindings> <basicHttpBinding> <binding name="DocumentExplorerServiceBinding" sendTimeout="00:10:00" transferMode="Streamed" messageEncoding="Text" textEncoding="utf-8" maxReceivedMessageSize="9223372036854775807"> </binding> </basicHttpBinding> </bindings> <behaviors> <serviceBehaviors> <behavior name="DocumentExplorerServiceBehavior"> <serviceMetadata httpGetEnabled="true"/> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> </configuration>
|
注意,設定檔中的服務名必須包含服務契約以及服務類的命名空間。此外,在設定檔中我通過標籤為服務添加了基地址,因此在endpoint中,address為""。
此時,調用服務的用戶端設定檔也應與服務的配置保持一致:
<configuration> <system.serviceModel> <client> <endpoint address="http://localhost:8008/DocumentExplorerService" binding="basicHttpBinding" bindingConfiguration="DocumentExplorerServiceBinding" contract="IDocumentsExplorerService"/> </client> <bindings> <basicHttpBinding> <binding name="DocumentExplorerServiceBinding" sendTimeout="00:10:00" transferMode="Streamed" messageEncoding="Text" textEncoding="utf-8" maxReceivedMessageSize="9223372036854775807"> </binding> </basicHttpBinding> </bindings> </system.serviceModel> </configuration>
|
注意,兩個設定檔中的服務地址都是一樣的,對於綁定的配置也基本一致。
在通常的公司專屬應用程式中,我們很少會採用自宿主方式託管服務,這是因為這種方式必須要在應用程式運行下,用戶端才能夠調用服務,且並不便於隨時啟動和停止服務。除了不具有易用性與易管理性之外,在可靠性、效能等諸多方面受到很多限制。但由於它簡單、易於實現,因而往往用於開發期間的調試或示範環境。
自託管宿主支援所有的綁定。
2、Windows Services宿主
WindowsServices宿主則完全克服了自託管宿主的缺點,它便於管理者方便地啟動或停止服務,且在服務出現故障之後,能夠重新啟動服務。我們還可以通過Service Control Manager(服務控制管理員),將服務設定為自動啟動方式,省去了服務的管理工作。此外,WindowsServices自身還提供了一定的安全性以及檢測機制和日誌機制。
Windows Services宿主的實現也非常簡單。我們可以在Visual Studio中建立WindowsServices項目。在建立項目之後,就可以建立一個繼承了System.ServiceProcess.ServiceBase類的Windows服務類。Windows服務類繼承了ServiceBase類的OnStart()和OnStop()方法,完成Windows服務的啟動與停止。我們可以重寫這兩個方法,將ServiceHost的啟動與關閉對應地放入這兩個方法的實現中。例如我們建立的DocumentsExplorerWindowsService類:
namespace BruceZhang.WCF.DocumentsExplorer { public partial class DocumentsExplorerWindowsService : ServiceBase { private ServiceHost m_serviceHost = null; public static void Main() { ServiceBase.Run(new DocumentsExplorerWindowsService()); } public DocumentsExplorerWindowsService() { InitializeComponent(); ServiceName = "DocumentsExplorerService"; } protected override void OnStart(string[] args) { if (m_serviceHost != null) { m_serviceHost.Close(); } m_serviceHost = new ServiceHost(typeof(DocumentsExplorerService)); m_serviceHost.Open(); } protected override void OnStop() { if (m_serviceHost != null) { m_serviceHost.Close(); m_serviceHost = null; } } } }
|
在Main函數中,我們通過ServiceBase.Run()靜態方法建立Windows服務執行個體,並在Windows服務類的建構函式中,調用ServiceBase類的ServiceName屬性指定服務名。在重寫的OnStart()方法中,我們首先判斷是否已經存在ServiceHost執行個體,如果不存在,則建立它。建立ServiceHost執行個體的方法與自託管宿主方式相同。
為了完成ServiceHost執行個體的建立,我們同樣需要在項目中添加app.config設定檔,設定檔的內容與前完全一樣。
如果在公司專屬應用程式中要使用WCF技術,最佳的宿主方式我認為就是WindowsServices,尤其是伺服器的作業系統不是Vista的情況之下。它便於服務的管理,能夠維持服務長時期的運行,同時它還支援所有的綁定,因而受到的限制最小。然而,這種方式唯一的缺點卻是對宿主的部署相對比較複雜,必須通過.NET提供的Installutil.exe工具完成對服務宿主的安裝(也可以通過安裝包的自訂動作完成)。
若要完成對服務宿主的安裝,我們還需要建立它的安裝程式。我們可以自訂一個安裝類,使其繼承自System.Configuration.Install.Installer類。更簡單的辦法則是通過Windows服務提供的設計時支援,直接建立安裝類。方法是在Windows服務例如DocumentsExplorerWindowsService的設計器視圖下,通過單擊右鍵,在捷徑功能表中選擇“Add Installer”,2示:
建立的安裝程式ExplorerServiceInstaller如下所示:
namespace BruceZhang.WCF.DocumentsExplorer { //It needs be ran at the command mode //Type installutil filename to install the windows service //Type services.msc to access the Service Control Manager(SCM) and browse the windows services //Type installutil /u filename to uninstall the windows service [RunInstaller(true)] public partial class ExplorerServiceInstaller : Installer { private ServiceProcessInstaller m_process; private ServiceInstaller m_service; public ExplorerServiceInstaller() { InitializeComponent(); m_process = new ServiceProcessInstaller(); m_process.Account = ServiceAccount.LocalSystem; m_service = new ServiceInstaller(); m_service.ServiceName = "DocumentsExplorerService"; Installers.Add(m_process); Installers.Add(m_service); } } }
|
在ExplorerServiceInstaller類中,ServiceAccount是一個枚舉類型,可以設定為LocalService,LocalSystem,NetworkService以及User值。其中,LocalService的安全性最低,User值的安全性最高,需要有效使用者帳號方才可以安裝服務。
對於安裝程式而言,也可以直接在設計器視圖下設定它的屬性。
安裝程式直接建立在Windows服務的程式集中,編譯之後會獲得一個exe檔案,例如DocumentsExplorer.exe。然後,我們通過在Visual Studio的Command Prompt模式下運行如下命令:
installutil DocumentsExplorer.exe
即可完成對服務宿主的安裝。
開啟服務控制管理員(可以在Command Prompt模式下輸入Services.msc開啟),可以看到名為DocumentsExplorerService的服務:
如果要卸載該服務宿主,可以通過installutil的/u開關卸載。
在公司專屬應用程式中,我們往往會將該Windows服務設定為自動啟動,可以簡化管理員的工作。
3、IIS宿主(說明,這裡講的IIS為IIS 6.0)
若要使用IIS宿主,需要為程式集中添加一個svc檔案。我們可以通過為項目添加一個新項的方式添加svc檔案:
我們也可以直接建立一個WCF Service應用程式作為IIS宿主,它會自動建立一個svc檔案,5所示:
建立的svc檔案
WCF Service應用程式建立的svc檔案以及通過添加新項獲得svc檔案,自動會建立WCF服務。因此,如果我們希望在svc檔案中嵌入WCF服務的代碼,則可以採取這種方式。例如:
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; using System.IO; using BruceZhang.WCF.DocumentsExplorerDataContract; namespace BruceZhang.WCF.DocumentsExplorerServiceImplementation { [ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)] public class DocumentsExplorerService : IDocumentsExplorerService { //Service Implementation } }
|
上述代碼中的@ServiceHost指示符只能是在按右鍵svc檔案後,在View Marckup中才能夠看到。
Svc檔案通過@ServiceHost指示符指定它所要託管的服務,此外還指定了實現服務的語言、調用模式,還可以設定CodeBehind,指定服務代碼。不過,在IIS託管中,服務代碼或組件檔受到一定的限制,它只能放在如下的其中一個位置中:
(1)svc檔案的內嵌代碼中;
(2)放在註冊於GAC的單獨程式集中;
(3)駐留於應用程式的Bin檔案夾內的程式集中(此時,bin檔案夾不必include);
(4)駐留於應用程式的App_Code檔案夾的原始碼檔案中(根據Language的設定,或者為C#或者為VB)。
即使我們將服務代碼放在應用程式根目錄下,或者其它檔案夾中,然後通過CodeBehind指定代碼的路徑,仍然不能託管服務。
如果服務契約與服務類是通過引用的方式在宿主應用程式中,則我們可以直接建立一個副檔名為.svc的單個檔案,然後include到應用程式根目錄下,六中的HostService.svc,該檔案沒有關聯的cs檔案。此時,在VisualStudio中直接開啟該檔案,並不能編寫服務代碼,而是指定@ServiceHost即可。
注意,上述方式的IIS宿主只能建立ServiceHost執行個體,如果是自訂的ServiceHost,則需要通過@ServiceHost的Factory來指定建立自訂ServiceHost的工廠類。例如這樣的自訂ServiceHost以及相應的工廠類:
using System; using System.ServiceModel; using System.ServiceModel.Activation; namespace BruceZhang.WCF.DocumentsExplorerIISHost { public class CustomServiceHostFactory : ServiceHostFactory { protected override ServiceHost CreateServiceHost( Type serviceType, Uri[] baseAddresses) { CustomServiceHost customServiceHost = new CustomServiceHost(serviceType, baseAddresses); return customServiceHost; } } public class CustomServiceHost : ServiceHost { public CustomServiceHost(Type serviceType, params Uri[] baseAddresses) : base(serviceType, baseAddresses) { } protected override void ApplyConfiguration() { base.ApplyConfiguration(); } } }
|
則@ServiceHost修改為:
在IIS託管應用程式中,我們需要建立web.config(注意,不是app.config),在節中佈建服務的相關內容:
name="BruceZhang.WCF.DocumentsExplorerServiceImplementation.DocumentsExplorerService"> contract="BruceZhang.WCF.DocumentsExplorerServiceContract. IDocumentsExplorerService" />
|
注意,這裡的設定檔與之前的宿主設定檔有個別的差異,就是沒有指定服務的基地址。這是因為IIS託管會自動將svc檔案的地址作為服務的基地址,我們無法在設定檔中自行指定。Svc檔案的地址為svc檔案在IIS虛擬目錄或網站所設定的路徑。例如,我們在IIS中建立一個虛擬目錄DocumentsExplorer指向IIS宿主應用程式DocumentsExplorerIISHost,7所示:
如果網站的屬性沒有做任何修改,使用預設的連接埠號碼,以及Localhost,則訪問服務的基地址為http://localhost/DocumentsExplorer/HostService.svc。如果在設定檔的服務endpoint中設定地址為DocumentsService,如:
address="DocumentsService" binding="basicHttpBinding" bindingConfiguration="DocumentExplorerServiceBinding" contract="BruceZhang.WCF.DocumentsExplorerServiceContract.IDocumentsExplorerService" />
|
則公開服務的地址則為http://localhost/DocumentsExplorer/HostService.svc/DocumentsServic。
通過IIS啟動網站後,不需要做任何操作,服務宿主自動會建立ServiceHost執行個體或者Factory指定的自訂ServiceHost執行個體。
由於服務地址發生了變化,因此用戶端的設定檔也需要做出相應的修改,必須將服務的地址設定為與之對應的地址。其中,服務的基地址為svc檔案在IIS中的地址。
IIS宿主是一種主要的服務託管方式,這是因為它具有易用性、可維護性、安全性、易於部署等多個優勢。然而,它卻具有一個致命的阿客流斯之踵,那就是它只支援HTTP協議的傳輸綁定。特別對於區域網路情境下,如果使用IIS宿主,就無法利用TCP傳輸的高效率,甚至無法使用MSMQ以及Peerto Peer傳輸。
IIS 7.0(基於Windows Vista和Windows Server 2007)提供的Windows啟用服務(WAS)突破了IIS 6.0對於HTTP的依賴。
4、WAS宿主
WAS是IIS 7.0的一部分,但也可以獨立地安裝與配置。WAS支援所有可用的WCF傳輸協議、連接埠與隊列。
利用WAS託管服務與IIS宿主託管服務的方法並沒有太大的區別,仍然需要建立svc檔案,同時在IIS中需要在網站中建立應有程式指向託管應用程式,還可以設定訪問服務的別名與應用程式集區。
由於WAS訴諸支援所有的綁定,因此此時的服務綁定並不會受到宿主的限制。