Published Thursday, October 26, 2006 4:36 PM by mtaulty

WCF, MSMQ and CompositeDuplex

One of the clever things that the WCF can do for you is to take 2 one-way communication transports and turn them into a logical duplex transport.

This crops up with the wsDualHttpBinding which takes one HTTP request/response channel from the client to the service and another HTTP request/response channel from the service back to the client (assuming you have that connectivity) and ties them together to form a duplex channel.

So...a client can talk to the service over HTTP and send the service a message and some time later the service can use the CallbackChannel to reply back to the client and that reply will be delivered to an HTTP endpoint back in the client. Naturally, when the client first makes its request to the service it'll be needing to include details of a reply address otherwise this can't work.

One of the things that I puzzled over earlier on in the days of the WCF is the absence of an MSMQ duplex binding. Given that MSMQ is a very obviously one-way transport it initially seems an obvious one to include although when I really thought about it I'm not sure that it's likely to be particularly useful because MSMQ tends to encourage a client and service to have different lifetimes and that might be a little odd for a duplex channel :-) i.e.

  1. Client sends message to service that's not running. Client has an object instance for the "callback". Client exits.
  2. Service runs and picks up message and later on replies to client.
  3. Client runs and picks up the reply. However, it's a response to a previous instance of the client and this instance hasn't a clue what to do with that message.

You can probably make this work but it does look like it can get a little bit weird a bit quickly and hence that's perhaps one of the reasons why it's not included in the box.

Regardless...I thought that I'd press on and see what I can do to play with using the MSMQ transport as a duplex transport just to see what it was like to take a WCF one-way transport and try and use it as a duplex one.

I built myself a tiny contract;

 

[ServiceContract]
public interface ICallback
{
  [OperationContract(IsOneWay=true)]
  void StringWasPrinted(DateTime when);
}

 

[ServiceContract(CallbackContract=typeof(ICallback))]
public interface IContract
{
  [OperationContract(IsOneWay=true)]
  void PrintString(string s);
}

I implemented that in my service to print the message from the client to the Console (this is a long way from rocket-science as you can already tell) and to call back to the client with a slight delay;

 

public class Implementation : IContract
{
  public void PrintString(string s)
  {
    Console.WriteLine(s);

    ICallback cb = OperationContext.Current.GetCallbackChannel<ICallback>();

    ThreadPool.QueueUserWorkItem(delegate
    {
      Thread.Sleep(5000);
      cb.StringWasPrinted(DateTime.Now);
    });
  }
}

and then went ahead and hosted that up in the usual way.

 

class Program
{
  static void Main(string[] args)
  {
    ServiceHost host = new ServiceHost(typeof(Implementation));
    host.Open();

    Console.WriteLine("Listening...");
    Console.ReadLine();
  }
}

with a service side config that looked like this;

 

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

  <system.serviceModel>

    <services>

      <service name="Implementation">

        <endpoint address="net.msmq://localhost/private/testqueue"
                  contract="IContract"
                  binding="customBinding"
                  bindingConfiguration="serviceConfig"/>
        
      </service>
      
    </services>

    <bindings>

      <customBinding>

        <binding name="serviceConfig">

          <compositeDuplex/>

          <msmqTransport exactlyOnce="false">
            <msmqTransportSecurity msmqAuthenticationMode="None" 
                                   msmqProtectionLevel="None"/>
          </msmqTransport>

          
        </binding>
        
      </customBinding>
      
    </bindings>
    
  </system.serviceModel>
  
</configuration>

I went off to build the client in the usual way. There's nothing much that goes on in the client but this piece of code shows what's in there;

 

public class Proxy : DuplexClientBase<IContract>, IContract 
{
  public Proxy(InstanceContext ctx) : base(ctx)
  {
      
  }
  public void PrintString(string s)
  {
    base.Channel.PrintString(s);
  }
}

public class CallbackClass : ICallback
{
  public void StringWasPrinted(DateTime when)
  {
    Console.WriteLine("String got printed [{0}]", when.ToShortTimeString());
  }
}

class Program
{
  static void Main(string[] args)
  {
    CallbackClass callback = new CallbackClass();

    InstanceContext instanceContext = new InstanceContext(callback);

    Proxy p = new Proxy(instanceContext);

    p.PrintString("Hello");

    Console.ReadLine();
  }
}
 

