Search This Blog

Sunday, July 11, 2010

Silverlight: Interprocess Communicatin from Silverlight Application

Summary: The article shows how to implement the communication between Silverlight application and some Non-Silverlight application with using Eneter.Messaging.Framework. The example shows Silverlight client invoking requests to the console application. The request can be paused, resumed or canceled.



There are not many possibilities if you need to implement the communication between Silverlight application and some Non-Silverlight application (e.g. standard desktop application).
Basically, you can choose sockets to implement your own communication based on Tcp or you can choose WCF to communicate via remote calls.

Another option you can consider is to use Eneter.Messaging.Framework. The framework enables you to implement the message based communication scenarios. E.g. Silverlight client can invoke a request and manipulate it with 'pause', 'resume' and 'cancel'. It means the request being processed in the console application can be paused, resumed or canceled from Silverlight client.

The example bellow shows how to implement the Silverlight client invoking a request to the Non-Silverlight application that can be 'paused', 'resumed' or 'canceled'.


1. Create server - standard console application

Create standard console application in Visual Studio and add Eneter.Messaging.Framework.dll to references.
(Full, not limited and for non-commercial usage free version of the framework can be downloaded from www.eneter.net.)

Implement the main method the following way:

using Eneter.Messaging.MessagingSystems.MessagingSystemBase;
using Eneter.Messaging.MessagingSystems.TcpMessagingSystem;

namespace TcpCommandServer
{
    class Program
    {
        static void Main(string[] args)
        {
            // Start policy server to allow the communication with the Silverlight client.
            TcpPolicyServer aPolicyServer = new TcpPolicyServer(System.Net.IPAddress.Loopback);
            aPolicyServer.StartPolicyServer();

            // Create the communication based on Tcp.
            // Note: Silverlight clients can communicate on the following ports: 4502 - 4532.
            IMessagingSystemFactory aMessagingFactory = new TcpMessagingSystemFactory();
            IDuplexInputChannel aDuplexInputChannel = aMessagingFactory.CreateDuplexInputChannel("127.0.0.1:4530");

            // Start listening for requests.
            CommandReceiver aCommandReceiver = new CommandReceiver();
            aCommandReceiver.StartCommandReceiver(aDuplexInputChannel);
        }
    }
}


Note: The policy server listens to port 943 and returns Xml telling to Silveright that the communication is allowed. The framework returns a basic Xml but you can also set your own if it is needed.

Implement the CommandReceiver class responsible for processing requests.

using System;
using System.Threading;
using Eneter.Messaging.DataProcessing.Sequencing;
using Eneter.Messaging.DataProcessing.Serializing;
using Eneter.Messaging.EndPoints.Commands;
using Eneter.Messaging.MessagingSystems.MessagingSystemBase;

namespace TcpCommandServer
{
    public class TSomeRequestMessage
    {
        public string SomeParam1 { get; set; }
        public int SomeParam2 { get; set; }
    }

    public class TSomeResponseMessage
    {
        public int Progress { get; set; }
    }


    internal class CommandReceiver
    {
        public CommandReceiver()
        {
            // Silverlight does not support binary serialization.
            // In Silverlight, the framework uses serialization to Xml by default.
            // Therefore we must set here Xml serialization too.
            ISerializer aSerializer = new XmlStringSerializer();

            // Create command that puts incoming requests to the queue and
            // processes them in one thread.
            // CommandProcessingMethod - is the user provided method called to process the request.
            ICommandsFactory aCommandsFactory = new CommandsFactory(aSerializer);
            myCommand = aCommandsFactory.CreateCommand<TSomeResponseMessage, TSomeRequestMessage>(CommandProcessingMethod, EProcessingStrategy.SingleThread);
        }

        // Starts listening to commands.
        public void StartCommandReceiver(IDuplexInputChannel duplexInputChannel)
        {
            // Attach the duplex input channel to the command - the command starts the listening.
            myCommand.AttachDuplexInputChannel(duplexInputChannel);
        }

        // Stops listening.
        public void StopCommandReceiver()
        {
            // Detach the input channel from the command.
            // Note: If the input channel is not detached the thread processing command requests would leak
            //       and prevent the closing the application.
            myCommand.DetachDuplexInputChannel();
        }

        // This method is called when the command is executed.
        private void CommandProcessingMethod(ICommandContext<TSomeResponseMessage, TSomeRequestMessage> commandContext)
        {
            try
            {
                // Read the input data.
                DataFragment<TSomeRequestMessage> anInputDataFragment = commandContext.DequeueInputData();
                TSomeRequestMessage anInputData = anInputDataFragment.Data;

                Console.WriteLine("Request received: " + anInputData.SomeParam1);

                for (int i = 0; i < 100; ++i)
                {
                    // Simulate some work.
                    Thread.Sleep(100);

                    // Wait if pause is requested.
                    if (commandContext.CurrentRequest == ECommandRequest.Pause)
                    {
                        // Notify the command proxy that the command was paused.
                        commandContext.ResponsePause();

                        // Wait until resumed or canceled.
                        commandContext.WaitIfPause();
                    }

                    // If the cancel is requested then stop the command.
                    if (commandContext.CurrentRequest == ECommandRequest.Cancel)
                    {
                        // Notify the command proxy that the command was canceled.
                        commandContext.ResponseCancel();
                        return;
                    }

                    // Create response message with the progress.
                    TSomeResponseMessage aResponseMessage = new TSomeResponseMessage();
                    aResponseMessage.Progress = i + 1;

                    // Notify the progress.
                    ECommandState aState = (i == 99) ? ECommandState.Completed : ECommandState.InProgress;
                    commandContext.Response(aState, aResponseMessage);
                }
            }
            catch (Exception err)
            {
                commandContext.ResponseFailure(err.Message);
            }
        }

