65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Bonjour 创建自托管 WCF 应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (2投票s)

2013年3月14日

CPOL

4分钟阅读

viewsIcon

22543

downloadIcon

353

一个自托管的 WCF 服务,它通过 Bonjour 发布来宣告自身的存在。

介绍 

在此处 下载源代码 

本文将介绍我尝试掌握在 Windows 服务中托管几个 WCF Web 服务的努力。由于客户端需要在无线网络上进行访问,并且部署中最大的问题显然不是实际设置主机,而是网络配置,因此我决定也添加一小部分关于 Bonjour 发布的内容。希望您会喜欢,甚至觉得它有用。 

请注意,代码实际上并未暴露 Web 服务。实际的 Web 服务定义在单独的程序集中,以保持对 WCF 托管本身的关注。   

背景

我选择在一个程序集中托管两个独立的 Web 服务。选择将它们分成两个服务的主要原因是它们本质上服务于完全不同的范围。一个旨在用于向底层数据模型添加内容(“创建”服务),另一个旨在用于从中获取信息(“搜索”服务)。由于整个 Web 服务可以配置为一项可选功能,不一定需要在所有部署中运行,因此我选择了单独的主机应用程序。

示例中的数据模型由一个简单的文本文件表示。创建服务将创建文件,搜索服务将从文件中读取。

当用户决定使用 Web 服务时,我希望他们在使用时尽可能少遇到麻烦。这就是为什么我选择将其实现为 Windows 服务,该服务可以自动启动。一旦安装,他们就不必担心 Web 服务是否可用。 

使用代码 

首先,我将展示如何通过外部
主机应用程序公开 Web 服务。出于调试目的,主机应用程序
配置为控制台应用程序。  通过以下条件可以区分在调试/控制台模式下启动它,还是由 Windows 服务控制器启动它。

if (Environment.UserInteractive) {
    // I'm launching the program from commandline
    using (ServiceHost host = new ServiceHost(typeof(WindowsFormsApplication1.SearchService))) {
        host.Open();
        while (true) {
            var key = Console.ReadKey(true).Key;
            if (key == ConsoleKey.Q || key == ConsoleKey.Escape) break;
        }
        Console.WriteLine(Environment.NewLine + "Exiting" + Environment.NewLine);
    }
}
else {
    // The windows service controller is launching my host
}

应用程序配置必须包含绑定信息和终结点地址。请注意选择的(任意)端口和基址扩展。

主机应用程序配置

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="WindowsFormsApplication1.SearchService" behaviorConfiguration="wsdl">
       
 <endpoint name="http" address="" binding="basicHttpBinding" 
contract="WindowsFormsApplication1.ISearchSvc" 
bindingNamespace="urn:schemas-mycompany-com:searchsvc-v10"/>
        <host>
          <baseAddresses>
            <add baseAddress="https://:8300/GSvc/"/>
          </baseAddresses>
        </host>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <!-- Provides for the service metadata (wsdl availability) -->
        <behavior name="wsdl">
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="True"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <bindings>
      <basicHttpBinding>
        <binding name="none">
          <security mode="None"/>
        </binding>
      </basicHttpBinding>
    </bindings>
  </system.serviceModel>
</configuration> 

在客户端配置中,它们将按如下方式返回。

事实上,当向客户端应用程序添加服务引用时,此 URL 可能用于生成必要服务引用。由于此过程非常简单,因此我将不再详细介绍。 

服务安装

Windows 服务的安装由主机应用程序处理。当然,这只执行一次,所以我选择将其分配给一个命令行标志。

