Skip to content
Tags

WCF MSMQ Binding to the Rescue: Service Down? Queue Messages on the Client!

July 1, 2012

In my continuing studies of the WCF technology I have begun to explore the WCF netMsmqBinding.  This article presents a very simple example of its use.  In addition to being a good learning exercise, this example may be useful in real life situations.  

For example, what happens when a Client sends a WCF message to a Service over an endpoint implementing the netTcpBinding binding and the Service is not running at the time?  The proxy goes into a faulted state on a timeout, becomes unusable, and it is up to the Client to detect that and take corrective action.  By adding a second endpoint implementing the netMsmqBinding and implementing a little additional logic to the Client, a failure to send the message over TCP will cause to Client to send it over the netMsmqBinding instead.  

Long story short, if the service is not responding all messages will be queued on the Client.  When the service starts again it will automatically retrieve all queued messages from the Client over the Service’s WCF MSMQ endpoint and no messages will be lost.

Ingredients

  • Client and server both implement 2 endpoints, one using the netTcpBinding and the other using the netMsmqBinding.
  • Client puts names on each of the 2 endpoints:  “ServiceIsRunning” on the netTcpBinding and “ServiceIsNotRunning” on the netMsmqBinding.
  • Client needs logic to do:  Client first attempts to send a message to the Service over the netTcpBinding within a try/catch statement.  If an exception is caught, then the Client sends the message over the netMsmqBinding, which will immediately queue it. 
  • Client must instantiate a new instance of its proxy if the proxy becomes faulted, as in when the service is not running. A fault disables a proxy from further use.  Thus, to send the message via the netMsmqBinding after a failure to send it over the netTcpBinding requires that the Client make a new instance of the proxy.

Limitation

This scheme will not work with an http binding on the ServiceIsRunning endpoint!  Why?  Because the netMsmqBinding will not go through firewalls, being designed to work only on intranets.  A pattern called the Http Bridge is required to use the netMsmqBinding over the internet.  For more information please see “Programming WCF Services, 3rd Edition” by Juval Lowy.  Copyright 2010.  O’Reilly Media, Inc.  Sebastapol, CA.  pp 462 – 463 and p 518 for the Http Bridge.

The Code

The Visual Studio solution containing this code example can be down loaded from my OneDrive Public Folder in the file ServiceNotAvailableMSMQ.  Just click the link, then click “Download” on OneDrive’s top menu bar when it appears.

Be sure to read the explanatory comments in the code for the small but important details.

The Service code is extremely simple.  The Client code is a little more complex, but still simple.  Here is the code for the Service:

Service Host App.config:

<pre><?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <!--Must fully qualify service name to prevent an exception when Host is in
          a different project from the Service.-->
      <service name ="SimpleTestService.SimpleTestServiceImpl">
        <endpoint
          address="net.tcp://localhost:8001/SimpleTestServiceImpl"
          binding="netTcpBinding"
          contract="SimpleTestService.ISimpleTestService"
        />
        <endpoint
          address ="net.msmq://localhost/private/SimpleTestServiceNotAvailableQ"
          binding ="netMsmqBinding"
          bindingConfiguration="MSMQNoSecurity"
          contract="SimpleTestService.ISimpleTestService"
        />
      </service>
    </services>

    <bindings>
      <netMsmqBinding>
        <binding name="MSMQNoSecurity">
          <security mode="None"/>
        </binding>
      </netMsmqBinding>
    </bindings>
  </system.serviceModel>
</configuration></pre>

The Service code:

ISimpleTestService.cs

<pre>
namespace SimpleTestService
{
    [ServiceContract(Namespace = "GeorgeStevens.Software.Developer/2012/05")]
    public interface ISimpleTestService
    {
        // Best practices say that ServiceContract attribues for PerCall instancing should
        // also set SessionMode to NotAllowed.  However, NetTcpBinding does not allow a
        // SessionMode of NotAllowed, although MSMQ does.  Thus this best practice
        // must be avoided so as to make this example work, which uses both NetTcpBinding
        // and NetMsqmBinding.

        // NetMsqmBinding requires IsOneWay=true since a message from the Client is placed
        // in a queue on the Client.  Then later (possibly a long time later if the Client
        // is disconnected) the Service Hosts's queue listener detects that there are
        // messages in the queue and initiates the operation to get them from the Client
        // and has the Service process each message.
        //
        // Thus, the queue is an intermediary between the Client's service call and the
        // Service, and therefore there will never be a response from the service side of
        // a NetMsmqBinding back to the Client.  This includes exceptions in the Service.
        // Therefore it is pointless to use Fault Contracts and have the Client catch
        // Fault Exceptions when using the NetMsmqBinding.
        // For more info on the topic of NetMsmqBindings, see
        // "Programming WCF Services, 3rd Edition" by Juval Lowy.
        // Copyright 2010.  O'Reilly Media, Inc.  Sebastapol, CA. pp 461 - 464
        //
        // Keep in mind this Contract must support 2 endpoints, one with a netMsmqBinding
        // and one with a netTcpBinding.
        [OperationContract(IsOneWay = true)]
        void DisplayMsgFromClient(string message);
    }
}</pre>

