Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#4.0

Create a self-hosted WCF application with Bonjour

4.89/5 (2 votes)
14 Mar 2013CPOL4 min read 22.2K   341  
A self-hosted WCF service that announces itself through Bonjour publishing.

Introduction 

Download source code here 

This article will somehow describe the efforts I made in mastering the hosting of a couple of WCF Web Services in a Windows Service. Since the client was supposed to consume on a wireless network, and apparently the biggest problem in deployment wasn't the actual setting up of the host but the network configuration, I decided to add a small part on Bonjour publishing as well. I hope you'll appreciate it, and maybe even find it useful. 

Note that the code is not actually exposing the web service. The actual webservice is defined in a separate assembly, to maintain the focus on WCF hosting only.   

Background

I chose to host two separate web services in one assembly. The main reason why I chose to separate them in two services was that inherently they served completely different scopes. One was intended to be used to add something to the underlying datamodel ('Create' Service), one was intended to be used to obtain information from it ('Search' Service). Since the whole webservice can be configured as an optional feature, which not necessarily needed to be made operative in all deployments, I chose the separate host application.

The data model in the example is represented by a simple text file. The create service will create a file, the search service will read from it.

When the user decides to use the webservices, I wanted him to have as little trouble as possible in using it. That's why I chose to implement it in a Windows Service, which could be started automatically. Once installed, he would never have to worry whether or not the web service was available or not. 

Using the code 

First, I will show how to expose the web service through the external
host application. For debugging reasons, the host application is
configured as a Console application.  The distinction between launching it in debug / console mode, or have it launched by the windows service controller, can be made by the following condition:

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
}

The application configuration must contain the binding information and endpoint address. Note the (arbitrary) port chosen and the base address extension.

Host application configuration

XML
<?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="http://localhost: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> 

In client configuration these will return, as shown below.

In fact, when adding the service reference to the client application, this URL might be used to generate the necessary service reference. Since this process is pretty straightforward, I will not go into detail on this.  

Service installation

The installation of the windows service is handled on the host application. Of course this is performed only once, so I chose to assign it to a commandline flag.

C#
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
}

A couple of reminders here: Check before installing the service, that it isn't already installed. Besides that, since working with a webservice, it might be a good idea to insert logic here to add a firewall rule. Opposite logic goes for the uninstallation: if the service doesn't exist, no need to uninstall it. Remove the firewall rule for the program. 

The rules for Windows firewall are as follows. I prefer the rule to apply to a (range of) port number(s), since when applying it to a program, the rule applies only on the local system.

C#
// 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"

The installation of the windows service uses System.Configuration.Install.ManagedInstallerClass InstallHelper method. Note that installing the service requires a newline on the console, uninstalling does not. See the following code sample:

C#
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);
    }
}

Adding Bonjour

Apple's bonjour is an open protocol for Zero Configuration Networking over IP. The Windows SDK can be downloaded from https://developer.apple.com/downloads/index.action?q=Bonjour%20SDK%20for%20Windows. Note that you must be a registered apple developer to access this page.

We choose to advertise the presence of our webservice through  bonjour, with service type _soap._tcp, subtype gsvc. Any client browsing the local network for the given service type will then be able to autoconfigure and consume the webservices presented by our Windows Service wrapped Web Service host.

Service publishing is performed upon startup of the windows service. Find the following code for the actual bonjour publishing.

C#
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) {
    }
}

The client must now implement Discovery / Browse functionality and Resolve functionality.  We configure the web service host on client side programmatically, as soon as they're available on the (local) network.

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

We add a listener for ServiceLost events for convenience sake. Notice that the published service will be found on all network interface adapters on your PC. The ServiceFound event will establish the service name and domain. ServiceResolved provides the information we need, namely the host name and port.

Now we have a self-hosted, Windows service based WCF WebService, published using BonJour, and a client that will dynamically find and configure the reference to the host.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)