Friday, December 16, 2016

Combining sync/async WCF client/server that share an interface-based contract

Having a shared interface between WCF client and server is handy, often you just put such contract in a shared assembly and you just build your service out of the contract and then use the ChannelFactory to automatically create clients.

// shared contract
[ServiceContract]
public interface IService1
{
  [OperationContract]
  string DoWork( string s );
}
// server
public class Service1 : IService1
{
  public string DoWork( string s )
  {
     return s + " from wcf";
  }
}
// client
var binding  = new BasicHttpBinding();
var endpoint = new EndpointAddress( "http://localhost:55748/Service1.svc" );
 
var factory = new ChannelFactory<IService1>( binding, endpoint );
var channel = factory.CreateChannel();
 
var result  = channel.DoWork("foo");

The problem starts when you decide to go async at the server or the client but in the same time keep the other sync – in MVC or in Windows.Forms you just change the method from Foo Method() to async Task<Foo> Method(), here in WCF you just can’t change the signature of the interface method as it affects both the client and the server.

For example, I you to be async at the server side so that you switch to the async interface of your data provider, and you change the interface to

[ServiceContract]
public interface IService1
{
  [OperationContract]
  Task<string> DoWork( string s );
}

// oops, the client doesn't compile anymore as the supposed contract has just changed
// the client is not "smart enough" to notice that in fact this is still the same contract 
// at the conceptual level

Fortunately, a simple solution exists, mentioned here. The solution is to have two interfaces, a sync one and an async one. Both interfaces have to share the name, however they can exist in the very same assembly, in adjacent namespaces. It turns out that both the client and the server could implement any of the two interfaces and wiring still succeeds.

namespace Sync
{
    // shared contract
    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        string DoWork( string s );
    }
}

namespace Async
{
    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        Task<string> DoWork( string s );
    }
}

An example async client, using the async interface, would be

  
private async void button1_Click( object sender, EventArgs e )
{
    var binding  = new BasicHttpBinding();
    var endpoint = new EndpointAddress( "http://localhost:55748/Service1.svc" );

    var factory = new ChannelFactory<Async.IService1>( binding, endpoint );
    var channel = factory.CreateChannel();

    var result = await channel.DoWork( "foo" );

    MessageBox.Show( result );
}

An example async server would be

public class Service1 : IService1
{
    public async Task<string> DoWork( string s )
    {
        await Task.Delay( 5000 );
        return s + " from wcf";
    }
}

The conclusion is: the shared assembly should just provide both interfaces, the sync and the async one, which lets both the client and the server freely decide which interface they implement. In a sense, the client and the server still share the same interface although technically these are two different interfaces.