Wednesday, January 14, 2009

Self-Hosting WCF

I'd like to have two separate processes (both under my control) communicate over WCF. What's the easiest way to achieve this? As an example, I'd like Process 1 (a client) to be able to check the health or presence of Process 2 (the server) by periodically requesting the server's local time.

To permit communication, we need to define a contract between the two processes. All I'd like this contract (let's call it HealthCheck) to define is an operation GetTimestamp. Given that my two separate processes will be two separate projects in Visual Studio, and that they both need to access this contract, we'll create a third class library called SharedTypes that will contain nothing but the contract.


SharedTypes (contains the contract)

Assembly references: System.ServiceModel

We'll define the contract as an interface, decorated by System.ServiceModel.ServiceContract. The interface's one method signature (GetTimestamp) should be decorated by System.ServiceModel.OperationContract.

using System;
using System.ServiceModel;
 
namespace SharedTypes
{
    [ServiceContract]
    public interface IHealthCheck
    {
        [OperationContract]
        DateTime GetTimestamp();
    }
}


Server

Assembly references: SharedTypes, System.ServiceModel

The server process, perhaps unsurprisingly, will need to implement this contract interface as follows:

using System;
using SharedTypes;
 
namespace Server
{
    class HealthCheck : IHealthCheck
    {
        public DateTime GetTimestamp()
        {
            return DateTime.Now;
        }
    }
}

The program's main method will simply instantiate and open a ServiceHost to expose the service, and hang around in the background:

using System;
using System.ServiceModel;
 
namespace Server
{
    class Program
    {
        static void Main(string[] args)
        {
            ServiceHost serviceHost = new ServiceHost(typeof(HealthCheck));
            serviceHost.Open();
 
            // Keep the process running (hence the service open) for an hour
            System.Threading.Thread.Sleep(3600000);
        }
    }
}

Finally, we'll need to specify the address and protocol by which the service can be called. Part of the joy of WCF is allowing this to be configurable by an end-user or sysadmin, so we'll specify a minimal app.config to contain the core settings we require:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
   <system.serviceModel>
      <services>
         <service name="Server.HealthCheck">
            <endpoint address="basic" binding="basicHttpBinding"
                      contract="SharedTypes.IHealthCheck" />
            <host>
               <baseAddresses>
                  <add baseAddress="http://localhost:8080/HealthCheck" />
               </baseAddresses>
            </host>
         </service>
      </services>
   </system.serviceModel>
</configuration>


Client

Assembly references: SharedTypes, System.ServiceModel

The client will also need to have a class that implements the service contract, but we'll make it such that calls to its methods actually result in a remote call over WCF:

using System;
using System.ServiceModel;
using SharedTypes;
 
namespace Client
{
    public class HealthCheck : ClientBase<IHealthCheck>, IHealthCheck
    {
        public DateTime GetTimestamp()
        {
            return base.Channel.GetTimestamp();
        }
    }
}

The program's main method will just sit in a loop calling and echoing the result from GetTimestamp:

using System;
 
namespace Client
{
    class Program
    {
        static void Main(string[] args)
        {
            HealthCheck healthCheck = new HealthCheck();
            while (true)
            {
                Console.WriteLine(healthCheck.GetTimestamp());
 
                // Wait for a second
                System.Threading.Thread.Sleep(1000);
            }
        }
    }
}

Again, we'll need to specify in app.config the location of the server, and the protocol we want to use:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
   <system.serviceModel>
      <client>
         <endpoint address="http://localhost:8080/HealthCheck/basic"
                   binding="basicHttpBinding"
                   contract="SharedTypes.IHealthCheck" />
      </client>
   </system.serviceModel>
</configuration>

No comments:

Post a Comment