        private ICommand<TSomeResponseMessage, TSomeRequestMessage> myCommand;
    }
}


2. Create client - Silverlight application

Create Silverlight application in Visual Studio and add Eneter.Messaging.Framework.Silverlight.dll to references.
(Full, not limited and for non-commercial usage free version of the framework can be downloaded from www.eneter.net.)

Implement the Silverlight client sending requests to the console application (server).

using System.Windows;
using System.Windows.Controls;
using Eneter.Messaging.EndPoints.Commands;
using Eneter.Messaging.MessagingSystems.MessagingSystemBase;
using Eneter.Messaging.MessagingSystems.TcpMessagingSystem;

namespace SilverlightTcpCommandClient
{
    public class TSomeRequestMessage
    {
        public string SomeParam1 { get; set; }
        public int SomeParam2 { get; set; }
    }

    public class TSomeResponseMessage
    {
        public int Progress { get; set; }
    }

    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();

            // Create messaging system to send messages to the command.
            // Note: It can be any messaging system but in this case is the fastest one is Synchronous.
            IMessagingSystemFactory aMessaging = new TcpMessagingSystemFactory();
            IDuplexOutputChannel anOutputChannel = aMessaging.CreateDuplexOutputChannel("127.0.0.1:4530");

            // Create command proxy to send requests and receive responses.
            // The command responses progress 
            ICommandsFactory aCommandsFactory = new CommandsFactory();
            myCommandProxy = aCommandsFactory.CreateCommandProxy<TSomeResponseMessage, TSomeRequestMessage>();
            myCommandProxy.CommandResponseReceived += OnCommandResponseReceived;

            // Attach the output channel to the command proxy and enable sending of requests.
            myCommandProxy.AttachDuplexOutputChannel(anOutputChannel);
        }

        private void ExecuteBtn_Click(object sender, RoutedEventArgs e)
        {
            // Create the message.
            TSomeRequestMessage aRequestMessage = new TSomeRequestMessage();
            aRequestMessage.SomeParam1 = "Some text.";
            aRequestMessage.SomeParam2 = 100;

            // Execute the command.
            myCommandProxy.Execute("MyCommandId", aRequestMessage);
        }

        private void PauseBtn_Click(object sender, RoutedEventArgs e)
        {
            myCommandProxy.Pause("MyCommandId");
        }

        private void ResumeBtn_Click(object sender, RoutedEventArgs e)
        {
            myCommandProxy.Resume("MyCommandId");
        }

        private void CancelBtn_Click(object sender, RoutedEventArgs e)
        {
            myCommandProxy.Cancel("MyCommandId");
        }

        private void OnCommandResponseReceived(object sender, CommandResponseReceivedEventArgs<TSomeResponseMessage> e)
        {
            // Display the progress from incomming response message.
            if (e.CommandState == ECommandState.InProgress)
            {
                myProgressBar.Value = e.ReturnData.Progress;
            }
            else if (e.CommandState == ECommandState.Canceled ||
                     e.CommandState == ECommandState.Completed)
            {
                myProgressBar.Value = 0;
            }
        }

        private ICommandProxy<TSomeResponseMessage, TSomeRequestMessage> myCommandProxy;
    }
}


For sake of completeness here is the Xaml file for the Silverlight client defining buttons and progress bar.

<UserControl x:Class="SilverlightTcpCommandClient.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White" Height="181" Width="273">
        <Button Content="Execute Task" Height="23" HorizontalAlignment="Left" Margin="28,35,0,0" Name="ExecuteBtn" VerticalAlignment="Top" Width="89" Click="ExecuteBtn_Click" />
        <Button Content="Pause" Height="23" HorizontalAlignment="Left" Margin="179,35,0,0" Name="PauseBtn" VerticalAlignment="Top" Width="75" Click="PauseBtn_Click" />
        <Button Content="Resume" Height="23" HorizontalAlignment="Left" Margin="179,64,0,0" Name="ResumeBtn" VerticalAlignment="Top" Width="75" Click="ResumeBtn_Click" />
        <Button Content="Cancel" Height="23" HorizontalAlignment="Left" Margin="179,93,0,0" Name="CancelBtn" VerticalAlignment="Top" Width="75" Click="CancelBtn_Click" />
        <ProgressBar Height="22" HorizontalAlignment="Left" Margin="28,138,0,0" Name="myProgressBar" VerticalAlignment="Top" Width="226" />
    </Grid>
</UserControl>

As you can see the implementation requires just little coding. I hope you like my posts about Eneter.Messaging.Framework. I am going to write more articles. If you are interested in more technical information you can try here:
http://www.eneter.net/OnlineHelp/EneterMessagingFramework/Index.html

No comments:

Post a Comment