Thursday, October 1, 2009

Simple WCF client/server

So communicating between two windows services on the same computer is easy. But then I was asked, what if we decide in the future that we want these services to run on the separate machines? Well, I guess we'll need to make changes to both of them ... and that's exactly what we want to avoid. Okay, the WCF offers a few ways to host a service - in a managed application, in a managed windows service, in IIS, in WAS ... (Hosting Options) Since I already have windows services implemented, the choice is obvious. I looked up a couple of tutorials on the subject fairly quickly:
How to: Host a WCF Service in a Managed Windows Service, WCF Tutorial - Basic Interprocess Communication

However, that was not quite enough for me because the first tutorial's problem was that it explained the configuration file a bit, but did not implement the "client", and the second tutorial implemented both server and client, but had no info on configuration at all. So I quickly got to the point where I could have a server and client running inside separate windows services on the same machine, but as soon as I tried taking one of the services away - to another computer on the network - different errors started to happen. Not enough time and space on explaining each error and what was the reason for it, just a few words on what I ended up with (which eventually worked).

Service implementation

To define and implement the function on the server:

[ServiceContract(Namespace="MyNamespace.IMyInterface")]
public interface IMyInterface
{
[OperationContract]
string ReturnMyString();
}

public class MyInterfaceImplementation : IMyInterface
{
public string ReturnMyString()
{
return "My String";
}
}

To create the instance of the host, first define the host

private ServiceHost host;

In the service OnStart method

if (host != null)
{
host.Close();
}

host = new ServiceHost(typeof(MyInterfaceImplementation), new Uri[] { new Uri(http://MyServer:8080) });
host.AddServiceEndpoint(typeof(IMyInterface), new BasicHttpBinding(), "MyMethod");

In the service OnStop method

if (host != null)
{
host.Close();
host = null;
}

This part was fairly easy.

Service configuration

This bit went into the app.config file inside the "configuration".

<system.serviceModel>
<services>
<service name="MyNamespace.MyService" behaviorConfiguration="MyServiceBehavior">
<host>
<baseAddresses>
<add baseAddress="http://MyServer:8000/MyMethod"/>
</baseAddresses>
</host>
<!-- this endpoint is exposed at the base address provided by host-->
<endpoint address="" binding="basicHttpBinding" contract="MyNamespace.IMyInterface" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="MyServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="False"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>

Note the service name attribute "MyNamespace.MyService" which is the windows service names, and the endpoint contract attribute, which is the ServiceContract Namespace attribute. Some small things are easy to get wrong, and the error messages will not be very informative.

Client implementation

[ServiceContract(Namespace="MyNamespace.IMyInterface")]
public interface IMyInterface
{
[OperationContract]
string ReturnMyString();
}

public string MyClientString()
{
string result = string.Empty;

string endpoint = "http://MyServer:8000/MyMethod";

ChannelFactory httpFactory = new ChannelFactory(
new BasicHttpBinding(), new EndpointAddress(endpoint));

IMyInterface httpProxy = httpFactory.CreateChannel();

while (true)
{
result = httpProxy.ReturnMyString();
if (result != string.Empty)
{
return result;
}
}
}

I missed the [ServiceContract(Namespace="MyNamespace.IMyInterface")] bit initially on the interface definition and the error message was really not helping. It went like that: "Exception: The message with Action 'http://tempuri.org/IMyInterface/ReturnMyString' cannot be processed at the receiver" and so on. What tempuri.org? I never pun any tempuri.org in my project! OK, turns out it is some default name that was used because I have not provided my own.

Client configuration

Just a small bit of configuration was required here (and I'm not even 100% sure that all of it is required)

<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="basicHttp"/>
</basicHttpBinding>
</bindings>
<client>
<!-- this endpoint is exposed at the base address provided by host-->
<endpoint address="" binding="basicHttpBinding" contract="MyNamespace.IMyInterface" />
</client>
</system.serviceModel>

Overall, it's just a few dozen lines of code, but it took me almost a whole day to get it working properly through the network.

by . Also posted on my website

No comments: