Flex integration with Microsoft Message Queuing (MSMQ) using WebORB – Part II – Server Side
.NET, Flex June 28th, 2008Introduction
For the Server Side I have made a new example in Flex based on the previous example in Part I article. Let me sketch the problem in our project again. On the client side a user can edit things and save them. If two clients were to edit the same thing, then the user that saves last will win. The other user will have saved his work, but will loose it because the other user overwrites it.
Ok, assume the Client loads in a tree. Leafs can be Apples, Potatoes or Bananas. If the Client A clicks on an Apple Leaf, then a message is sent saying that an Apple with Id X is locked by Client A. Other Clients get this message and lock the Apple leaf. If Client A navigates to another leaf, then the previous leaf is released (so a message is sent saying this Apple with Id X is no more locked) and a new leaf is locked (again a message).
Requirements: Please check Part I.
Problems:
- A Client connects and leafs are allready locked. This means that the server needs to have a registry of locked Apples, locked Potatoes and locked Bananas. If a Client connects the server notifies the Client of already locked things.
- Client A and Client B click simultaneously on the same Apple. Two messages are sent locking the same Apple. In this case the server will have to let one message through and cancel to other message. If the message from Client B has been cancelled, the server will have to notify this Client B that locking the Apple did not work.
- When stress testing such cases, lot's of things can go wrong due to asynchronous processes. When a Producer sends a message locking an Apple with IdX, then it has to wait for his Consumer getting the Message back saying the Apple with IdX is locked. But image clicking very fast... This means that by the time the Consumer gets the affirmation that the Apple with IdX has successfully been locked, the user may already have clicked another Apple with IdY (and a message locking this IdY has also been sent...). I will not go to deep into these problems for now, maybe I'll talk about it in a part III or IV...
For this part II I have created a second flex example. This example is a bit more complex than the one in part I.
The server side will be a bit more work to explain. This is the source code. You'll see there's a Site, a DotNet and a Binaries folder. First create a site in IIS and link it to the Site folder (if you use johleromsmq as the name of your site, then you won't have to change a lot, but you're free to name it as you want). Then in the DotNet folder open the solution with Visual Studio 2008. Normally it won't find my site, so you'll have to remove johleromsmq site and then add your site to the solution. Then check the references. The Messaging Class library should have a reference to the System.Messaging and weborb. The site should have a reference to the Messaging Class library and weborb.dll.
I made the target Framework 2.0 but 3.5 will also work.
If IIS is properly set then you should be able to build the solution. You also should be able to surf to weborbconsole.html and to diagnostics.aspx (don't worry about the WebORB Permissons Summary being all red --> If you want WebORB to be able to write logs, then you should give the Network Service write permissions on your log folder).
Flow
Ok, let's go.
First take a look at the constants
-
private const ENDPOINT:String = "http://localhost/johleromsmq/weborb.aspx";
-
private const URI:String = "rtmp://localhost:2037/MessagingService";
-
private const REMOTEDESTINATION:String = "GenericDestination";
-
private const MESSAGINGDESTINATION:String = "JohleroMsmqChannel"
-
private const NETCONNECTIONSUCCESS:String = "NetConnection.Connect.Success";
-
private const MANAGEMENTSERVICE:String = "Weborb.Management.ManagementService";
The ENDPOINT should point to your site. You will not find weborb.aspx as it is configured as a http handler in the web.config. Also be aware of the RTMPTHttpHandlers! Don't forget to add them to your own project!
-
<?xml version="1.0"?>
-
<configuration>
-
<system.web>
-
<customErrors mode="Off" />
-
<compilation debug="true">
-
</compilation>
-
<authentication mode="Windows"/>
-
<httpHandlers>
-
<add verb="*" path="weborb.aspx" type="Weborb.ORBHttpHandler"/>
-
<add verb="*" path="codegen.aspx" type="Weborb.Management.CodeGen.CodegeneratorHttpHandler"/>
-
<add verb="*" path="/open/*" type="Weborb.Messaging.Net.RTMPT.RTMPTHttpHandler"/>
-
<add verb="*" path="/send/*" type="Weborb.Messaging.Net.RTMPT.RTMPTHttpHandler"/>
-
<add verb="*" path="/idle/*" type="Weborb.Messaging.Net.RTMPT.RTMPTHttpHandler"/>
-
<add verb="*" path="/close/*" type="Weborb.Messaging.Net.RTMPT.RTMPTHttpHandler"/>
-
<add verb="*" path="/open/*/*" type="Weborb.Messaging.Net.RTMPT.RTMPTHttpHandler"/>
-
<add verb="*" path="/send/*/*" type="Weborb.Messaging.Net.RTMPT.RTMPTHttpHandler"/>
-
<add verb="*" path="/idle/*/*" type="Weborb.Messaging.Net.RTMPT.RTMPTHttpHandler"/>
-
<add verb="*" path="/close/*/*" type="Weborb.Messaging.Net.RTMPT.RTMPTHttpHandler"/>
-
</httpHandlers>
-
<caching>
-
<outputCache enableOutputCache="true"/>
-
</caching>
-
</system.web>
-
</configuration>
The URI should stay the same, as it is used by the Channel to connect to the MessagingService started on port 2037. The /MessagingService points to a folder in the Applications folder under the root of your site where you can find an app.config. Here a custom ApplicationHandler can be entered to track connections, disconnections, joins, leaves, ...
The REMOTEDESTINATION points to a destination in the WEB-INF/flex/remoting-config.xml file on the root of your site. There you'll find the GenericDestination and it can be used to configurate access to certain services, ... Explaining this will however be for a next post. This destination will be used to by the RemoteObject.
The MESSAGINGDESTINATION points to a destination in the WEB-INF/flex/messaging-config.xml file on the root of your site. There you'll find a destination with id JohleroMsmqChannel. The weborb-rtmp channel set in the channels attribute refers to a channel-definition n the WEB-INF/flex/weborb-services-config.xml file on the root of your site. For the rest if you read the comments in the file, everything should be clear. The
.\private$\weborb-[destinationName] in our case will be translated to weborb-JohleroMsmqChannel-subqueue. The subqueue will be configured later on in the Consumer. The Messaging.Selectors.CustomSelector will be explained later.
Now look at the onCreationComplete() handler in the Flex Code.
-
public function onCreationComplete(event:FlexEvent) : void
-
{
-
this.addLogMessage("onCreationComplete");
-
-
registerClassAlias("Messaging.Messages.LockedAppleMessage", LockedAppleMessage);
-
registerClassAlias("Messaging.Messages.LockedPotatoeMessage", LockedPotatoeMessage);
-
registerClassAlias("Messaging.Messages.LockedBananaMessage", LockedBananaMessage);
-
-
this.createChannelSet();
-
}
-
-
private function createChannelSet() : void
-
{
-
this.addLogMessage("createChannelSet()");
-
-
cs = new ChannelSet();
-
channel = new WeborbMessagingChannel("WebOrbMessagingChannel", URI);
-
channel.addEventListener(MessageEvent.MESSAGE, onChannelMessage);
-
channel.addEventListener(ChannelEvent.CONNECT, onChannelConnect);
-
channel.addEventListener(ChannelEvent.DISCONNECT, onChannelDisconnect);
-
channel.addEventListener(ChannelFaultEvent.FAULT, onChannelFault);
-
cs.addChannel(channel);
-
}
Every function that is called writes a log to a TextArea, so you can follow on the screen what happens.
With the registerClassAlias Client side classes (the ones that will be used for messaging) are mapped to server side classes. After this a Channel Set is created with a channel connecting to the MessagingServer.
Pinging WebORB to ensure MessagingServer is started
Now start the Flex application and click on the Button Pinging WebORB to ensure MessagingServer is started. The following code in Flex is executed:
-
private function pingWeborb() : void
-
{
-
this.addLogMessage("Pinging WebORB");
-
-
var ro:RemoteObject = new RemoteObject(REMOTEDESTINATION);
-
ro.source = MANAGEMENTSERVICE;
-
ro.endpoint = ENDPOINT;
-
ro.ping.addEventListener(ResultEvent.RESULT, onWebOrbPingResult);
-
ro.ping.addEventListener(FaultEvent.FAULT, onWebOrbPingFault);
-
ro.ping();
-
}
On the server side Application_Start(object sender, EventArgs e) in Global.asax is triggered (if not then first kill the w3wp.exe process). You can check this by marking some breakpoints in VS2008 and starting your site in debug mode.
-
void Application_Start(object sender, EventArgs e)
-
{
-
try
-
{
-
-
Weborb.Messaging.RTMPServer server = new Weborb.Messaging.RTMPServer( "default", 2037, 500, config );
-
server.start();
-
Application[ "weborbMessagingServer" ] = server;
-
-
}
-
catch( Exception )
-
{
-
}
-
}
As you can see the RTMPServer is started here. If you started for instance the weborbconsole.html in the weborb30 installation site, then this RTMPServer will already be running on port 2037. In this case weborb30 installation site will catch messages and not your site. It's hard for me to explain this as I am not a IIS expert...
Next the Session_Start(object sender, EventArgs e) in Global.asax is triggered. Arter this the weborb.aspx handler will handle this ping request and the ping() function in the Weborb.Management.ManagementService will be triggered. If this is successfull then the onWebOrbPingResult should be triggered (on flex side), if not the onWebOrbPingFault will be triggered (on flex side).
If there's a problem then start your site in debug mode and check the output. Here's the output for a successfull ping
-
[Thread-4] WEBORB INFO:3/07/2008 20:59:39:Received request for http://localhost/johleromsmq/weborb.aspx from ::1
-
[Thread-4] WEBORB INFO:3/07/2008 20:59:39:Request is in session 4shaj0bj10sjmb55o1rhcrii
-
[Thread-4] WEBORB INSTRUMENTATION:3/07/2008 20:59:39:Request parsing time in ms - 3
-
[Thread-4] WEBORB INFO:3/07/2008 20:59:39
ispatching request processing to V3 Dispatcher -
[Thread-4] WEBORB SECURITY:3/07/2008 20:59:39:access allowed. resource name - 'Weborb.Management.ManagementService#ping'. reason -
-
[Thread-4] WEBORB INFO:3/07/2008 20:59:39:loading type: Weborb.Management.ManagementService
-
[Thread-4] WEBORB INSTRUMENTATION:3/07/2008 20:59:39:clr method invocation time (in ms) - 0 method - ping
-
[Thread-4] WEBORB INFO:3/07/2008 20:59:39:Object Handler handler successfully invoked method ping on the service Weborb.Management.ManagementService
-
[Thread-4] WEBORB INFO:3/07/2008 20:59:39:V3 Dispatcher successfully handled request processing
-
[Thread-4] WEBORB INFO:3/07/2008 20:59:39:Beginning to write response
-
[Thread-4] WEBORB INFO:3/07/2008 20:59:39:got headers 0
-
[Thread-4] WEBORB INFO:3/07/2008 20:59:39:got bodies 1
-
[Thread-4] WEBORB INFO:3/07/2008 20:59:39:AMFMessageWriter.write - message version: 3 header length: 0
-
[Thread-4] WEBORB INSTRUMENTATION:3/07/2008 20:59:39:Response serialization time in ms - 9
-
[Thread-4] WEBORB INSTRUMENTATION:3/07/2008 20:59:39:Total request processing time in ms - 141
Initializing connection to get unique ClientId
Click on the Button Initializing connection to get unique ClientId. Next Flex code is executed:
-
private function initConnection() : void
-
{
-
this.addLogMessage("InitConnection");
-
-
var uri:String = URI;
-
wOrb_nc = new NetConnection();
-
-
wOrb_nc.client = this;
-
wOrb_nc.objectEncoding = ObjectEncoding.AMF3;
-
wOrb_nc.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
-
wOrb_nc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
-
wOrb_nc.connect( uri, "NetConnection");
-
}
This makes a connection with the localhost on port 2037. On the server side the IScopeHandler.connect(IConnection conn, IScope scope, object[] parms) function is triggered in the CustomApplicationAdapter. This CustomApplicationAdapter is configured in the app.config in the /Applications/MessagingService folder under root of the site. The "NetConnection" parameter will appear in the object[] parms paramter in C# function.
-
bool IScopeHandler.connect(IConnection conn, IScope scope, object[] parms)
-
{
-
Log.startLogging(Weborb.Util.Logging.LoggingConstants.DEBUG);
-
Log.log(Weborb.Util.Logging.LoggingConstants.DEBUG, "*** CustomApplicationAdapter:connect(" + conn.ToString() + ", " + scope.ToString() + ")");
-
-
//Get the connections of the scope (MessagingService scope)
-
IEnumerator connections = scope.getConnections();
-
-
//Create an object that will be sent back to client
-
args.SetValue(conn.getClient().getId(), 0);
-
String inName = null;
-
try
-
{
-
inName = (Convert.ToString(parms[0])); //Will throw an error if Consumer of Producer connects
-
//Check if it is the NetConnection connecting (Consumer & Producer will also be connecting)
-
{
-
Log.log(Weborb.Util.Logging.LoggingConstants.DEBUG, "*** CustomApplicationAdapter:connect:NetConnection ClientId " + conn.getClient().getId());
-
-
Log.log(Weborb.Util.Logging.LoggingConstants.DEBUG, "*** CustomApplicationAdapter:connect:NetConnection Invoking setClientID on Flex Client " + conn.getClient().getId());
-
//Let Client now his ClientId
-
((IServiceCapableConnection)conn).invoke("setClientID", args);
-
-
Log.log(Weborb.Util.Logging.LoggingConstants.DEBUG, "*** CustomApplicationAdapter:connect:NetConnection Invoking initLockedPotatoeMessages on Flex Client " + conn.getClient().getId());
-
//Let Client now which Potatoes are already locked
-
((IServiceCapableConnection)conn).invoke("initLockedPotatoeMessages", new Object[] { LockedPotatoeRegistry.instance.GetAllLockedPotatoeMessages() });
-
-
Log.log(Weborb.Util.Logging.LoggingConstants.DEBUG, "*** CustomApplicationAdapter:connect:NetConnection Invoking initLockedBananaMessages on Flex Client " + conn.getClient().getId());
-
//Let Client now which Banana's are already locked
-
((IServiceCapableConnection)conn).invoke("initLockedBananaMessages", new Object[] { LockedBananaRegistry.instance.GetAllLockedBananaMessages() });
-
-
Log.log(Weborb.Util.Logging.LoggingConstants.DEBUG, "*** CustomApplicationAdapter:connect:NetConnection Invoking initLockedAppleMessages on Flex Client " + conn.getClient().getId());
-
//Let Client now which Apples are already locked
-
((IServiceCapableConnection)conn).invoke("initLockedAppleMessages", new Object[] { LockedAppleRegistry.instance.GetAllLockedAppleMessages() });
-
-
Log.log(Weborb.Util.Logging.LoggingConstants.DEBUG, "*** CustomApplicationAdapter:connect:NetConnection Setting client connection uName attribute to " + inName);
-
//Set an attribute on the connection so when disconnecting we can now if it is the NetConnection disconnecting
-
conn.getClient().setAttribute("uName", inName);
-
}
-
}
-
catch (Exception)
-
{
-
//Consumer or Producer trying to connect. Haven't found a way to make difference between the two.
-
Log.log(Weborb.Util.Logging.LoggingConstants.DEBUG, "*** CustomApplicationAdapter:connect:WebORBConsumer or WebORBProducer connected with clientId " + conn.getClient().getId());
-
}
-
-
Log.stopLogging(Weborb.Util.Logging.LoggingConstants.DEBUG);
-
return base.connect(conn, scope, parms);
-
}
After this the IScopeHandler.join(IClient client, IScope scope) is also triggered adding the client to the scope.
Please see the comments in the code to now what is happening. In Flex there are four five functions being triggered as you can see in the output panel:
-
SERVER CALL: setClientID(2)
-
SERVER CALL: initLockedPotatoeMessages(0)
-
SERVER CALL: initLockedBananaMessages(0)
-
SERVER CALL: initLockedAppleMessages(0)
-
netStatusHandler(NetConnection.Connect.Success)
The clientId is 2 (that's the id from the NetConnection) and there are not locked potatoes, banana's or apples. Next there's the return base.connect(conn, scope, parms); which triggers the netStatusHandler with the event.info.code being NetConnection.Connect.Success.
If you would change the port from 2037 to 2038 you would have to get this in the output panel:
-
netStatusHandler(NetConnection.Connect.Failed)
If you kill the w3wp.exe process and try to initialize the netconnection (on port 2037) without pinging WebOrb you'll get the same Failed Connection. This is because in Global.asax the Application_Start will not have been triggered and the MessagingServer not been started.
Connecting the Consumer
In Flex application click on Connect as Consumer to receive the messages. Next Flex code is executed:
-
private function connectAsConsumer() : void
-
{
-
this.addLogMessage("connectAsConsumer()");
-
-
consumer = new WeborbConsumer();
-
consumer.channelSet = cs;
-
consumer.destination = MESSAGINGDESTINATION;
-
consumer.subqueue = this.subQueueu.text;
-
consumer.addEventListener(MessageAckEvent.ACKNOWLEDGE, onConsumerMessageAck);
-
consumer.addEventListener(MessageFaultEvent.FAULT, onConsumerMessageFault);
-
consumer.subscribe(this.clientId);
-
}
The consumer uses the ChannelSet that was created in the onCreationComplete handler. The subqueue will be used to create the name of the queue (see MESSAGINGDESTINATION). The paramter this.clientId will be accessible in the CustomSelector.cs. We will be able to use this in the CustomSelector.cs to now which client will receive a message.
On the server side next function in CustomApplicationAdapter are triggered:
IScopeHandler.connect(IConnection conn, IScope scope, object[] parms){...}
IScopeHandler.join(IClient client, IScope scope){...}
After this the CustomSelector Constructor is triggered creating an instance for the Flex Consumer. Messages send by the Producer to Clients will pass through these CustomSelectors. Next the setClientID(string clientID) is triggered setting the ClientId for this CustomSelector.
In the Output window this should appear:
-
connectAsConsumer()
-
onChannelConnect([ChannelEvent channelId="WebOrbMessagingChannel" reconnecting=false rejected=false type="channelConnect" bubbles=false cancelable=false eventPhase=2])
-
onConsumerMessageAck([MessageAckEvent messageId="EAE06CDF-7937-46C3-A3BF-D36DB702B530" correlationId="7DD6330D-536F-C6F9-1B35-EEE6E1050877" type="acknowledge" bubbles=false cancelable=false eventPhase=2])
The onChannelConnect and onConsumerMessageAck are actually the same (I think). They are just saying that the Channel did connect in the same way that the NetConnection could connect.
Connecting the Producer
Now click on Connect as Producer to send the messages. Next Flex code is executed:
-
private function connectAsProducer() : void
-
{
-
this.addLogMessage("connectAsProducer()");
-
-
producer = new WeborbProducer();
-
producer.channelSet = cs;
-
producer.destination = MESSAGINGDESTINATION;
-
producer.subqueue = this.subQueueu.text;
-
producer.addEventListener(MessageAckEvent.ACKNOWLEDGE, onProducerMessageAck);
-
producer.addEventListener(MessageFaultEvent.FAULT, onProducerMessageFault);
-
producer.producerId = this.clientId;
-
producer.send(new AsyncMessage());
-
}
I think this is quite obvious. Setting the clientId doesn't really do anthing and I haven't been able to trace it on the Server side. Sending an empy AsyncMessage() is just a test for me to know if the Producer is able to send the message. If the Consumer receives this empty message, then I know that I can begin sending real messages. If you don't send something, then the Producer will not be connected. I'm a bit uncertain how this Producer works as it is a class written by WebOrb. You can check it yourself by commenting the producer.send(new AsyncMessage()); line.
On the server side the processClientMessage(V3Message message) function in the CustomSelector is triggered. It is the CustomSelector instance for the Consumer of our Flex applicatoin. No connect or join is triggered because the same Channel as the Consumer is used. And this Channel is already connected. In the Flex Output Panel this should appear:
-
onProducerMessageAck([MessageAckEvent messageId="A489BA11-1A51-4E61-994B-D6866DF1DF4E" correlationId="462E277A-BFB5-DC6C-9B5E-EEEF74B6CD56" type="acknowledge" bubbles=false cancelable=false eventPhase=2])
-
onChannelMessage([MessageEvent messageId="462E277A-BFB5-DC6C-9B5E-EEEF74B6CD56" type="message" bubbles=false cancelable=false eventPhase=2])
-
Unknown Message
Sending three locked messages
Now click the button that sends a locked apple, banana and potatoe message and see what happens...
From here on I'll let you find things out yourself as I am tired or writing, taking screenshots, copy pasting...
I will explain some choices I made in Part III. If you have suggestions, please write them down. I'm very much sure that things can be done better, and as I continue with the project, I'm sure I'll comment on errors in my own post... One example is the NetConnection. I have done this to get a clientId. This clientId can be given to the Consumer. I would like to omit this NetConnection and give the clientId of the Consumer to the suscribe method of the Consumer... I have tried to first connect the Channel to get the ClientId and then suscribe the Consumer, but this throws an error...
So, well, I hope this was of any use!
Ciao! Johlero!
Thx to the midnight coders!


July 12th, 2008 at 8:13 pm
Too bad i didnt come across this blog before. Great stuff you got here. Thanks.
July 19th, 2008 at 10:35 pm
I came across this blog the other day and you got some great info here – thanks.
January 4th, 2009 at 11:17 am
Is this possibile with Flash CS3/4 too?
January 4th, 2009 at 3:57 pm
Hi Gabriella, yes, it certainly is possible with Flash CS3/4. This post ( http://blog.johlero.eu/?p=26 ) is written some time ago, but should still be valid. Otherwise I guess you can find examples in the WebORB installation!
March 12th, 2009 at 3:03 pm
Great articles about weborb & MSMQ. Looking forward for part III.
Thanks for sharing,
Alberto Acevedo
March 14th, 2009 at 10:39 am
Will try to do a part III with screen capture and include some of the new things in WebORB 3.6.
September 3rd, 2009 at 3:12 pm
Excellent site, keep up the good work
February 2nd, 2010 at 4:03 pm
Your example shows publishing an AsyncMessage from Flex to Flex. Can an AsyncMessage be published from the C# code? I want to invoke a push to the MSMQ from the server side.
February 2nd, 2010 at 4:22 pm
Check this.