若要公開WCF服務,需要提供一個運行服務的宿主環境。就像.NET CLR需要創建宿主環境以托管代碼一般,WCF的宿主環境同樣運行在進程的應用程序域中。在應用程序域中可以創建一個或多個ServiceHost實例,其關系如圖一所示:
圖一 托管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<Document>
{
//IList<Document> Methods
}
}
注意以上定義的服務契約、服務類與數據契約的命名空間。
1、自托管宿主
利用WCF提供的ServiceHost<T>提供的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>
注意,配置文件中的服務名必須包含服務契約以及服務類的命名空間。此外,在配置文件中我通過<baseAddresses>標簽為服務添加了基地址,因此在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宿主
Windows Services宿主則完全克服了自托管宿主的缺點,它便於管理者方便地啟動或停止服務,且在服務出現故障之後,能夠重新啟動服務。我們還可以通過Service Control Manager(服務控制管理器),將服務設置為自動啟動方式,省去了服務的管理工作。此外,Windows Services自身還提供了一定的安全性以及檢測機制和日志機制。
Windows Services宿主的實現也非常簡單。我們可以在Visual Studio中創建Windows Services項目。在創建項目之後,就可以創建一個繼承了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技術,最佳的宿主方式我認為就是Windows Services,尤其是服務器的操作系統不是Vista的情況之下。它便於服務的管理,能夠維持服務長時期的運行,同時它還支持所有的綁定,因而受到的限制最小。然而,這種方式唯一的缺點卻是對宿主的部署相對比較復雜,必須通過.NET提供的Installutil.exe工具完成對服務宿主的安裝(也可以通過安裝包的自定義操作完成)。
若要完成對服務宿主的安裝,我們還需要創建它的安裝程序。我們可以自定義一個安裝類,使其繼承自System.Configuration.Install.Installer類。更簡單的辦法則是通過Windows服務提供的設計時支持,直接創建安裝類。方法是在Windows服務例如DocumentsExplorerWindowsService的設計器視圖下,通過單擊右鍵,在快捷菜單中選擇“Add Installer”,如圖二所示:
圖二 添加安裝程序
創建的安裝程序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文件:
圖四 添加svc文件
我們也可以直接創建一個WCF Service應用程序作為IIS宿主,它會自動創建一個svc文件,如圖五所示:
圖五 創建WCF Service應用程序
創建的svc文件如圖:
圖六 創建的svc文件
WCF Service應用程序創建的svc文件以及通過添加新項獲得svc文件,自動會創建WCF服務。因此,如果我們希望在svc文件中嵌入WCF服務的代碼,則可以采取這種方式。例如:
<%@ ServiceHost Language="C#" Debug="true" Service="BruceZhang.WCF.DocumentsExplorerServiceImplementation.DocumentsExplorerService"%>
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文件。此時,在Visual Studio中直接打開該文件,並不能編寫服務代碼,而是指定@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修改為:
<%@ ServiceHost Language="C#" Debug="true" Service="BruceZhang.WCF.DocumentsExplorerServiceImplementation.DocumentsExplorerService" Factory="BruceZhang.WCF.DocumentsExplorerIISHost.CustomServiceHostFactory"%>
在IIS托管應用程序中,我們需要創建web.config(注意,不是app.config),在<system.serviceModel>節中配置服務的相關內容:
<system.serviceModel>
<services>
<service behaviorConfiguration="DocumentExplorerServiceBehavior"
name="BruceZhang.WCF.DocumentsExplorerServiceImplementation.DocumentsExplorerService">
<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>
注意,這裡的配置文件與之前的宿主配置文件有個別的差異,就是沒有指定服務的基地址。這是因為IIS托管會自動將svc文件的地址作為服務的基地址,我們無法在配置文件中自行指定。Svc文件的地址為svc文件在IIS虛擬目錄或站點所設置的路徑。例如,我們在IIS中創建一個虛擬目錄DocumentsExplorer指向IIS宿主應用程序DocumentsExplorerIISHost,如圖七所示:
圖七 在IIS站點中為IIS創建虛擬目錄
如果站點的屬性沒有做任何修改,使用默認的端口號,以及Localhost,則訪問服務的基地址為http://localhost/DocumentsExplorer/HostService.svc。如果在配置文件的服務endpoint中設置地址為DocumentsService,如:
<endpoint
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以及Peer to 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訴諸支持所有的綁定,因此此時的服務綁定並不會受到宿主的限制。