托管
WCF服务类不能凭空存在。每个WCF服务都必须托管(Hosting) 在Windows进程中,该进程被称为宿主进程(Host Process)。单个宿主进程可以托管多个服务,而相同的服务类型也能够托管在多个宿主进程中。WCF没有要求宿主进程是否同时又是客户端进程。显然,一个独立的进程有利于错误与安全的隔离。谁提供进程或是提供何种类型的进程并不重要。宿主可以由IIS提供,也可以由Windows Vista的Windows激活服务(Windows Activation Service,WAS)提供,或者开发者直接将它作为应用程序的一部分。
注意:一种特殊的托管方式称为进程内托管(In-Process Hosting),简称in-proc。服务与客户端驻留在相同的进程中。通过定义,开发者能够提供进程内托管。
IIS托管
在微软的Internet信息服务器(Internet Information Server,IIS)中托管服务,主要的优势是宿主进程可以在客户端提交第一次请求的时候自动启动,还可以借助IIS管理宿主进程的生命周期。IIS托管的主要缺点在于只能使用HTTP协议。如果是IIS 5,还要受端口限制,要求所有服务必须使用相同的端口号。
在IIS中托管服务与经典的ASMX Web服务托管相似,需要在IIS下创建虚拟目录,并提供一个.svc文件。.svc文件的功能与.asmx文件相似,主要用于识别隐藏在文件和类后面的服务代码。例1-2展示了.svc文件的语法结构。
例1-2:.svc文件
<%@ ServiceHost
Language= "C#"
Debug = "true"
CodeBehind = "~/App_Code/MyService.cs"
Service = "MyService"
%>
注意:我们甚至可以将服务代码注入到.svc文件中,但这样的做法并不明智。这与ASMX Web服务的要求相同。
使用IIS托管,服务的基地址必需与.svc文件的地址保持一致。
使用Visual Studio 2005
使用Visual Studio 2005,可以生成IIS托管服务的模版文件。选择File菜单的New Website菜单项,然后从New Web Site对话框中选择WCF Service。通过这种方式可以让Visual Studio 2005创建一个新的Web站点,以及服务代码和对应的.svc文件。之后,我们还可以通过Add New Item对话框添加另外的服务。
Web.Config文件
Web站点的配置文件(Web.Config)必须列出需要公开为服务的类型。类型使用类型全名,如果服务类型来自于一个没有被引用的程序集,则还要包括程序集名:
<system.serviceModel>
<services>
<service name = "MyNamespace.MyService">
...
</service>
</services>
</system.serviceModel>
自托管
所谓自托管(Self-Hosting) ,就是由开发者提供和管理宿主进程的生命周期。自托管方式适用于如下场景:需要确定客户端与服务之间的进程(或机器)边界时;使用进程内托管,即服务与客户端处于相同的进程中时。进程可以是任意的Windows进程,例如Windows窗体应用程序、控制台应用程序或Windows NT服务。注意,进程必须在客户端调用服务之前运行,这意味着通常必须预先启动进程。但NT服务或进程内托管不受此限制。宿主程序的实现只需要简单的几行代码,就能够实现IIS托管的一部分特性。
与IIS托管相似,托管应用程序的配置文件(App.Config)必须列出所有希望托管和公开的服务类型:
<system.serviceModel>
<services>
<service name = "MyNamespace.MyService">
...
</service>
</services>
</system.serviceModel>
此外,宿主进程必须在运行时显式地注册服务类型,同时为客户端的调用打开宿主,因此,我们才要求宿主进程必须在客户端调用到达之前运行。创建宿主的方法通常是在Main()方法中调用ServiceHost类。ServiceHost类的定义如例1-3所示。
例1-3:ServiceHost类
public interface ICommunicationObject
{
void Open();
void Close();
//更多成员
}
public abstract class CommunicationObject : ICommunicationObject
{...}
public abstract class ServiceHostBase : CommunicationObject,IDisposable,...
{...}
public class ServiceHost : ServiceHostBase,...
{
public ServiceHost(Type serviceType,params Uri[] baseAddresses);
//更多成员
}
创建ServiceHost对象时,需要为ServiceHost的构造函数提供服务类型,至于默认的基地址则是可选的。可以将基地址集合设置为空。如果提供了多个基地址,也可以将服务配置为使用不同的基地址。ServiceHost拥有基地址集合可以使得服务能够接收来自于多个地址和协议的调用,同时只需要使用相对的URI。注意,每个SeriviceHost实例都与特定的服务类型相关,如果宿主进程需要运行多个服务类型,则必须创建与之匹配的多个ServiceHost实例。在宿主程序中,通过调用Open()方法,可以允许调用传入;通过调用Close()方法终结宿主实例,完成进程中的调用。此时,即使宿主进程还在运行,仍然会拒绝客户端的调用。而在通常情况下,执行关闭操作会停止宿主进程。例如,在Windows窗体应用程序中托管服务:
[ServiceContract]
interface IMyContract
{...}
class MyService : IMyContract
{...}
我们可以编写如下的托管代码:
public static void Main()
{
Uri baseAddress = new Uri("http://localhost:8000/");
ServiceHost host = new ServiceHost(typeof(MyService),baseAddress);
host.Open();
//可以执行用于阻塞的调用:
Application.Run(new MyForm());
host.Close();
}
打开宿主时,将装载WCF运行时(WCF runtime),启动工作线程监控传入的请求。由于引入了工作线程,因此可以在打开宿主之后执行阻塞(blocking)操作。通过显式控制宿主的打开与关闭,提供了IIS托管难以实现的特征,即能够创建定制的应用程序控制模块,管理者可以随意地打开和关闭宿主,而不用每次停止宿主的运行。
使用Visual Studio 2005
Visual Studio 2005允许开发者为任意的应用程序项目添加WCF服务,方法是在Add New Item对话框中选择WCF Service选项。当然,这种方式添加的服务,对于宿主进程而言属于进程内托管方式,但进程外的客户端仍然可以访问它。
自托管与基地址
启动服务宿主时,无需提供任何基地址:
public static void Main()
ServiceHost host = new ServiceHost(typeof(MyService));
host.Open();
Application.Run(new MyForm());
host.Close();
}
警告:但是我们不能向空列表传递null值,这会导致抛出异常:
serviceHost host;
host = new ServiceHost(typeof(MyService),null);
只要这些地址没有使用相同的传输样式(Transport Schema),我们也可以注册多个基地址,并以逗号作为地址之间的分隔符。代码实现如下所示(注意例1-3中params限定符的使用):
Uri tcpBaseAddress = new Uri("net.tcp://localhost:8001/");
Uri httpBaseAddress = new Uri("http://localhost:8002/");
ServiceHost host = new ServiceHost(typeof(MyService),
tcpBaseAddress,httpBaseAddress);
WCF也允许开发者在宿主配置文件中列出基地址内容:
<system.serviceModel>
<services>
<service name = "MyNamespace.MyService">
<host>
<baseAddresses>
<add baseAddress = "net.tcp://localhost:8001/"/>
<add baseAddress = "http://localhost:8002/"/>
</baseAddresses>
</host>
...
</service>
</services>
</system.serviceModel>
创建宿主时,无论在配置文件中找到哪一个基地址,宿主都会使用它,同时还要加上以编程方式提供的基地址。需要特别注意,我们必须确保配置的基地址的样式不能与代码中的基地址的样式重叠。
我们甚至可以针对相同的类型注册多个宿主,只要这些宿主使用了不同的基地址:
Uri baseAddress1 = new Uri("net.tcp://localhost:8001/");
ServiceHost host1 = new ServiceHost(typeof(MyService),baseAddress1);
host1.Open();
Uri baseAddress2 = new Uri("net.tcp://localhost:8002/");
ServiceHost host2 = new ServiceHost(typeof(MyService),baseAddress2);
host2.Open();
然而,这并不包括第8章介绍的使用线程的情况,以这种方式打开多个宿主并无优势可言。此外,如果基地址是配置文件提供的,那么就需要使用ServiceHost的构造函数为相同的类型打开多个宿主。
托管的高级特性
ServiceHost实现的ICommunicationObject接口定义了一些高级特性,如例1-4所示。
例1-4:ICommunicationObject接口
public interface ICommunicationObject
{
void Open();
void Close();
void Abort();
event EventHandler Closed;
event EventHandler Closing;
event EventHandler Faulted;
event EventHandler Opened;
event EventHandler Opening;
IAsyncResult BeginClose(AsyncCallback callback,object state);
IAsyncResult BeginOpen(AsyncCallback callback,object state);
void EndClose(IAsyncResult result);
void EndOpen(IAsyncResult result);
CommunicationState State
{get;}
//更多成员
}
public enum CommunicationState
{
Created,
Opening,
Opened,
Closing,
Closed,
Faulted
}
如果打开或关闭宿主的操作耗时较长,可以采用异步方式调用BeginOpen()和BeginClose()方法。我们可以订阅诸如状态改变或错误发生等宿主事件,通过调用State属性查询当前的宿主状态。ServiceHost类同样实现了Abort()方法。该方法提供强行退出功能,能够及时中断进程中的所有服务调用,然后关闭宿主。此时,活动的客户端会获得一个异常。
ServiceHost<T>类
ServiceHost<T>类能够改进WCF提供的ServiceHost类,它的定义如例1-5所示。
例1-5:ServiceHost<T>类
public class ServiceHost<T> : ServiceHost
{
public ServiceHost() : base(typeof(T))
{}
public ServiceHost(params string[] baseAddresses) :
base(typeof(T),Convert(baseAddresses))
{}
public ServiceHost(params Uri[] baseAddresses) :
base(typeof(T),baseAddresses)
{}
static Uri[] Convert(string[] baseAddresses)
{
Converter<string,Uri> convert = delegate(string address)
{
return new Uri(address);
};
return Array.ConvertAll(baseAddresses,convert);
}
}
ServiceHost<T>简化了构造函数,它不需要传递服务类型作为构造函数的参数,还能够直接处理字符串而不是处理令人生厌的Uri值。在本书余下的内容中,对Service-Host<T>进行了扩展,增加了一些特性,提高了它的性能。
WAS托管
Windows激活服务(WAS)是一个系统服务,只适用于Windows Vista。WAS是IIS 7的一部分,但也可以独立地安装与配置。若要使用WAS托管WCF服务,必须提供一个.svc文件,这与IIS托管一样。IIS与WAS的主要区别在于WAS并不局限于使用HTTP,它支持所有可用的WCF传输协议、端口与队列。
WAS提供了大量基于自托管的强大功能,包括应用程序池、回收机制、空闲时间管理(Idle Time Management)、身份管理(Identity Management)以及隔离(Isolation);宿主进程可以根据情况选择使用这些功能。若需考虑可扩展性,就应该使用Vista服务器作为目标机器;如果只有少数客户端,则可以将Vista客户机作为服务器。
当然,自托管进程还提供了许多卓越特性,例如进程内宿主、匿名用户环境的处理,同时还为之前介绍的高级宿主特性提供了便捷地编程访问方式。





