使用多种协议在多宿主环境中托管您的 WCF 服务






4.78/5 (39投票s)
探索 WCF 服务的多种托管环境。
引言
在分布式应用程序的世界中,已经引入、实现和使用了许多技术,如 DCOM、CORBA 等。
在此之后,一种新概念被采纳,那就是 Web 服务。Web 服务易于开发和实现,无需担心防火墙问题,从而简化了系统间的通信。
开发 Web 服务时的问题在于,您需要使用 http 或 wshttp 协议将其托管在 Web 服务器(如 IIS 或 Apache)中,并且这是您唯一的选择。
WCF 通过提供将您的服务托管在不同应用程序进程中使用各种协议的可能性,解决了这个问题。
在本文中,我将展示如何实现这一点。
示例代码包括一个简单的 WCF 服务、控制台窗口主机、Windows 服务主机、IIS6 主机、II7 主机以及在 VS 2008 标准版和 .NET 3.0 中构建的客户端控制台窗口应用程序。
背景
正如我们所知,如果您的系统采用 Web 服务架构,您需要以下内容:
- 使用 [WebService] 和 [WebMethod] 属性创建一个类似组件库的服务
- 在 IIS 等 Web 服务器中托管它
- 从 WSDL 生成代理(接口或契约)
- 将此代理分发给需要调用和使用此 Web 服务的客户端应用程序。
WCF 的多主机和协议支持
Microsoft 推出了 WCF 概念,以简化分布式应用程序的开发和部署。
托管环境 | 支持的协议 |
Windows 控制台和窗体应用程序 | HTTP,net.tcp,net.pipe,net.msmq |
Windows 服务应用程序(以前称为 NT 服务) | HTTP,net.tcp,net.pipe,net.msmq |
Web 服务器 IIS6 | http, wshttp |
Web 服务器 IIS7 - Windows 进程激活服务 (WAS) | HTTP,net.tcp,net.pipe,net.msmq |
创建测试服务
在这里,我创建了一个非常简单的服务,名为“FirstWcfService.Service”,如下面的列表 1 所示。
// Listing -1 IService.cs file
using System;
using System.Runtime.Serialization;
using System.ServiceModel;
namespace FirstWcfService
{
[ServiceContract]
public interface IService
{
[OperationContract]
string Hello();
}
}
// Service.cs file
using System;
namespace FirstWcfService
{
public class Service : IService
{
public string Hello()
{
return ("Hello WCF");
}
}
}
正如我们所见,此服务仅包含一个操作契约(Hello),此操作将返回简单的字符串“Hello WCF”。
托管我们的服务
正如我在文章开头提到的,随着 WCF 的出现,我们现在有了多种托管此服务的方式。
在每个托管环境中,我们需要为该服务提供一个终结点,并且还需要引用它,以便客户端应用程序可以访问该服务。
这在实践中意味着什么?这意味着我们必须为已托管的服务创建一个配置文件。
选项 1 - Windows 控制台主机应用程序
1 - 我在这里创建了一个经典的控制台应用程序(图 1),然后按以下列表 2 托管了我的服务。
//Listing – 2 Program.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Description;
namespace FirstWcfServiceHostConsoleApp
{
class Program
{
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(typeof(FirstWcfService.Service)))
{
host.Open();
Console.WriteLine("Press <Enter> to terminate the Host application.");
Console.ReadLine();
}
}
}
}
要在控制台应用程序中托管 WCF 服务,我们需要最少的工作量。让我们检查上面列表中的代码。
此代码 ServiceHost host = new ServiceHost(typeof(FirstWcfService.Service)
创建我们服务的一个实例。
此行 host.Open() 使服务在托管环境中可供访问。
2- 其次,我创建了一个具有最少配置选项的配置文件,如下面的列表 3 所示。
//Listing – 3 App.config
<?xml version="1.0"?>
<configuration>
<system.serviceModel>
<services>
<service name="FirstWcfService.Service" behaviorConfiguration="ServiceBehavior">
<!-- Service Endpoints -->
<endpoint address="FirstWcfService" binding="netTcpBinding"
contract="FirstWcfService.IService"/>
<!-- This Endpoint is used for genertaing the proxy for the client -->
<!-- To avoid disclosing metadata information, set the value below to false and
remove the metadata endpoint above before deployment -->
<endpoint address="mex" contract="IMetadataExchange" binding="mexTcpBinding" />
<host>
<baseAddresses>
<add baseAddress="net.tcp://:9100/"/>
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="ServiceBehavior">
<serviceMetadata httpGetEnabled="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
选项 2 - Windows 服务主机应用程序
1 - 我在这里创建了一个经典的 Windows 服务应用程序,然后按以下列表 4 托管了我的服务。
//Listing - 4, Program.cs
using System;
using System.ServiceProcess;
namespace Windows_Service
{
static class Program
{
static void Main()
{
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[] { new WCFWindowsService() };
ServiceBase.Run(ServicesToRun);
}
}
}
//Listing - 4, WCFWindowsService.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.ServiceProcess;
using System.ServiceModel;
namespace Windows_Service
{
public partial class WCFWindowsService : ServiceBase
{
ServiceHost m_serviceHost;
protected override void OnStart(string[] args)
{
m_serviceHost = new ServiceHost(typeof(FirstWcfService.Service));
m_serviceHost.Open();
}
protected override void OnStop()
{
if (m_serviceHost != null)
{
m_serviceHost.Close();
}
m_serviceHost = null;
}
}
}
要在 Windows 服务应用程序中托管 WCF 服务,我们还需要最少的工作量,通过实现以下函数:
void OnStart(string[] args) 和 protected override void OnStop()
让我们检查上面列表中的代码。
这段代码
m_serviceHost = new ServiceHost(typeof(FirstWcfService.Service));
m_serviceHost.Open();
创建我们服务的一个实例,然后使服务在托管环境中可供访问。
2 - 要使此应用程序像 Windows 服务一样工作,我们应该将其安装为列表 5。此文件与 WCF 实现无关,因此我将不解释此代码的作用。
//Listing - 5, WCFWindowsServiceInstaller.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration.Install;
using System.ServiceProcess;
// To install or uninstall this Windows service via cmd:
//
//C:\Program Files\Microsoft Visual Studio 9.0\VC>installutil.exe /u yourpath/WCFWindowsService.exe
//C:\Program Files\Microsoft Visual Studio 9.0\VC>installutil.exe yourpath/WCFWindowsService.exe
namespace Windows_Service
{
[RunInstaller(true)]
public partial class WCFWindowsServiceInstaller : Installer
{
public WCFWindowsServiceInstaller()
{
InitializeComponent();
ServiceProcessInstaller processInstaller = new ServiceProcessInstaller();
ServiceInstaller serviceInstaller = new ServiceInstaller();
processInstaller.Account = ServiceAccount.LocalSystem;
serviceInstaller.DisplayName = "WCF_WindowsService";
serviceInstaller.Description = "WCF_WindowsService.";
serviceInstaller.ServiceName = "WCF_WindowsService";
serviceInstaller.StartType = ServiceStartMode.Manual;
Installers.Add(processInstaller);
Installers.Add(serviceInstaller);
}
}
}
3 - 我创建了一个具有最少配置选项的配置文件,如下面的列表 6 所示。
//Listing - 6, App.config <?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <services> <service name="FirstWcfService.Service" behaviorConfiguration="ServiceBehavior"> <endpoint address="FirstWcfService" contract="FirstWcfService.IService" binding="netTcpBinding" /> <!-- This Endpoint is used for genertaing the proxy for the client --> <endpoint address="mex" contract="IMetadataExchange" binding="mexTcpBinding" /> <host> <baseAddresses> <add baseAddress="net.tcp://:9000"/> </baseAddresses> </host> </service> </services> <behaviors> <serviceBehaviors> <behavior name="ServiceBehavior"> <serviceMetadata httpGetEnabled="false"/> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> </configuration>
选项 3 - IIS6 主机应用程序
1 - 我在这里创建了一个简单的 WCF 服务 Web 应用程序,然后引用了 FirstWcfService 以便按以下列表 7 托管它。
//Listing - 7, file Service.svc
<%@ ServiceHost Language="C#" Debug="true" Service="FirstWcfService.Service"%>
正如我们在这里看到的,当我们在 IIS 中托管 WCF 服务时,我们不需要像在控制台和 Windows 服务应用程序托管中所做的那样创建主机服务并打开它,因为 IIS 负责创建服务并使其监听客户端调用。我们只需要 .svc 文件中的上述代码。
2- 我创建了一个具有最少配置选项的配置文件,如下面的列表 8 所示。
//Listing - 8, Web.config
<?xml version="1.0"?>
<configuration>
<system.serviceModel>
<services>
<service name="FirstWcfService.Service" behaviorConfiguration="ServiceBehavior">
<!-- Service Endpoints -->
<endpoint address="" binding="basicHttpBinding"
contract="FirstWcfService.IService"/>
<!-- This Endpoint is used for genertaing the proxy for the client -->
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="ServiceBehavior">
<!-- To avoid disclosing metadata information, set the value below to
false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes,
set the value below to true. Set to false before deployment to avoid
disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
选项 4 - IIS7 (WAS) 主机应用程序
1 - 我在这里使用 Windows Server 2008 (IIS7) 创建了一个简单的 WCF 服务 Web 应用程序,然后引用了 FirstWcfService 以便按以下列表 9 托管它。
//Listing - 9 file Service.svc
<%@ ServiceHost Language="C#" Debug="true" Service="FirstWcfService.Service"%>
2- 我创建了一个具有最少配置选项的配置文件,如下面的列表 10 所示。
//Listing - 10, Web.config
<?xml version="1.0"?>
<configuration>
<system.serviceModel>
<services>
<service name="FirstWcfService.Service" behaviorConfiguration="ServiceBehavior">
<!-- Service Endpoints -->
<endpoint address="" binding="netTcpBinding" contract="FirstWcfService.IService"
bindingConfiguration="tcpbinding"/>
<endpoint address="mextcp" binding="mexTcpBinding" contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="ServiceBehavior">
<!-- To avoid disclosing metadata information, set the value below to false
and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes,
set the value below to true. Set to false before deployment to avoid
disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<netTcpBinding>
<binding name="tcpbinding">
<!--<security mode="None"></security>-->
<security mode="Transport">
<transport clientCredentialType="Windows" protectionLevel="EncryptAndSign"/>
<message clientCredentialType="Windows"/>
</security>
</binding>
</netTcpBinding>
</bindings>
</system.serviceModel>
</configuration>
注意:正如您在这里看到的,我在将 SCF 服务托管到 II6 中时,与选项 3 进行了相同的实现。唯一的区别是,我已将我的服务配置为在 IIS7 (WAS) 中使用“net.Tcp”协议而不是经典的“basicHttp”协议进行调用。我将不解释 IIS7 的新架构,因为这个主题不在本文的讨论范围内。
3- 当您在 IIS7 中创建 WCF 服务时,默认情况下,该服务仅启用了 http 和 wshttp。我已经将我的服务配置为可以使用 net.Tcp 协议进行调用,如下面的步骤所示:
A - 为了启用 net.Tcp 等新协议,我们需要通过 IIS 管理器将其添加到我们的默认网站树中,方法是点击“编辑绑定”。
B - 添加新协议以及所需的端口,我还添加了 9200。
C - 始终从 IIS 管理器中,通过单击高级设置将 net.Tcp 添加到您的 WCF 服务。
D - 在“已启用协议”设置中输入您想要的协议。
创建客户端应用程序
我创建了一个简单的客户端控制台应用程序,以便向您展示如何使用多种托管选项调用我们的 WCF 服务。
我的客户端应用程序看起来像这样。
让我们检查一下这个应用程序。
1 - 我们需要创建一个代理,创建代理有两种可能性:
要么从 Visual Studio 中通过添加服务引用,如下所示。
或者使用 SvcUtil.exe 工具,因为我们有多种托管选项,我们可以在这里看到我们只需要使用以下创建的代理之一:
• 如果您从托管在控制台应用程序中的服务创建代理,则执行:SvcUtil.exe net.tcp://:9100/mex /out:path\proxy.cs /n:*,localhost
• 如果您从托管在 Windows 服务应用程序中的服务创建代理,则执行 SvcUtil.exe net.tcp://:9000/mex /out:path\proxy.cs /n:*,localhost
• 如果您从托管在 IIS 6 中的服务创建代理,则执行 SvcUtil.exe https:///FirstWcfIISHost/Service.svc /out:path\proxy.cs /n:*,localhost
• 如果您从托管在 IIS7 中使用 tcp 协议的服务创建代理,则执行 SvcUtil.exe net.tcp://winserver2008:9200/FirstWcfHost/Service.svc/ out:path\proxy.cs /n:*,localhost
当您第一次创建代理时,您可以使用相同的代理在不同的托管环境中调用相同的服务。
最后,我们的代理将如下面的列表 11 所示。
// Listing - 11 proxy.cs file
namespace localhost
{
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="localhost.IService")]
public interface IService
{
[System.ServiceModel.OperationContractAttribute(Action =
"http://tempuri.org/IService/Hello",
ReplyAction = "http://tempuri.org/IService/HelloResponse")]
string Hello();
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
public interface IServiceChannel : localhost.IService,
System.ServiceModel.IClientChannel
{
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
public partial class ServiceClient :
System.ServiceModel.ClientBase<localhost.IService>, localhost.IService
{
public ServiceClient()
{
}
public ServiceClient(string endpointConfigurationName) :
base(endpointConfigurationName)
{
}
public ServiceClient(string endpointConfigurationName, string remoteAddress) :
base(endpointConfigurationName, remoteAddress)
{
}
public ServiceClient(string endpointConfigurationName,
System.ServiceModel.EndpointAddress remoteAddress) :
base(endpointConfigurationName, remoteAddress)
{
}
public ServiceClient(System.ServiceModel.Channels.Binding binding,
System.ServiceModel.EndpointAddress remoteAddress) :
base(binding, remoteAddress)
{
}
public string Hello()
{
return base.Channel.Hello();
}
}
}
2- 我创建了一个具有最少配置选项的配置文件,如下面的列表 12 所示。
//Listing - 12, App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<client>
<!-- calling the WCF service which is hosted inside windows console application -->
<endpoint address="net.tcp://:9100/FirstWcfService" binding="netTcpBinding"
contract="localhost.IService" name="Windows_Console_Host_TcpBinding">
</endpoint>
<!-- calling the WCF service which is hosted inside IIS6 -->
<endpoint address="https:///FirstWcfIISHost/Service.svc"
binding="basicHttpBinding"
contract="localhost.IService" name="IIS_Host_HttpBinding">
</endpoint>
<!-- calling the WCF service which is hosted inside windows service -->
<endpoint address="net.tcp://:9000/FirstWcfService"
binding="netTcpBinding"
contract="localhost.IService" name="Windows_Services_Host_TcpBinding">
</endpoint>
<!-- calling the WCF service which is hosted inside IIS7 (WAS) -->
<endpoint address="net.tcp://winserver2008:9200/FirstWcfHost/Service.svc"
binding="netTcpBinding"
contract="localhost.IService" name="II7_WAS_Host_TcpBinding"
bindingConfiguration="II7NetTcpBinding">
</endpoint>
</client>
<bindings>
<netTcpBinding>
<binding name="II7NetTcpBinding">
<!--<security mode="None"></security>-->
<security mode="Transport">
<transport clientCredentialType="Windows"
protectionLevel="EncryptAndSign"/>
<message clientCredentialType="Windows"/>
</security>
</binding>
</netTcpBinding>
</bindings>
</system.serviceModel>
</configuration>
在此文件中,我配置了我的客户端,以便向您展示如何调用托管在不同环境中的同一个 WCF 服务。实际上,您不需要所有这些终结点,您的客户端只需要其中一个终结点,因此您的“app.config”将如下面的列表 13 所示。
<?x ml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<client>
<!-- calling the WCF service which is hosted inside windows console application -->
<endpoint address="net.tcp://:9100/FirstWcfService" binding="netTcpBinding"
contract="localhost.IService" name="Windows_Console_Host_TcpBinding">
</endpoint>
</client>
</system.serviceModel>
</configuration>
3- 我已按以下列表 14 实现我的客户端。
//Listing - 14, Program.cs
using System;
namespace FirstWcfServiceClientConsoleApp
{
class Program
{
static void Main(string[] args)
{
try
{
#region Call the hosted service inside IIS6
localhost.ServiceClient iis6proxy = new localhost.ServiceClient(
"IIS_Host_HttpBinding");
Console.WriteLine("=====================");
Console.WriteLine("Call the hosted service inside IIS6");
Console.WriteLine(iis6proxy.Hello());
Console.WriteLine("=====================");
Console.WriteLine();
#endregion
#region Call the hosted service inside IIS7 (WAS)
localhost.ServiceClient iis7proxy = new localhost.ServiceClient(
"II7_WAS_Host_TcpBinding");
Console.WriteLine("=====================");
Console.WriteLine("Call the hosted service inside IIS7 (WAS)");
Console.WriteLine(iis7proxy.Hello());
Console.WriteLine("=====================");
Console.WriteLine();
#endregion
#region Call the hosted service inside Windows service application
localhost.ServiceClient tcpWindowsSrviceproxy =
new localhost.ServiceClient("Windows_Services_Host_TcpBinding");
Console.WriteLine("=====================");
Console.WriteLine(
"Call the hosted service inside Windows service application");
Console.WriteLine(tcpWindowsSrviceproxy.Hello());
Console.WriteLine("=====================");
Console.WriteLine();
#endregion
#region Call the hosted service inside Windows Console application
localhost.ServiceClient tcpproxy = new localhost.ServiceClient(
"Windows_Console_Host_TcpBinding");
Console.WriteLine("=====================");
Console.WriteLine(
"Call the hosted service inside Windows Console application");
Console.WriteLine(tcpproxy.Hello());
Console.WriteLine("=====================");
Console.WriteLine();
Console.WriteLine("Press <Enter> to terminate the client application.");
Console.ReadLine();
#endregion
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.ReadLine();
}
}
}
}
在上面的代码中,我们注意到为了调用 WCF 服务,我们需要像这样实例化代理类:
localhost.ServiceClient iis6proxy = new localhost.ServiceClient("IIS_Host_HttpBinding");
然后我们可以像这样调用一个函数或方法:
iis6proxy.Hello());
将所有内容整合在一起并获得结果。
我的最终解决方案将是这样的。
最后,当我们运行客户端应用程序(FirstWcfServiceClientConsoleApp.exe)并在验证所有四个托管环境(IIS6、IIS7、Windows 服务、Windows 控制台应用程序)都已运行并正在侦听后,我们将获得以下精彩结果。
正如您在此处注意到的,我们创建了一个 WCF 服务,并将其托管在以下四个不同的环境中,使用了不同的协议:
- Windows 控制台应用程序,使用 tcp 协议。
- Windows 服务进程,使用 tcp 协议。
- IIS6,使用 http 协议。
- IIS7,使用 tcp 协议。
通过仅更改客户端上的终结点,我们就可以访问我们的 WCF 服务并调用函数。
我想在此说明的是,不同的客户端应用程序(Windows、Intranet、Internet、Extranet 等)可以在不同的托管环境中调用该服务。
结论
我们可以得出结论,WCF 为我们提供了一个非常简单但功能强大的开发和部署框架,它能够使用 SOA(面向服务的架构)设计和方法在不同的系统之间建立通信。