if (args != null && args.Length >= 1) {
    List<string> services = new List<ServiceController>(
      ServiceController.GetServices()).ConvertAll<string>(svc => svc.ServiceName);
    switch (args[0]) {
        case "/i": // install service
        break;
        case "/u": // uninstall service
        break;
        default:
        // I'm launching the program from commandline
}

这里有几点提醒:在安装服务之前,请检查它是否尚未安装。此外,由于使用 Web 服务,最好在此处插入逻辑以添加防火墙规则。卸载则相反:如果服务不存在,则无需卸载。删除程序的防火墙规则。 

Windows 防火墙的规则如下。我更倾向于将规则应用于一个(范围的)端口号,因为当应用于程序时,规则仅适用于本地系统。

// add a rule with name 'My WCF Host' which allows incoming traffic on port range 7000-7050
advfirewall firewall add rule name="My WCF Host" dir=in action=allow enable=yes protocol=TCP localport="7000-7050"
// delete a rule with name 'My WCF Host' which allows incoming traffic on port range 7000-7050
advfirewall firewall delete rule name="My WCF Host" dir=in protocol=TCP localport="7000-7050"

Windows 服务的安装使用 System.Configuration.Install.ManagedInstallerClass InstallHelper 方法。请注意,安装服务需要在控制台中换行,卸载则不需要。请参阅以下代码示例

public static void Install() {
    // InstallHelper with /u switch set adds CRLF here, but with /i switch set it does not. So...
    Console.WriteLine();
    InstallHelper(new[] { "/LogFile=", ASM.Location });
}

public static void Uninstall() {
    InstallHelper(new[] { "/u", "/LogFile=", ASM.Location });
}
private static void InstallHelper(string[] args) {
    try { ManagedInstallerClass.InstallHelper(args); }
    catch (Exception e) {
        Console.WriteLine(" --- ERROR --- " + Environment.NewLine + e);
    }
}

添加 Bonjour

Apple 的 Bonjour 是一种基于 IP 的零配置网络的开放协议。Windows SDK 可从 https://developer.apple.com/downloads/index.action?q=Bonjour%20SDK%20for%20Windows 下载。请注意,您必须是注册的 Apple 开发者才能访问此页面。

我们选择通过 Bonjour 宣告我们 Web 服务的存在,服务类型为 _soap._tcp,子类型为 gsvc。任何在本地网络上搜索给定服务类型的客户端都将能够自动配置和访问我们的 Windows 服务包装的 Web 服务主机提供的 Web 服务。

服务发布在 Windows 服务启动时执行。查找以下代码以获取实际的 Bonjour 发布。

private static void Advertise(ServiceHost host) {
    string version = "1.0.0.0";
    DNSSDService service = new DNSSDService();
    DNSSDEventManager eventManager = new DNSSDEventManager();

    try {
        Uri baseaddress = host.BaseAddresses.Count == 1 ? host.BaseAddresses[0] : null;
        string serviceName = string.Format("{0} {1}", host.Description.Name, version);
        int port = baseaddress == null ? 0 : baseaddress.Port;
        // start the register and browse operations
        service.Register(0, 0, serviceName, "_soap._tcp,_gsvc", null, null, (ushort)port, null, eventManager);
    }
    catch (Exception e) {
    }
}

客户端现在必须实现发现/浏览功能和解析功能。  我们在客户端程序中以编程方式配置 Web 服务主机,一旦它们在(本地)网络上可用。

eventManager = new DNSSDEventManager();
eventManager.ServiceFound += new _IDNSSDEvents_ServiceFoundEventHandler(ServiceFound);
eventManager.ServiceResolved += new _IDNSSDEvents_ServiceResolvedEventHandler(ServiceResolved);
eventManager.ServiceLost += new _IDNSSDEvents_ServiceLostEventHandler(ServiceLost);

为了方便起见,我们添加了一个 ServiceLost 事件的监听器。请注意,发布的将会在您 PC 的所有网络接口适配器上找到。ServiceFound 事件将建立服务名称和域。ServiceResolved 提供我们需要的信息,即主机名和端口。

现在我们有了一个自托管的、基于 Windows 服务的 WCF WebService,使用 Bonjour 发布,以及一个能够动态查找和配置主机引用的客户端。

© . All rights reserved.