Saturday, May 28, 2011

WCF Duplex Message Exchange Pattern - Simple sample

This post just explains how to create a simple WCF service of Duplex MEP(Message Exchange Pattern).
  • Two Service contracts need to be created.
  • First, create a Callback contract which the client will implemnt. The service will use this Callback contract to call the client back.
  • Then as for other WCF services create a Service contract and associate the Callback contract to this Service contract using the CallbackContract property of the Service contract attribute.
  • Specify the Operation contracts as "OneWay".
  • Session is required(enabled by default).
  • Get a channel to the current client instance using OperationContext.Current.GetCallbackChannel<>() to call the callback methods.
  • Configure the service with the wsDualHttpBinding.
Snippet 1 - Service Contract

namespace DuplexWcfService1
{
    [ServiceContract]
    public interface IAddHandler
    {
        [OperationContract(IsOneWay = true)]
        void Resultant(double result);
    }

    [ServiceContract(Namespace = "DuplexWcfService1", CallbackContract = typeof(IAddHandler))]    
    public interface IAddService
    {
        [OperationContract(IsOneWay = true)]
        void Add(double param1, double param2);
    }
}

Snippet 2 - Service Implementation

namespace DuplexWcfService1
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
    public class AddService : IAddService
    {
        public void Add(double param1, double param2)
        {
            double result = param1 + param2;

            IAddHandler handler = OperationContext.Current.GetCallbackChannel<IAddHandler>();
            handler.Resultant(result);
        }
    }
}

Snippet 3 - Configuration

<system.serviceModel>
  <services>
    <service name="DuplexWcfService1.AddService">
      <endpoint address="AddService" binding="wsDualHttpBinding" contract="DuplexWcfService1.IAddService" />
      <host>
        <baseAddresses>
          <add baseAddress="http://localhost:2290/"/>
        </baseAddresses>
      </host>        
    </service>
  </services>
  
  <behaviors>
    <serviceBehaviors>
      <behavior>
        <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
        <serviceMetadata httpGetEnabled="true"/>
        <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
        <serviceDebug includeExceptionDetailInFaults="false"/>
      </behavior>
    </serviceBehaviors>
  </behaviors>
  <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>

I've posted the above sample snippets with namespace for valid reasons. In my configuration I've specified the "name" property of the "service" element as fully qualified name of my service implementation.

If I failed to do so, like if I mentioned "AddService" instead of "DuplexWcfService1.AddService" then, the error "Contract requires Duplex, but Binding 'BasicHttpBinding' doesn't support it or isn't configured properly to support it." will be raised.
Hence be cautious on specifying the values for properties in the configuration.

Now create a console application for consuming the above created service. For that,
  • First add the service reference to the service.
  • Create an implementation for the Callback contract.
  • Call the operation to infer the Duplex MEP.
Snippet 4 - Callback implementation

public class AddHandler : AddServiceServiceReference1.IAddServiceCallback
{

    public void Resultant(double result)
    {
        Console.WriteLine("add result : " + result.ToString());            
    }
}

Snippet 5 - Consuming the service

class Program
{
    static void Main(string[] args)
    {
        InstanceContext instanceContext = new InstanceContext(new AddHandler());
        AddServiceServiceReference1.AddServiceClient serviceClient = new AddServiceServiceReference1.AddServiceClient(instanceContext);

        serviceClient.Add(10, 90);
        Console.ReadKey();
    }
}

In order to understand the implementation easily, I've used primitive data types. Use custom types and scale up the scenario when you try on your own. 

2 comments:

Creative Commons License
This work by Tito is licensed under a Creative Commons Attribution 3.0 Unported License.