Saturday, August 16, 2014

WCF - Duplex Named Pipe Binding

Today topic cover about how to create a WCF service which uses named pipe communication channel but also support duplex communication. In a normal web service behavior, client sends request message to the server, the server process the request then respond the message back to the client, therefore, it always need to have a requester to call the web service. What if you need your service to automatically send response to the client, but the client does not require to send request as always? Duplex communication allow server to interact with the client.

For today blog post, I have created a simple Hello World WCF service that support duplex communication and uses NetNamedPipeBinding and then the service is hosted in IIS with WAS (Windows Process Activation Service). This Hello World service allows clients to subscribe itself to the service to allow the service spam them with "Hello World!" words.

First, let's create the service first, defining the service contracts and operation contracts. In order to create a duplex communication service, we need to create a duplex contract first. This duplex contract will be used by the client to implement the service callback method, which also mean the service will be firing this callback method to send response to the client. 

Server Side

For my case, my service is going to spam "Hello World!" string to the client, so I define my duplex contract as follow:

public interface IHelloWorldServiceCallback
{
    [OperationContract(IsOneWay=true)]
    void Spam(string message);
}

Note that I have set IsOneWay as true for the named parameter in my operation contract. It is because after the client subscribed itself to the server, server automatically send message to the client, server does not expect a reply from the client.

Then, define the normal service contract as follow:

[ServiceContract(
    SessionMode=SessionMode.Required,
    CallbackContract=typeof(IHelloWorldServiceCallback))]
public interface IHelloWorldService
{
    [OperationContract]
    void SpamMe();

    [OperationContract]
    void StopSpam();
}

Note that I have specified the Session Mode named parameter with SessionMode.Required. It is required when creating a duplex communication service because the server need to keep track the client session, and then during firing the callback method, the service know the response message should be sent to which correct client.

The following is the implementation of my Hello World service contract:

[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
public class HelloWorldService : IHelloWorldService
{
    private static Dictionary<string, CancellationTokenSource> _sessionCTSs;
    static HelloWorldService()
    {
        //Create a single dictionary to keep all cancellation token source for tasks
        _sessionCTSs = new Dictionary<string, CancellationTokenSource>();
    }

    public void SpamMe()
    {
        //Client register a callback channel session for itself to the server
        var callback = OperationContext.Current.GetCallbackChannel<IHelloWorldServiceCallback>();
           
        //Get the client session Id
        var sessionId = OperationContext.Current.SessionId;

        //Server spawn a new thread that process and invoke the callback method
        var spamTaskCTS = new CancellationTokenSource();
        Task.Run(() =>
        {
            for (int i = 0; i < 100; i++ )
            {
                if (spamTaskCTS.IsCancellationRequested)
                {
                    callback.Spam("Client request stop spam!");
                    break;
                }

                callback.Spam("Hello World!");
                Thread.Sleep(1000);
            }
        }, spamTaskCTS.Token);

        _sessionCTSs.Add(sessionId, spamTaskCTS);
    }

    public void StopSpam()
    {
        //Cancel the existing thread base on Session Id
        var sessionId = OperationContext.Current.SessionId;
        var spamTaskCTS = _sessionCTSs[sessionId];
        spamTaskCTS.Cancel();
    }
}

The SpamMe() operation contract is to allow client register itself to the server that it want to subscribe the spam, asking the server to send message to it automatically. When the client invokes the SpamMe() operation, the client session ID will be kept in a dictionary. Then, a new thread will be created, the new thread will do the processing and invoke the callback method. The code above is actually sending "Hello World!" message to the client every one second for 100 times.

The StopSpam() operation contract is to allow client to request server to stop sending message to it. The method will get the client session ID from the same dictionary, and then cancel the sending message task. Once the task is cancelled, the service will not invoke the callback method anymore.

Host

Now, host the above service in IIS. The following is how my configuration file look like:

<system.serviceModel>
  <serviceHostingEnvironment multipleSiteBindingsEnabled="true">
    <serviceActivations>
      <add relativeAddress="./HelloWorldService.svc"
            service="NamedPipeDuplexSample.Services.HelloWorldService"
            factory="System.ServiceModel.Activation.ServiceHostFactory" />
    </serviceActivations>
  </serviceHostingEnvironment>
  <services>
    <service name="NamedPipeDuplexSample.Services.HelloWorldService"
              behaviorConfiguration="">
      <endpoint address="net.pipe://localhost/NamedPipeDuplexServer/HelloWorldService.svc"
                binding="netNamedPipeBinding"
                contract="NamedPipeDuplexSample.Services.Contracts.IHelloWorldService">
      </endpoint>
      <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"></endpoint>
    </service>
  </services>
  <behaviors>
    <serviceBehaviors>
      <behavior>
        <!-- To avoid disclosing metadata information, set the values below to false before deployment -->
        <serviceMetadata httpGetEnabled="true" httpsGetEnabled="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="true"/>
      </behavior>
    </serviceBehaviors>
  </behaviors>
</system.serviceModel>

The following are the steps to configure IIS to enable named pipe protocol. Before that, you need to ensure you have install WAS (Windows Process Activation Service) in your server first.

At the website level, right click and open the Edit Bindings menu, make sure you have net.pipe binding enabled as you see in the screenshot below.


Setup and host your service at the website that you had configured just now.

At your application level, right click then go to Manage Application, open Advanced Settings menu. Enter net.pipe to enable the named pipe protocol in your application as you see in the screenshot below:



Done.

Client Side

For the client side, I have created a simple console application to test my duplex WCF service. We need to implement the callback interface which we had defined in the server side code above. 

public class HelloWorldServiceCallback : IHelloWorldServiceCallback
{
    public void Spam(string message)
    {
        Console.WriteLine(message);
    }
}

So, when the server invoke the callback method, this method at client side will be fired with the message from the server.

The following is the code of how to connect to the WCF service hosted in IIS with named pipe channel:

class Program
{
    static void Main(string[] args)
    {
        var address = new EndpointAddress("net.pipe://localhost/NamedPipeDuplexServer/HelloWorldService.svc");
        var binding = new NetNamedPipeBinding();
        var callback = new HelloWorldServiceCallback();
        var context = new InstanceContext(callback);
        var factory = new DuplexChannelFactory<IHelloWorldService>(context, binding, address);
        var service = factory.CreateChannel();

        service.SpamMe();
        Console.ReadKey();

        service.StopSpam();
        Console.ReadKey();
    }
}

Once done, simply run the console program, then you will get the result as follow:


You even can run multiple console program to test the WCF session. Simply hit one key in one of the console program, only the correct one console will stop, the rest are remain untouched.



If you are interested with my source code, feel free to get it from HERE.




4 comments:

  1. " Setup and host your service at the website that you had configured just now."
    Care to elaborate please?

    ReplyDelete
  2. Is there an advantage of storing your cancellation token in a static dictionary instead of a non-static property?

    ReplyDelete
    Replies
    1. for posterity: Each service class instance is running as "instance", and as threadpool is not running code outside the service class instance, there is an advantage if you want to implement complex logic outside service class instance. For example service that has internal maintenance thread running on it's own and you want to receive messages when this internal thread reaches specific points during its processing.

      Delete
  3. The essential reason to have an expert to clean your carpet is that they are certified and professional too. This is immensely important because a certified carpet cleaner knows exactly how to clean every kind of carpet. Duct Replacement Services Melbourne

    ReplyDelete

Send Transactional SMS with API

This post cover how to send transactional SMS using the Alibaba Cloud Short Message Service API. Transactional SMS usually come with One Tim...