Search This Blog

Saturday, May 17, 2014

WinForm Application as Service

Simple example showing WinForm application as a service. It also shows how to set the threading model so that Eneter dispatches messages in main UI thread.

The source code for this example can be downloaded here.

Introduction

I typically use a console application as a service in my examples. I have received several questions regarding how to implement the service as a WinForm application. So I have decided to write a separate article demonstrating this possibility.

The example bellow implements a simple request-response communication between two WinForm applications.
The service WinForm application allows to start and stop the service with a button and it maintains a list of connected clients in the listbox. Then it allows to send a message to the selected client and it also allows to disconnect the selected client.
The client WinForm application allows to connect or disconnect the service with a button. It also allows to send a message to the service and receive a message from the service.

To Run Example

  1. Download Eneter for .NET.
  2. Download this example and open its solution in Visual Studio.
  3. Add Eneter.Messaging.Framework.dll library to 'References' of both projects inside the solution.
  4. Build the solution.
  5. Run WinFormServiceApplication and press 'Start Service'.
  6. Run WinFormClientApplication and press 'Open Connection'.
  7. The client should get connected and should appear in the 'Connected Clients' list.

Threading Model

To receive messages Eneter framework implements listening threads. When a message is then received it is the listening thread which notifies it. The problem is the listening thread is not the main UI thread. Therefore it is not possible to use this thread to access UI controls.
To overcome this issue Eneter allows to specify the threading model. It means it allows to specify in which thread events receiving messages or notifying connection/disconnection shall be dispatched.



Eneter provides several threading models. The example bellow uses WinFormsDispatching which ensures messages are received in the main UI thread.

WinForm Service Application

The implementation of the service as a WinForm application is very similar to implement it as a console application. The only difference is you must consider the threading model.
It means whether you want Eneter to notify messages in the main UI thread or not.
If not then you cannot access UI controls directly but you must dispatch the access to the main UI thread.
If yes then Eneter makes the dispatching for you and you can access UI controls.

using System;
using System.Windows.Forms;
using Eneter.Messaging.EndPoints.TypedMessages;
using Eneter.Messaging.MessagingSystems.MessagingSystemBase;
using Eneter.Messaging.MessagingSystems.TcpMessagingSystem;
using Eneter.Messaging.Threading.Dispatching;

namespace WinFormServiceApplication
{
    public partial class Form1 : Form
    {
        // Receive string messages and send back string messages.
        IDuplexTypedMessageReceiver<string, string> myReceiver;

        public Form1()
        {
            InitializeComponent();

            // Create message receiver.
            // Note: it receives string and sends back string.
            IDuplexTypedMessagesFactory aReceiverFactory = new DuplexTypedMessagesFactory();
            myReceiver = aReceiverFactory.CreateDuplexTypedMessageReceiver<string, string>();

            // Subscribe to get notified when a client connects, disconnects
            // or sends a message.
            myReceiver.ResponseReceiverConnected += OnClientConnected;
            myReceiver.ResponseReceiverDisconnected += OnClientDisconnected;
            myReceiver.MessageReceived += OnMessageReceived;
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            // Detach input channel and stop listening.
            myReceiver.DetachDuplexInputChannel();
        }

        private void OnClientConnected(object sender, ResponseReceiverEventArgs e)
        {
            // Add the client id to the listbox.
            // Note: we can directly access the listbox because we set threading mode of
            //       InputChannelThreading to the main UI thread.
            ConnectedClientsListBox.Items.Add(e.ResponseReceiverId);
        }

        private void OnClientDisconnected(object sender, ResponseReceiverEventArgs e)
        {
            // Remove the client from the listbox.
            // Note: we can directly access the listbox because we set threading mode of
            //       InputChannelThreading to the main UI thread.
            ConnectedClientsListBox.Items.Remove(e.ResponseReceiverId);
        }

        private void OnMessageReceived(object sender, TypedRequestReceivedEventArgs<string> e)
        {
            // Insert received message at the beginning of the listbox.
            // Note: we can directly access the listbox because we set threading mode of
            //       InputChannelThreading to the main UI thread.
            ReceivedMessagesListBox.Items.Insert(0, e.RequestMessage);
        }


