Building a simple chat application with C#, Microsoft’s Velocity, WebORB for .NET, and Flex Builder 3

Whether its an instant messaging client running on your desktop or a customized one on an embedded website perhaps built in Flex/Flash, Silverlight, or even JavaScript/HTML you see chat apps everywhere now. Today we’ll look into how to quickly build a chat application using the following technologies:
1. The Midnight Coders WebORB for .NET
2. C# (.NET 3.5 Framework)
3. Microsoft’s Project Codenamed: Velocity
4. Adobe’s Flex Builder 3

You might want to go through my tutorial on Using WebORB for .NET with Flex and C# before attempting to get this project up and running. The tutorial will lay the groundwork for some of the things I’m doing here.

Ok, let me start by saying this was a very quick and dirty hack I did in some free time. The code is not efficient nor is it elegant. I simply wanted to prove to myself more than anything that I could take a bunch of existing technologies and weld them into a simple chat application in a very short amount of time. Obviously, to scale this out one would need to put in some serious work, but the basics are here and that is all I’m interested in showing right now.

It should also be noted using Microsoft’s Velocity in this manner is probably not how they expected someone to use it, but hey, it works, and it works surprisingly well for this but obviously loses it’s ability to cluster with how I’m using “regions” and “tags” – at least that is what I’m guessing from the documentation.

Also let me say that you could do the same thing with WebORB for .NET (or BlazeDS) using their RMTP support, but, not everyone will want to tweak IIS to support RMTP, hence this way presented here shows a possible solution to RMTP.

The first thing I did was download Velocity (currently a CTP1 release) and install it. When I got to Velocity’s configuration screen I used these settings:

I then started Velocity from it’s Administration Tool Command Prompt:

With Velocity off and running I then decided to code the .NET part of this. I created a new C# Class Library called “VelocityConnector” in Visual Studio 2008 and added two classes to the project called “MessageManager” and “Message” with this code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Caching;

namespace VelocityConnector
{
    public class MessageManager
    {
        private static String regionName = "LobbyChat";
        private CacheFactory cacheFactory;
        private Cache defaultCache;
        Tag[] allTags;

        public MessageManager()
        {
            cacheFactory = new CacheFactory();

            if ((defaultCache = cacheFactory.GetCache("default")) != null)
            {
                allTags = new System.Data.Caching.Tag[1] { new Tag("Messages") };
                try
                {
                    defaultCache.CreateRegion(regionName, false);
                }
                catch (CacheException)
                {
                    // The region already exists
                }
            }
        }

        public void InsertMessage(Message msg)
        {
            msg.MessageTimeStamp = DateTime.Now.Ticks;
            defaultCache.Add(regionName, DateTime.Now.Ticks.ToString(), (Object)msg, allTags);
        }

        public List<Message> GetMessages()
        {
            List<KeyValuePair<string, object>> getByTagReturnKeyValuePair;
            List<Message> msgList = new List<Message>();

            if ((getByTagReturnKeyValuePair = (List<KeyValuePair<String, Object>>)defaultCache.GetByTag(regionName, allTags[0])) != null)
            {
                foreach (KeyValuePair<string, object> kvp in getByTagReturnKeyValuePair)
                {
                    Message msg = (Message)kvp.Value;
                    msgList.Add(msg);
                }
            }

            return msgList;
        }
    }
}

…and the other class

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace VelocityConnector
{
    [Serializable]
    public class Message
    {
        public String MessageBody { get; set; }
        public String MessageFrom { get; set; }
        public long MessageTimeStamp { get; set; }
    }
}

I compiled that in release mode once I tested that it was working with a console project test harness that looked liked this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using VelocityConnector;

namespace VelocityConnectorTestConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            MessageManager mm = new MessageManager();
            Message msg = new Message();

            msg.MessageFrom = "TestApp";
            msg.MessageBody = "Hello World";

            mm.InsertMessage(msg);
            Console.WriteLine("Message Inserted" + Environment.NewLine);
            List<Message> list = mm.GetMessages();

            Console.WriteLine("Getting Messages..." + Environment.NewLine);
            foreach (Message m in list)
                Console.WriteLine(m.MessageFrom + ": " + m.MessageBody);

            Console.WriteLine("Completed!");
            Console.ReadLine();
        }
    }
}

With the resulting release assemblies I then moved those into WebORB’s “bin” folder in IIS. I then made sure the methods in those DLLs worked by using WebORB’s Management Console. With that working I moved onto the Flex Builder portion.

Creating a new Flex Builder 3 project called “SimpleChat” I used the following settings:

Then I opened the project properties from the menu at the top of Flex Builder and inserted this into the Flex Compiler’s additional arguments:

-services c:\Inetpub\wwwroot\weborb30\web-inf\flex\services-config.xml

- If your paths are different then adjust these to your settings. Also note that if your path contains spaces you need to wrap the path in quotes like this:

-services "c:\Inetpub\wwwroot\A SPACE IN THE PATH\weborb30\web-inf\flex\services-config.xml"