configured with;

 

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

  <system.serviceModel>

    <client>

      <endpoint address="net.msmq://localhost/private/testqueue"
                binding="customBinding"
                contract="IContract"
                bindingConfiguration="clientConfig">
      </endpoint>
      
    </client>

    <bindings>
      <customBinding>
        <binding name="clientConfig">

          <compositeDuplex 
            clientBaseAddress="net.msmq://localhost/private/replyqueue"/>           
          
          <msmqTransport exactlyOnce="false" useActiveDirectory="false"  >
            <msmqTransportSecurity msmqAuthenticationMode="None" 
                                   msmqProtectionLevel="None"/>
            
          </msmqTransport>

        </binding>
        
      </customBinding>
            
    </bindings>
    
  </system.serviceModel>
</configuration>
 

It looked like it would all work fine but it doesn't. I get an error from the client side when it comes to try and open the MSMQ queue that it's going to use for responses from the server.

I spent quite a long time scratching my head on this. Ultimately, I think it  comes down to a number of different properties that affect how the client listens for responses from the service. My hope was that by setting CompositeDuplexBindingElement.ClientBaseAddress I could affect the behaviour of the MSMQ queue that is used as a response queue on the client and, indeed, I think you can but there are some other properties that come into play which live on the BindingContext class and they are;

BindingContext.ListenUriBaseAddress

BindingContext.ListenUriMode

BindingContext.ListenUriRelativeAddress

and, to cut a long story short, I could find no way of setting these from code or config for my "Composite Duplex" scenario.

In the end, help came my way from Nick Allen who provided a means by which I can get hold of that BindingContext and change things around. As the BindingElements go to work in a binding, they can contribute to the BindingContext and so one way to add things into the BindingContext is to add an additional BindingElement into the CustomBinding that I'm using.

So, I went off to try and build a BindingElement (more or less copying some code that Nick gave me) and that also involved building a BindingElementExtensionElement (more or less copying some code from an SDK sample) and I ended up with this ListenUriBindingElement;

 

public class ListenUriBindingElement : BindingElement
{
  public ListenUriBindingElement()
  {
  }
  public ListenUriBindingElement(Uri listenUriBaseAddress, 
    string listenUriRelativeAddress, 
    ListenUriMode listenUriMode)
  {
    this.listenUriBaseAddress = listenUriBaseAddress;
    this.listenUriRelativeAddress = listenUriRelativeAddress;
    this.listenUriMode = listenUriMode;
  }
  public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
  {
    context.ListenUriBaseAddress = listenUriBaseAddress;
    context.ListenUriRelativeAddress = listenUriRelativeAddress;
    context.ListenUriMode = listenUriMode;
    return base.BuildChannelListener<TChannel>(context);
  }
  public override BindingElement Clone()
  {
    return new ListenUriBindingElement(listenUriBaseAddress, listenUriRelativeAddress, listenUriMode);
  }
  public override T GetProperty<T>(BindingContext context)
  {
    return context.GetInnerProperty<T>();
  }
  public Uri ListenUriBaseAddress
  {
    get { return listenUriBaseAddress; }
    set { listenUriBaseAddress = value; }
  }
  public string ListenUriRelativeAddress
  {
    get { return listenUriRelativeAddress; }
    set { listenUriRelativeAddress = value; }
  }
  public ListenUriMode ListenUriMode
  {
    get { return listenUriMode; }
    set { listenUriMode = value; }
  }
  private Uri listenUriBaseAddress;
  private string listenUriRelativeAddress;
  private ListenUriMode listenUriMode;
}

 

and in order to make this work from config I ended up with this ListenUriElement;

 

