Search This Blog

Tuesday, August 3, 2010

How to Implement Service Receiving Requests via Messages

Summary: The example in the article implements the service application Calculator. This service application listens to request messages and calculate numbers (summation, subtraction, multiplication and dividing). The result is then sent back to the requesting client as the response message.




To use the implementation bellow you must include the Eneter.Messaging.Framework.dll library into your project. (Full, not limited and for non-commercial usage free version of the framework can be downloaded from www.eneter.net. The online help for developers can be found http://www.eneter.net/OnlineHelp/EneterMessagingFramework/Index.html)

Service application
The Calculator service provides the following functionality: summation, subtraction, multiplication and dividing. Therefore the service recognizes four types of request messages. Every request message will take two numbers as the input for the calculation.

// Input data for calculator requests
public class CalculatorInputData
{
    public double Number1 { get; set; }
    public double Number2 { get; set; }
}

When the service calculates numbers it sends back the response message with the result.

// Output result from the calculator
public class CalculatorOutputData
{
    public double Result { get; set; }
}

We also want that the service listens to request messages on one address (one input channel). (It would not be effective if every type of request message would have its own address.)
To recognize multiple message types on one address we use the Channel Unwrapper component from Eneter.Messaging.Framework.

The Channel Unwrapper component receives messages on one input channel, "unwraps" them and forwards to correct receivers.
In our case the Channel Unwrapper recognizes if the incoming message is summation, subtraction, multiplication or dividing. Then it forwards the message to the correct receiver handling the requested type of calculation.

The code implementing the behavior above is very simple with Eneter.Messaging.Framework.

using System;

namespace ServerCalculator2
{
    class Program
    {
        static void Main(string[] args)
        {
            Calculator aCalculator = new Calculator();
            Console.WriteLine("Calculator service started.");
            aCalculator.StartCalculatorService();
        }
    }
}

using System;
using Eneter.Messaging.DataProcessing.Serializing;
using Eneter.Messaging.EndPoints.TypedMessages;
using Eneter.Messaging.Infrastructure.ConnectionProvider;
using Eneter.Messaging.MessagingSystems.MessagingSystemBase;
using Eneter.Messaging.MessagingSystems.TcpMessagingSystem;
using Eneter.Messaging.MessagingSystems.ThreadPoolMessagingSystem;
using Eneter.Messaging.Nodes.ChannelWrapper;

namespace ServerCalculator2
{
    // Input data for calculator requests
    public class CalculatorInputData
    {
        public double Number1 { get; set; }
        public double Number2 { get; set; }
    }

    // Output result from the calculator
    public class CalculatorOutputData
    {
        public double Result { get; set; }
    }

    internal class Calculator
    {
        public Calculator()
        {
            // We want that requests do not block each other. So every request will be processed in its own thread.
            IMessagingSystemFactory anInternalMessaging = new ThreadPoolMessagingSystemFactory();

            // We want to use Xml for serialization/deserialization.
            // Note: Alternative you can use: BinarySerializer
            ISerializer aSerializer = new XmlStringSerializer();

            // All messages are received via one channel. So we must provide "unwrapper" forwarding incoming messages
            // to correct receivers.
            IChannelWrapperFactory aChannelWrapperFactory = new ChannelWrapperFactory(aSerializer);
            myDuplexChannelUnwrapper = aChannelWrapperFactory.CreateDuplexChannelUnwrapper(anInternalMessaging);

            // To connect receivers and the unwrapper with duplex channels we can use the following helper class.
            IConnectionProviderFactory aConnectionProviderFactory = new ConnectionProviderFactory();
            IConnectionProvider aConnectionProvider = aConnectionProviderFactory.CreateConnectionProvider(anInternalMessaging);

            // Factory to create message receivers.
            // Received messages will be deserialized from Xml.
            IDuplexTypedMessagesFactory aMessageReceiverFactory = new DuplexTypedMessagesFactory(aSerializer);
            
            // Create receiver to sum two numbers.
            mySumReceiver = aMessageReceiverFactory.CreateDuplexTypedMessageReceiver<CalculatorOutputData, CalculatorInputData>();
            mySumReceiver.MessageReceived += SumCmd; // attach method handling the request
            aConnectionProvider.Attach(mySumReceiver, "Sum"); // attach the input channel to get messages from unwrapper

            // Receiver to subtract two numbers.
            mySubtractReceiver = aMessageReceiverFactory.CreateDuplexTypedMessageReceiver<CalculatorOutputData, CalculatorInputData>();
            mySubtractReceiver.MessageReceived += SubCmd; // attach method handling the request
            aConnectionProvider.Attach(mySubtractReceiver, "Sub"); // attach the input channel to get messages from unwrapper

            // Receiver for multiply two numbers.
            myMultiplyReceiver = aMessageReceiverFactory.CreateDuplexTypedMessageReceiver<CalculatorOutputData, CalculatorInputData>();
            myMultiplyReceiver.MessageReceived += MulCmd; // attach method handling the request
            aConnectionProvider.Attach(myMultiplyReceiver, "Mul"); // attach the input channel to get messages from unwrapper

            // Receiver for divide two numbers.
            myDivideReceiver = aMessageReceiverFactory.CreateDuplexTypedMessageReceiver<CalculatorOutputData, CalculatorInputData>();
            myDivideReceiver.MessageReceived += DivCmd; // attach method handling the request
            aConnectionProvider.Attach(myDivideReceiver, "Div"); // attach the input channel to get messages from unwrapper
        }


        public void StartCalculatorService()
        {
            // We will use Tcp for the communication.
            IMessagingSystemFactory aServiceMessagingSystem = new TcpMessagingSystemFactory();

            // Create input channel for the calculator receiving messages via Tcp.
            IDuplexInputChannel aGlobalInputChannel = aServiceMessagingSystem.CreateDuplexInputChannel("127.0.0.1:8091");

            // Attach the input channel to the unwrapper and start to listening.
            myDuplexChannelUnwrapper.AttachDuplexInputChannel(aGlobalInputChannel);
        }

        // It is called when a request to sum two numbers was received.
        private void SumCmd(object sender, TypedRequestReceivedEventArgs<CalculatorInputData> e)
        {
            // Get input data.
            CalculatorInputData anInputData = e.RequestMessage;

            // Calculate output result.
            CalculatorOutputData aReturn = new CalculatorOutputData();
            aReturn.Result = anInputData.Number1 + anInputData.Number2;

            Console.WriteLine("{0} + {1} = {2}", anInputData.Number1, anInputData.Number2, aReturn.Result);

            // Response result to the client.
            mySumReceiver.SendResponseMessage(e.ResponseReceiverId, aReturn);
        }

        // It is called when a request to subtract two numbers was received.
        private void SubCmd(object sender, TypedRequestReceivedEventArgs<CalculatorInputData> e)
        {
            // Get input data.
            CalculatorInputData anInputData = e.RequestMessage;

            // Calculate output result.
            CalculatorOutputData aReturn = new CalculatorOutputData();
            aReturn.Result = anInputData.Number1 - anInputData.Number2;

            Console.WriteLine("{0} - {1} = {2}", anInputData.Number1, anInputData.Number2, aReturn.Result);

            // Response result to the client.
            mySubtractReceiver.SendResponseMessage(e.ResponseReceiverId, aReturn);
        }
        

        // It is called when a request to multiply two numbers was received.
        private void MulCmd(object sender, TypedRequestReceivedEventArgs<CalculatorInputData> e)
        {
            // Get input data.
            CalculatorInputData anInputData = e.RequestMessage;

            // Calculate output result.
            CalculatorOutputData aReturn = new CalculatorOutputData();
            aReturn.Result = anInputData.Number1 * anInputData.Number2;

            Console.WriteLine("{0} x {1} = {2}", anInputData.Number1, anInputData.Number2, aReturn.Result);

            // Response result to the client.
            myMultiplyReceiver.SendResponseMessage(e.ResponseReceiverId, aReturn);
        }

        // It is called when a request to divide two numbers was received.
        private void DivCmd(object sender, TypedRequestReceivedEventArgs<CalculatorInputData> e)
        {
            // Get input data.
            CalculatorInputData anInputData = e.RequestMessage;

            // Calculate output result.
            CalculatorOutputData aReturn = new CalculatorOutputData();
            aReturn.Result = anInputData.Number1 / anInputData.Number2;

            Console.WriteLine("{0} / {1} = {2}", anInputData.Number1, anInputData.Number2, aReturn.Result);

            // Response result to the client.
            myDivideReceiver.SendResponseMessage(e.ResponseReceiverId, aReturn);
        }
        


        // Block is the helper to create the infrastructure with less code.
        private IDuplexChannelUnwrapper myDuplexChannelUnwrapper;

        private IDuplexTypedMessageReceiver<CalculatorOutputData, CalculatorInputData> mySumReceiver;
        private IDuplexTypedMessageReceiver<CalculatorOutputData, CalculatorInputData> mySubtractReceiver;
        private IDuplexTypedMessageReceiver<CalculatorOutputData, CalculatorInputData> myMultiplyReceiver;
        private IDuplexTypedMessageReceiver<CalculatorOutputData, CalculatorInputData> myDivideReceiver;
    }
}

Client Application
The client application implements a simple UI taking numbers and invoking requests for the calculation.


To send multiple message types to the Calculator service via one output channel (because the service receives messages via one input channel (address)), the client must use the Channel Wrapper component. The Channel Wrapper component wrapps outgoing request messages so that they can be unwrapped by the service when they are received.




The code implementing the client behavior is very simple too.

using System;
using System.Windows.Forms;
using Eneter.Messaging.DataProcessing.Serializing;
using Eneter.Messaging.EndPoints.TypedMessages;
using Eneter.Messaging.Infrastructure.ConnectionProvider;
using Eneter.Messaging.MessagingSystems.MessagingSystemBase;
using Eneter.Messaging.MessagingSystems.TcpMessagingSystem;
using Eneter.Messaging.MessagingSystems.ThreadMessagingSystem;
using Eneter.Messaging.Nodes.ChannelWrapper;

namespace CalculatorClient2
{
    // Input data for calculator requests
    public class CalculatorInputData
    {
        public double Number1 { get; set; }
        public double Number2 { get; set; }
    }

    // Output result from the calculator
    public class CalculatorOutputData
    {
        public double Result { get; set; }
    }

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            // We want that sending of requests will be not blocking. Therefore we will choose
            // ThreadMessaging with the queue.
            IMessagingSystemFactory anInternalMessaging = new ThreadMessagingSystemFactory();

            // We want to use Xml for serialization/deserialization.
            // Note: Alternative you can use: BinarySerializer
            ISerializer aSerializer = new XmlStringSerializer();

            // The service receives messages via one channel (i.e. it listens on one address).
            // The incoming messages are unwrapped on the server side.
            // Therefore the client must use wrapper to send messages via one channel.
            IChannelWrapperFactory aChannelWrapperFactory = new ChannelWrapperFactory(aSerializer);
            myDuplexChannelWrapper = aChannelWrapperFactory.CreateDuplexChannelWrapper();


            // To connect message senders and the wrapper with duplex channels we can use the following helper class.
            IConnectionProviderFactory aConnectionProviderFactory = new ConnectionProviderFactory();
            IConnectionProvider aConnectionProvider = aConnectionProviderFactory.CreateConnectionProvider(anInternalMessaging);

            
            // Factory to create message senders.
            // Sent messages will be serialized in Xml.
            IDuplexTypedMessagesFactory aCommandsFactory = new DuplexTypedMessagesFactory(aSerializer);

            // Sender to sum two numbers.
            mySumSender = aCommandsFactory.CreateDuplexTypedMessageSender<CalculatorOutputData, CalculatorInputData>();
            mySumSender.ResponseReceived += OnResultResponse;
            aConnectionProvider.Connect(myDuplexChannelWrapper, mySumSender, "Sum");

            // Sender to subtract two numbers.
            mySubSender = aCommandsFactory.CreateDuplexTypedMessageSender<CalculatorOutputData, CalculatorInputData>();
            mySubSender.ResponseReceived += OnResultResponse;
            aConnectionProvider.Connect(myDuplexChannelWrapper, mySubSender, "Sub");

            // Sender to multiply two numbers.
            myMulSender = aCommandsFactory.CreateDuplexTypedMessageSender<CalculatorOutputData, CalculatorInputData>();
            myMulSender.ResponseReceived += OnResultResponse;
            aConnectionProvider.Connect(myDuplexChannelWrapper, myMulSender, "Mul");

            // Sender to divide two numbers.
            myDivSender = aCommandsFactory.CreateDuplexTypedMessageSender<CalculatorOutputData, CalculatorInputData>();
            myDivSender.ResponseReceived += OnResultResponse;
            aConnectionProvider.Connect(myDuplexChannelWrapper, myDivSender, "Div");

            // We use Tcp for the communication.
            IMessagingSystemFactory aTcpMessagingSystem = new TcpMessagingSystemFactory();

            // Create output channel to send requests to the service.
            IDuplexOutputChannel anOutputChannel = aTcpMessagingSystem.CreateDuplexOutputChannel("127.0.0.1:8091");

            // Attach the output channel to the wrapper - so that we are able to send messages.
            // Note: The service has the coresponding unwrapper.
            myDuplexChannelWrapper.AttachDuplexOutputChannel(anOutputChannel);
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            // Stop listening by detaching the input channel.
            myDuplexChannelWrapper.DetachDuplexOutputChannel();
        }


        private void OnResultResponse(object sender, TypedResponseReceivedEventArgs<CalculatorOutputData> e)
        {
            // If everything is ok then display the result.
            if (e.ReceivingError == null)
            {
                // The response does not come in main UI thread.
                // Therefore we must transfer it to the main UI thread.
                InvokeInUIThread(() => ResultLabel.Text = e.ResponseMessage.Result.ToString() );
            }
        }

        private void CalculateButton_Click(object sender, EventArgs e)
        {
            // Prepare input data for the calculator.
            CalculatorInputData anInputForCalculator = new CalculatorInputData();
            anInputForCalculator.Number1 = double.Parse(Number1TextBox.Text);
            anInputForCalculator.Number2 = double.Parse(Number2TextBox.Text);

            // Invoke request to sum.
            mySumSender.SendRequestMessage(anInputForCalculator);
        }
         
        private void SubtractButton_Click(object sender, EventArgs e)
        {
            // Prepare input data for the calculator.
            CalculatorInputData anInputForCalculator = new CalculatorInputData();
            anInputForCalculator.Number1 = double.Parse(Number1TextBox.Text);
            anInputForCalculator.Number2 = double.Parse(Number2TextBox.Text);

            // Invoke request to substract.
            mySubSender.SendRequestMessage(anInputForCalculator);
        }

        private void MultiplyButton_Click(object sender, EventArgs e)
        {
            // Prepare input data for the calculator.
            CalculatorInputData anInputForCalculator = new CalculatorInputData();
            anInputForCalculator.Number1 = double.Parse(Number1TextBox.Text);
            anInputForCalculator.Number2 = double.Parse(Number2TextBox.Text);

            // Invoke request to multiply.
            myMulSender.SendRequestMessage(anInputForCalculator);
        }

        private void DivideButton_Click(object sender, EventArgs e)
        {
            // Prepare input data for the calculator.
            CalculatorInputData anInputForCalculator = new CalculatorInputData();
            anInputForCalculator.Number1 = double.Parse(Number1TextBox.Text);
            anInputForCalculator.Number2 = double.Parse(Number2TextBox.Text);

            // Invoke request to divide.
            myDivSender.SendRequestMessage(anInputForCalculator);
        }

        // Helper method to invoke UI always in the correct thread.
        private void InvokeInUIThread(Action action)
        {
            if (InvokeRequired)
            {
                Invoke(action);
            }
            else
            {
                action.Invoke();
            }
        }

        // Wraps requests into one output channel.
        // The service side listens to one address and uses unwrapper to unwrap
        // messages and send them to correct receivers.
        private IDuplexChannelWrapper myDuplexChannelWrapper;

        // Message senders.
        private IDuplexTypedMessageSender<CalculatorOutputData, CalculatorInputData> mySumSender;
        private IDuplexTypedMessageSender<CalculatorOutputData, CalculatorInputData> mySubSender;
        private IDuplexTypedMessageSender<CalculatorOutputData, CalculatorInputData> myMulSender;
        private IDuplexTypedMessageSender<CalculatorOutputData, CalculatorInputData> myDivSender;
    }
}

I hope you found this example useful. If you would like to read more technical details or you would need the online help you can use
http://www.eneter.net/OnlineHelp/EneterMessagingFramework/Index.html.

If you have any questions, do not hesitate to ask me.

No comments:

Post a Comment