SimpleTestServiceImpl.cs

<pre>
namespace SimpleTestService
{
    // PerCall InstanceContextMode means that each time the Service receives a message,
    // a new instance of the Service class is instantiated and run on a separate thread
    // on the server.  Thus, a new instance of SimpleTestServiceImpl is instantiated to
    // serve each separate message from the client, regardless of the endpoint being
    // serviced.   And, when there are multiple items in the Client queue, there is still
    // a separate instance of the service instantiated for each separate queue item.
    //
    // In general, using PerCall is a best practice since it frees the developer from
    // having to deal with the burden of managing the state of multiple threads sharing
    // one instance of a service.
    //
    // When used with queues, PerCall should be configured as described in the below
    // reference regarding transactions (which is beyond the scope of this example
    // which blatantly ignores transactions for the sake of simplicity and focus).
    // Also see the first comment in ISimpleTestService, plus for more
    // info on this topic, see "Programming WCF Services, 3rd Edition"
    // by Juval Lowy.  Copyright 2010.  O'Reilly Media, Inc.
    // Sebastapol, CA. pp 478 - 479

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    public class SimpleTestServiceImpl : ISimpleTestService, IDisposable
    {
        public SimpleTestServiceImpl()
        {
            // This constructor exists only so you can put a break point here to verify
            // that InstanceContextMode.PerCall does indeed instantiate a new instance
            // of the service for each and every message received, even queued messages.
        }

        public void DisplayMsgFromClient(string message)
        {
            Console.WriteLine("SimpleTestServiceImpl.DisplayMsgFromClient:\n   message = {0}\n", message);

            // It is a Best Practice for each serivce to handle all exceptions generated in the service.
            // But just to see what happens when there is an unhandled exception in a Service using
            // the NetMsmqBinding, generate an unhandled exception.
            if (message.Contains("888"))
            {
                Console.WriteLine("SimpleTestServiceImpl.DisplayMsgFromClient: 888, Service throws NullReferenceException\n");
                throw new NullReferenceException("Server exception");
            }
        }

        // In Per Call instancing mode WCF will automatically call IDisposable.Dispose()
        // after the service operation has ended and has returned any results to the client.
        // All the developer has to do is provide the Dispose() method, and WCF will invoke it.
        // In this case there is nothing to dispose of.  Dispose() is here for demonstration only.
        // For more info on this topic, see "Programming WCF Services, 3rd Edition"
        // by Juval Lowy.  Copyright 2010.  O'Reilly Media, Inc.
        // Sebastapol, CA. pp 171 - 175

        #region IDisposable Members

        public void Dispose()
        {
            Console.WriteLine("SimpleTestServiceImpl.Dispose() entered.\n");
        }

        #endregion
    }
}
</pre>

The Service Host Console code:

<pre>
namespace ServiceHostConsole
{
    public class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("ServiceHostConsole:  Press ENTER to stop.\n");

            ServiceHost svcHost = new ServiceHost(typeof(SimpleTestService.SimpleTestServiceImpl));
            svcHost.Open();

            Console.ReadLine();
            svcHost.Close();
        }
    }
}
</pre>

The code for the Client is a little more complex since it must initialize the MSMQ queue.  I’ve also added a feature to report how many items there are in the MSMQ queue that adds a little complexity.  

The Client proxy code:

This is completely hand coded.  It uses ClientBase<T> as does an automatically generated proxy, rather than ChannelFactory<T> which was used my previous WCF article “A WCF Proxy From Scratch”.

<pre>
[ServiceContract(Namespace = "GeorgeStevens.Software.Developer/2012/05")]
public interface ISimpleTestService
{
    // MSMQ Requires IsOneWay=true.  See comments in ISimpleTestService.
    [OperationContract(IsOneWay = true)]
    void DisplayMsgFromClient(string message);
}

public class SimpleTestServiceClient : ClientBase<ISimpleTestService>, ISimpleTestService
{
    public SimpleTestServiceClient(string endpointConfigName) : base (endpointConfigName)
    {}

    public void DisplayMsgFromClient(string message)
    {
        Channel.DisplayMsgFromClient(message);
    }
}</pre>

The Client Console Code: 

The main work is done in ProcessUserInput() shown subquently.  Below is Main() and a few helpers.