public class ListenUriElement : BindingElementExtensionElement
{
  public ListenUriElement()
  {
  }
  public override Type BindingElementType
  {
    get { return(typeof(ListenUriBindingElement)); }
  }
  public override void ApplyConfiguration(
    BindingElement bindingElement)
  {
    base.ApplyConfiguration(bindingElement);

    ListenUriBindingElement element = (ListenUriBindingElement)bindingElement;

    this.listenUriBaseAddress = element.ListenUriBaseAddress;
    this.listenUriRelativeAddress = element.ListenUriRelativeAddress;
    this.listenUriMode = element.ListenUriMode;
  }  
  protected override ConfigurationPropertyCollection Properties
  {
    get
    {
      ConfigurationPropertyCollection properties = new ConfigurationPropertyCollection();

      properties.Add(new ConfigurationProperty("listenUriBaseAddress", typeof(Uri)));

      properties.Add(new ConfigurationProperty("listenUriRelativeAddress", typeof(string),
        string.Empty));

      properties.Add(new ConfigurationProperty("listenUriMode", typeof(ListenUriMode),
        ListenUriMode.Unique));

      return(properties);
    }
  }
  protected override BindingElement CreateBindingElement()
  {
    ListenUriBindingElement bindingElement = new ListenUriBindingElement();

    bindingElement.ListenUriBaseAddress = listenUriBaseAddress;
    bindingElement.ListenUriRelativeAddress = listenUriRelativeAddress;
    bindingElement.ListenUriMode = listenUriMode;

    return bindingElement;
  }
  protected override void DeserializeElement(System.Xml.XmlReader reader, bool serializeCollectionKey)
  {
    listenUriBaseAddress = new Uri(reader.GetAttribute("listenUriBaseAddress"));

    listenUriRelativeAddress = reader.GetAttribute("listenUriRelativeAddress");

    listenUriMode = 
      (ListenUriMode)Enum.Parse(typeof(ListenUriMode), reader.GetAttribute("listenUriMode"));
  }
  public Uri ListenUriBaseAddress
  {
    get { return listenUriBaseAddress; }
    set { listenUriBaseAddress = value; }
  }  
  public string ListenUriRelativeAddress
  {
    get { return listenUriRelativeAddress; }
    set { listenUriRelativeAddress = value; }
  }  
  public ListenUriMode ListenUriMode
  {
    get { return listenUriMode; }
    set { listenUriMode = value; }
  }
  private ListenUriMode listenUriMode;
  private string listenUriRelativeAddress;
  private Uri listenUriBaseAddress;
}
 

and, finally, with that in place I can configure my client up like this (leaving my service well alone);

 

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

  <system.serviceModel>

    <client>

      <endpoint address="net.msmq://localhost/private/testqueue"
                binding="customBinding"
                contract="IContract"
                bindingConfiguration="clientConfig"
                name="myConfig">
      </endpoint>
      
    </client>

    <bindings>
      <customBinding>
        <binding name="clientConfig">
          
          <compositeDuplex/>

          <listenUri
            listenUriBaseAddress="net.msmq://localhost/private/testreplyqueue" 
            listenUriRelativeAddress="" 
            listenUriMode="Explicit">

          </listenUri>

          <msmqTransport exactlyOnce="false" useActiveDirectory="false"  >
            <msmqTransportSecurity msmqAuthenticationMode="None" 
                                   msmqProtectionLevel="None"/>
            
          </msmqTransport>

        </binding>
        
      </customBinding>
            
    </bindings>
    <extensions>
      <bindingElementExtensions>
        <add name="listenUri" type="ListenUriElement, ClientApp"/>
      </bindingElementExtensions>
    </extensions>    
  </system.serviceModel>
</configuration>
 

So, that was all looking pretty good for a while and, for the first time in this experiment, I got a happy client that sent a message to a happy service but then the service tried to reply and I got an exception from the service along the lines of;

For sending a message on server side composite duplex channels, the message must have either the 'Via' property or the 'To' header set.

This is where the service code invokes the "Callback channel" to talk back to the client and I guessed (and partially checked) that it's because the Soap message from the client in the first place doesn't seem to have a ReplyTo address in it.

I found this a bit weird. My expectation of using the CompositeDuplex binding element in the first place was that it would ensure that I had a channel that knew how to put a ReplyTo address into any messages it sent otherwise how would I ever get anything "duplex" to happen?

I found some similar information over here but it didn't fully answer the question.

In the end, I found (mostly by trial and error) that I can only get this to work if I switch on reliableSession. I must admit that I don't really know why this is and I'll try and update the post in the future with an answer as to why you need to switch this on in order for the service to reply to the client as I genuinely haven't a clue right now but there's probably a good reason for it.

Anyway, with reliable session switched on in my binding I can get this to work and I'm happy :-)

Here's the project file for where I ended up. This is on the RC1 bits. Note that to make this work you need 2 private MSMQ queues called;

testqueue

testreplyqueue

# Straight-forward WCF duplex communication over MSMQ? @ Friday, October 27, 2006 11:08 AM

My friend Mike Taulty has recently posted a cool post about his attempt to create a WCF extension to...

Christian Weyer: Smells like service spirit

# A thoughtful look at Mike's Dual MSMQ extension @ Friday, October 27, 2006 12:22 PM

Mike Taulty has posted an excellent post on his extension to use WCF duplex programming model on MSMQ....

Buddhike's Weblog

gipoco.com is neither affiliated with the authors of this page nor responsible for its contents. This is a safe-cache copy of the original web site.