        private void StartServiceBtn_Click(object sender, EventArgs e)
        {
            if (myReceiver.IsDuplexInputChannelAttached)
            {
                // The channel is already attached so nothing to do.
                return;
            }

            // Use TCP communication
            IMessagingSystemFactory aMessaging = new TcpMessagingSystemFactory()
            {
                // Set to receive messages in the main UI thread.
                // Note: if this is not set then methods OnMessageReceived, OnClientConnected
                //       and OnClientDisconnected would not be called from main UI thread
                //       but from a listener thread.
                InputChannelThreading = new WinFormsDispatching(this)
            };

            // Create input channel.
            IDuplexInputChannel anInputChannel
              = aMessaging.CreateDuplexInputChannel("tcp://127.0.0.1:8033/");

            // Attach the input channel and be able to receive messages
            // and send back response messages.
            myReceiver.AttachDuplexInputChannel(anInputChannel);

            ServiceStatusLabel.Text = "Service Running";
        }

        private void StopServiceBtn_Click(object sender, EventArgs e)
        {
            // Detach input channel and stop listening.
            myReceiver.DetachDuplexInputChannel();

            ServiceStatusLabel.Text = "Service Not Running";
        }

        private void SendMessageBtn_Click(object sender, EventArgs e)
        {
            string aClientId = GetSelectedClient();
            if (!string.IsNullOrEmpty(aClientId))
            {
                string aMessage = MessageTextBox.Text;

                // Send message to client which is selected in the listbox.
                myReceiver.SendResponseMessage(aClientId, aMessage);
            }
        }

        private void DisconnectClientBtn_Click(object sender, EventArgs e)
        {
            // Disconnect client which is selected in the listbox.
            string aClientId = GetSelectedClient();
            if (!string.IsNullOrEmpty(aClientId))
            {
                myReceiver.AttachedDuplexInputChannel.DisconnectResponseReceiver(aClientId);
            }
        }

        private string GetSelectedClient()
        {
            string aClientId = ConnectedClientsListBox.SelectedItem as string;
            return aClientId;
        }
    }
}
The implementation is very simple:


WinForm Client Application

I used WinForm application as client in many previous examples. Therefore there is nothing new here.

The implementation is very simple:

using System;
using System.Windows.Forms;
using Eneter.Messaging.EndPoints.TypedMessages;
using Eneter.Messaging.MessagingSystems.MessagingSystemBase;
using Eneter.Messaging.MessagingSystems.TcpMessagingSystem;
using Eneter.Messaging.Threading.Dispatching;

namespace WinFormClientApplication
{
    public partial class Form1 : Form
    {
        // Sender which sends string request messages
        // and receives string response messages.
        IDuplexTypedMessageSender<string, string> mySender;

        public Form1()
        {
            InitializeComponent();

            // Create message sender.
            IDuplexTypedMessagesFactory aSenderFactory = new DuplexTypedMessagesFactory();
            mySender = aSenderFactory.CreateDuplexTypedMessageSender<string, string>();

            // Subscribe to handle when a message is received or if the client
            // is disconnected.
            mySender.ConnectionClosed += OnConnectionClosed;
            mySender.ResponseReceived += OnResponseReceived;
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            mySender.DetachDuplexOutputChannel();
        }

        private void OnResponseReceived(object sender, TypedResponseReceivedEventArgs<string> e)
        {
            // Insert the received message at the beginning of the listbox.
            // Note: we can directly access the UI control here because we set threading mode of
            //       OutputChannelThreading to the main UI thread.
            ReceivedMessagesListBox.Items.Add(e.ResponseMessage);
        }

        private void OnConnectionClosed(object sender, DuplexChannelEventArgs e)
        {
            // The client got disconnected so set the status.
            // Note: we can directly access the UI control here because we set threading mode of
            //       OutputChannelThreading to the main UI thread.
            ConnectionStatusLabel.Text = "Client Not Connected";
        }

        private void OpenConnectionBtn_Click(object sender, EventArgs e)
        {
            if (!mySender.IsDuplexOutputChannelAttached)
            {
                // The output channel is not attached yet.
                // So attach the output channel and be able to send
                // request messagas and receive response messages.
                IMessagingSystemFactory aMessaging = new TcpMessagingSystemFactory()
                {
                    // Set to receive messages in the main UI thread.
                    // Note: if this is not set then methods OnResponseReceived and
                    //       OnConnectionClosed would not be called from main UI thread
                    //       but from a listener thread.
                    OutputChannelThreading = new WinFormsDispatching(this)
                };
                IDuplexOutputChannel anOutputChannel
                   = aMessaging.CreateDuplexOutputChannel("tcp://127.0.0.1:8033/");
                mySender.AttachDuplexOutputChannel(anOutputChannel);

                ConnectionStatusLabel.Text = "Client Connected";
            }
            else if (!mySender.AttachedDuplexOutputChannel.IsConnected)
            {
                // The output channel is attached but the client got disconnected.
                // So jut reopen the connection.
                mySender.AttachedDuplexOutputChannel.OpenConnection();

                ConnectionStatusLabel.Text = "Client Connected";
            }
        }