Then I used the following MXML (and using the excellent random number class from gskinner.com):

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="init();" layout="absolute">

	<mx:Script>
		<![CDATA[
            import mx.controls.Alert;
            import mx.rpc.events.FaultEvent;
            import mx.rpc.events.ResultEvent;
            import mx.rpc.remoting.RemoteObject;
            import flash.utils.Timer;
            import flash.events.TimerEvent;

            [Bindable]
            private var messageService:RemoteObject;

            private var timer:Timer;
            private var timerIsRunning:Boolean = false;	

            private function init():void
            {
                chatName_txt.text = "Anonymous" + Rnd.integer(0, 999);

                messageService = new RemoteObject("GenericDestination");
                messageService.source = "VelocityConnector.MessageManager";

                messageService.GetMessages.addEventListener(ResultEvent.RESULT, incomingChat);
                messageService.GetMessages.addEventListener(FaultEvent.FAULT, incomingError);
                messageService.InsertMessage.addEventListener(FaultEvent.FAULT, incomingError);
            } 

            private function callChatUpdate(event:TimerEvent):void
            {
            	messageService.GetMessages();
            }

            private function incomingChat(event:ResultEvent):void
            {
            	var msgArray:Array = new Array();
            	var receivedMessageArray:Array = event.result as Array;            	

            	output_txt.htmlText = "";

				for each (var objRMA:Object in receivedMessageArray)
				{
					msgArray.push(new Message(objRMA.MessageFrom, objRMA.MessageBody, objRMA.MessageTimeStamp));
				}

				msgArray.sortOn("messageTimeStamp", Array.NUMERIC);

				for each (var objMA:Object in msgArray)
				{
					output_txt.htmlText += "<b>" + objMA.messageFrom + "</b>: " + objMA.messageBody + "<br />";
				}
            }

            private function incomingError( fault:FaultEvent ):void
            {
                Alert.show(fault.fault.faultString);
            }                         	

			private function onSend():void
			{
                var msg:Object = new Object();
                msg.MessageFrom = chatName_txt.text;
                msg.MessageBody = send_txt.text;

                messageService.InsertMessage(msg); 

                send_txt.text = "";

               // Has the timer been started?
                if(!(timerIsRunning))
                {
               		timer = new Timer(3000, 0);
               		timer.addEventListener("timer", callChatUpdate);
               		timer.start();

               		timerIsRunning = true;
                }
			}

		]]>
	</mx:Script>

	<mx:Panel width="421" height="360" layout="absolute" horizontalCenter="0" verticalCenter="0" fontFamily="Verdana" fontSize="12" id="simpleChat_pnl" title="Simple Chat" color="#000000" backgroundColor="#EEEEEE">
		<mx:TextInput x="13" y="286" width="308" maxChars="100" id="send_txt"/>
		<mx:TextInput x="212" y="8" width="176" maxChars="12" id="chatName_txt"/>
		<mx:Button x="329" y="286" label="Send" id="send_btn" click="onSend();"/>
		<mx:TextArea x="13" y="40" valueCommit="output_txt.verticalScrollPosition=output_txt.maxVerticalScrollPosition" width="375" height="238" id="output_txt" verticalScrollPolicy="on"/>
		<mx:Label x="130" y="10" text="Chat Name:"/>
	</mx:Panel>

</mx:Application>

Here is the code for the Message.class:

package
{
	public class Message
	{
	    public var messageBody:String;
	    public var messageFrom:String;
	    public var messageTimeStamp:Number;

	    public function Message(from:String, body:String, timeStamp:Number)
	    {
	        this.messageFrom = from;
	        this.messageBody = body;
	        this.messageTimeStamp = timeStamp;
	    }
	}
}

Now before I ran this I had to go into WebORB’s web.config file in IIS and modify it. I added this to the top of it in order to let my assembly talk to Velocity:

	<configSections>
		<section name="dcacheClient" type="System.Configuration.IgnoreSectionHandler" allowLocation="true" allowDefinition="Everywhere"/>
	</configSections>
	<dcacheClient deployment="simple" localCache="false">
    <hosts>
      <!--List of hosts -->
      <host name="localhost"
            cachePort="22233"
            cacheHostName="DistributedCacheService" />
    </hosts>
  </dcacheClient>

Here is something you’ll probably want to do:

- Add code to pass a the latest timestamp received from the Flex client back to the server so the server only gives the messages that have not been received since the last communication. This would save a lot of bandwidth rather than what it currently does which is sending everything back every three seconds.

- Add support for multiple chat rooms other than just the default main lobby room.

So this code can definitely be improved in many ways, however, here is a chat application done in a very short time using a mixture of technologies and it works pretty well.

Update: Here is the source code for the Flex part as well as the Visual Studio part.

3 Responses to “Building a simple chat application with C#, Microsoft’s Velocity, WebORB for .NET, and Flex Builder 3”

  1. Hi!! I’m currently using Velocity for a Finance App and I really like it, also my team is developing a chat app, so I’m happy to see an idea like this. BUT!! to do this the client needs to ask the cache for new mesages. I need to this async (duplex). Do you think can be possible with your Velocity approach?

  2. Chad Lung says:

    @Fernando,

    I think you could have some success then with hooking into a Comet capable server (Glassfish, Jetty, etc.). I have hooked up Flex to Jetty using their continuations (Comet support) a while back and it worked good. The client didn’t have to poll the server and still got the messages as they became available.

    Another option is to use the RMTP support in WebORB for .NET or Messaging support in WebORB for Java.

    Interestingly enough if you look at Silverlight’s support for duplexing you can see under the covers it’s really just polling but doing it in a smart way.

    Chad

  3. Darshan says:

    very nice…

Leave a Reply