WCF宿主与服务托管

翻译|其它|编辑:郝浩|2008-01-03 13:41:57.000|阅读 820 次

概述:

# 界面/图表报表/文档/IDE等千款热门软控件火热销售中 >>

若要公开 WCF 服务,需要提供一个运行服务的宿主环境。就像.NET CLR 需要创建宿主环境以托管代码一般,WCF 的宿主环境同样运行在进程的应用程序域中。在应用程序域中可以创建一个或多个 ServiceHost 实例,其关系如图一所示:
Host_ServiceHost.gif 
图一  托管 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”,如图二所示:
Host_AddInstaller.gif 
图二   添加安装程序

创建的安装程序 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 的服务:
Host_WindowsServices.gif 
图三  服务控制管理器

如果要卸载该服务宿主,可以通过 installutil 的/u 开关卸载。

在企业应用中,我们往往会将该 Windows 服务设置为自动启动,可以简化管理员的工作。

3、IIS 宿主(说明,这里讲的 IIS 为 IIS 6.0)

若要使用 IIS 宿主,需要为程序集中添加一个 svc 文件。我们可以通过为项目添加一个新项的方式添加 svc 文件:
Host_AddNewItem.gif
图四   添加 svc 文件

我们也可以直接创建一个 WCF Service 应用程序作为 IIS 宿主,它会自动创建一个 svc 文件,如图五所示:
Host_ServiceApp.gif 
图五   创建 WCF Service 应用程序

创建的 svc 文件如图:
Host_svc.gif 
图六  创建的 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,如图七所示:
Host_IIS.gif 
图七  在 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 诉诸支持所有的绑定,因此此时的服务绑定并不会受到宿主的限制。


标签:

本站文章除注明转载外,均为本站原创或翻译。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,如果存在内容上的异议请邮件反馈至chenjj@evget.com

文章转载自:个人博客

为你推荐

  • 推荐视频
  • 推荐活动
  • 推荐产品
  • 推荐文章
  • 慧都慧问
扫码咨询


添加微信 立即咨询

电话咨询

客服热线
023-68661681

TOP