        private void CloseConnectionBtn_Click(object sender, EventArgs e)
        {
            mySender.DetachDuplexOutputChannel();
        }

        private void SendMessageBtn_Click(object sender, EventArgs e)
        {
            // If the client is connected to the service.
            if (mySender.IsDuplexOutputChannelAttached &&
                mySender.AttachedDuplexOutputChannel.IsConnected)
            {
                string aMessage = MessageTextBox.Text;
                mySender.SendRequestMessage(aMessage);
            }
        }
    }
}

10 comments:

  1. Hello, is there a sample in .NET for Sending/receiving multiple types of messages by one sender/receiver (feature of new version 6.5)?

    ReplyDelete
    Replies
    1. Right now I do not have a detailed article like this one (but I plan to provide one soon).
      I think you could check examples in the online API help:
      http://www.eneter.net/OnlineHelp/EneterMessagingFramework/html/T_Eneter_Messaging_EndPoints_TypedMessages_MultiTypedMessagesFactory.htm

      The basic idea is that you can call
      aReceiver.RegisterRequestMessageReceiver(...) to register types of messages you want to process on the service side and you can call
      mySender.RegisterResponseMessageReceiver(...) to regiter types of messages you want to process on the client side.

      I hope my response helps. If you haf more questions please feel free to contact me via e-mail. I will try to support you.

      Delete
    2. Thank you for you tip. I have just the problem to register 2 MessageReceivers
      with different resultsets.

      mySender.RegisterResponseMessageReceiver>(OnGetContainer);
      mySender.RegisterResponseMessageReceiver>(OnGetMetaData);

      On second receiver i get an error that eventhandler is already registered?
      How can i define more receivers with different data classes?
      Best regards ...

      Delete
    3. It looks you like you are trying to register handler for the same message type multiple times.
      The idea of MultiTypedMessageSender/Receiver is that you register a handler for each message type. So if you need to receive multiple types of messages you then each message must be of different type.

      E.g.:

      public class ResponseMessage1
      {
      ...
      }

      public class ResponseMessage2
      {
      ...
      }

      and then:
      mySender.RegisterResponseMessageReceiver<ResponseMessage1>(..);
      mySender.RegisterResponseMessageReceiver<ResponseMessage2>(..);

      Delete
    4. I have different message types. I have made a example in C#. Can i send you this via mail attachment. How is your mail adress? Thank you for help ...

      Delete
  2. I figured out where the Problem is. I need the resultset as list
    When also list is defined following error occurs:
    MultiTypedMessageSender failed to register handler for response message List`1 because the handler for such class name is already registered.

    ReplyDelete
  3. Can you please fix this issue? List of ResponseMessage1 and List of ResponesMessage2 is not working such this error failed to register handler ...
    Thank you ...

    ReplyDelete
    Replies
    1. If I understand correctly you try to specify the type of the response message like:
      List<ResponseMessage1> and the second one List<ResponseMessage2>.

      So that then when you register them:
      mySender.RegisterResponseMessageReceiver<List<ResponseMessage1>>(..);
      mySender.RegisterResponseMessageReceiver<List<ResponseMessage2>>(..);

      The framework uses the type name to recognize between types. Unfortunately .NET does not include the type of the generic parameter into the name. So it returns just List'1.
      To fix it means to create own name creation including using the reflection which can impact the performance.

      Therefore I would rather recommend to define the response message types slightly differently:

      public class ResponseMessage1
      {
      List<YourType1> Items;
      }

      public class ResponseMessage2
      {
      List<YourType2> Items;
      }

      Note:
      In case you plan your communication should work across .NET and Java then you should not use generics in messages at all.

      Delete
  4. Thank you for your answer. I'm not familiar with java but how to transfer lists between .NET and Java? Using JSON?

    ReplyDelete
    Replies
    1. If you need the cross platform communication between .NET and Java applications then you need to use arrays instead of generics.
      E.g. instead of:

      public class Message
      {
      public List<string> Items { get; set; }
      }

      you need to declare:

      public class Message
      {
      public string[] Items { get; set; }
      }

      Then in Java the matching declaration would be:

      public class Message
      {
      public String Items;
      }

      The reason why messages must be declared without generics is that Java does not support retrieving the type of generic parameter. So it is not matter of the serializer.
      Once your declaration does not contain generics then you can use any serializer which works across .NET and Java. E.g. XmlSerializer inside Eneter, JSON or ProtocolBuffer.

      More details how to use various serializers you can be found here:
      http://eneter.blogspot.de/search/label/Serialization

      Delete