<pre>
    public class Program
    {
        // In real life the queue name and path would be read from a Config File.
        private const string ServiceNotAvailableQueueName = @"private$\simpletestservicenotavailableq";
        private const string ServiceNotAvailableQueuePath = @".\" + ServiceNotAvailableQueueName;

        private static int m_LastMSMQMessageCount = 0;
        private static int m_ServiceCallNumber = 0;
        private static string m_LocalMachineName;

        public static void Main(string[] args)
        {
            m_LocalMachineName = System.Environment.MachineName;
            InitQueue();

            string input = GetUserInput();

            while (!string.IsNullOrEmpty(input))
            {
                ProcessUserInput(input);
                input = GetUserInput();
            }
        }

        private static string GetUserInput()
        {
            DisplayUserInstructions();
            return  Console.ReadLine();
        }

        private static void DisplayUserInstructions()
        {
            Console.WriteLine("\nClientTestConsole: Type in a message.  Press ENTER to send message.\n                   Press ENTER without a message to quit.\n");
        }

        private static void InitQueue()
        {
            if (!MessageQueue.Exists(ServiceNotAvailableQueuePath))
            {
                MessageQueue.Create(ServiceNotAvailableQueuePath, true);
            }
        }

        private static int GetCountOfMessagesInQueue(string machineName, string queueName)
        {
            MessageQueue[] queues = MessageQueue.GetPrivateQueuesByMachine(machineName);
            MessageQueue msgQ = queues.Where(q => q.QueueName == queueName).FirstOrDefault();
            System.Messaging.Message[] msgs = msgQ.GetAllMessages();
            int msgCount = msgs.Length;
            return msgCount;
        }
    }
</pre>

The main work is done in ProcessUserInput(), as follows:

<pre>
    private static void ProcessUserInput(string userInput)
    {
        string clientStatusMessage = string.Empty;
        string message = string.Empty;
        SimpleTestServiceClient proxy = new SimpleTestServiceClient("ServiceIsRunning");
        try
        {
            m_LastMSMQMessageCount = GetCountOfMessagesInQueue(m_LocalMachineName, ServiceNotAvailableQueueName);
            message =
                    string.Format("Client's service call number = {0}.  User Input = '{1}'.\n   # items in Queue before sending or queueing this message = {2}",
                                ++m_ServiceCallNumber, userInput, m_LastMSMQMessageCount);
            Console.WriteLine("ClientTestConsole: SENDING MESSAGE =\n   '{0}'", message);
            proxy.DisplayMsgFromClient(message);
            clientStatusMessage = "SENT";
        }
        catch (CommunicationException ex)
        {
            // The Service was NOT running, thus the ServiceIsRunning endpoint was not found which
            // causes an EndPointNotFoundException that derives from CommunicationException.
            proxy.Abort();
            proxy = new SimpleTestServiceClient("ServiceIsNotRunning");
            clientStatusMessage = "QUEUED";
            proxy.DisplayMsgFromClient(message);
        }
        // Ignore catching other exceptions so as not to clutter up the main point of
        // this example.
        finally
        {
            m_LastMSMQMessageCount = GetCountOfMessagesInQueue(m_LocalMachineName, ServiceNotAvailableQueueName);
            Console.WriteLine("ClientTestConsole: MESSAGE WAS {0}: message =\n   '{1}'.\n# items now in Queue = {2}", clientStatusMessage, message, m_LastMSMQMessageCount);

            try
            {
                proxy.Close();
            }
            catch (Exception ex)
            {
                Console.WriteLine("ClientTestConsole: The Service was running, successfully received the message, but had an unhandled internal exception.\n\n  {0}", ex.Message);
            }
        }
    }
</pre>

Run It

First, you will have to have MSMQ installed and enabled on your computer.  For generally how to go about this, please see pages 382 – 386 of “Learning WCF” by Michele Leroux Bustamante Copyright 2007.  O’Reilly Media, Inc.  Sebastapol, CA.

Next, build the solution.  Then start the Client by placing your mouse cursor on the ClientTestConsole1 project, right clicking and selecting Debug->Start New Instance.  You will see a Console Window.

Input a couple letters and press enter.  There will be an attempt to send the message, which will fail since the Service Host has not been started.  After a brief delay the message will be queued by the netMSMQBinding endpoint.

Do the above a couple more times.  Notice that each message will be queued.

Now start the Service Host and Service:  Place your mouse cursor over the ServiceHostConsole project, right click and selecting Debug->Start New Instance.  You will see a second Console Window, and almost immediately see the Service retrieve the items currently in the Client queue from the above failed message sends over TCP.

Play around with it, sometime having the Service Host running and other times not running.  Put some break points in the code and see what happens.

To cause the Service to generate an unhandled exception enter a message of 888 into the client.

I hope you find this as useful and fun as I did!

George Stevens

Creative Commons License
dotnetsilverlightprism blog by George Stevens is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
Based on a work at dotnetsilverlightprism.wordpress.com.

Advertisements
Leave a Comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: