by Damian M. Piccolo

Damian M. Piccolo

by Esteban Yofre

Esteban Yofre

Table of contents

Created

29 March 2011

Requirements

   
Prerequisite knowledge
To make the most of this tutorial you’ll need previous experience building applications with Adobe Flash Builder as well as some knowledge of development techniques using .NET and Microsoft Visual Studio.
 
 
User level
Intermediate
 
Required 
Flash Builder 4 (Download trial)
Sample files

 
Additional required other products

In this tutorial you’ll learn about Remote Shared Objects (RSOs) and how to use them from either the client or server side. You’ll also develop a small application based on a simple word game that will access and modify RSOs using from client-side (ActionScript) and server-side (C#) code using the WebORB Integration Server to marshal the communications between client and server. You’ll need to have an IIS server running and WebORB installed on your server.
 

 
About Remote Shared Objects

RSOs track, store, share, and synchronize data between multiple client applications.
 
An RSO is an object that lives on the server. It resides in the scope of a messaging application that clients connect to. More than one client can connect to an RSO, and all of them will have access to the data in the RSO. WebORB, in this case, is responsible for managing the RSO and providing access to it for various clients.
 
RSOs are particularly useful when they are used on several clients at the same time. When one client changes data that updates the RSO on the server, the server sends the change to all other connected clients, enabling you to synchronize many different clients with the same data. RSOs can also be updated and accessed by the server, giving developers more options for application development.
 
To sum it up, RSOs can be used to:
 
  • Synchronize data of any type automatically, for example, in a collaborative web application.
  • Send a message to all clients connected to the same room, for example, in a chat application.
In this tutorial, you will use RSOs to create a simple online version of the Add-a-word game. The object of this game is to add a word to a sentence, one user at a time, and eventually come up with a very long sentence (that still makes sense).
 
Consider the following example:
 
  • Two connected users begin a game of Add-a-word.
  • The application assigns turns to each of the connected users.
  • User 1 starts the game by typing "My" and clicking Submit. Once User 1 submits a word, it is the next user’s turn. The application automatically disables the submit button for the first user, and enables it for the next user.
  • User 2 types "house" and clicks Submit, passing control back to User 1.
  • User 1 types "is", and so on.
  • During the game, the RSO keeps track of the content of the sentence. At this point it will read "My house is".

 
Writing the server-side code

The server-side code for this application keeps track of all connected users, assign turns, and add words to the sentence.
 
Note: The code snippets included in the steps below are not complete; rather they are used to illustrate the main concepts in the server-side implementation. For the complete code, see WeborbSharpRSO.cs in the sample files for this tutorial.
 
Follow these steps to create the server-side DLL:
 
  1. Create a Class Library project in Microsoft Visual Studio.
  2. In the project, add a reference to the Weborb.dll. This library is located in the bin folder in your WebORB installation folder.
  3. Create the main class, in this case, named WeborbSharpRSO. This class extends Weborb.Messaging.Server.Adapter.ApplicationAdapter, which lets you create WebORB applications:
namespace WeborbSharpRSO { public class WeborbSharpRSO : ApplicationAdapter { } }
The ApplicationAdapter class has several methods that let you know when an application is started, when a new room is created, and when a client connects or disconnects. An application can have several rooms running simultaneously. Each room may have one or more clients connected and sharing information. Clients connected to one room share information only with other clients in the same room.
 
For this example you’ll use the following two methods to detect when a client joins or leaves a room:
 
public override bool roomJoin(IClient client, IScope room) public override void roomLeave(IClient client, IScope room)
  1. Declare the Remote Shared Object name at the top of your class:
public string sharedObjectName = "addWord";
  1. In the roomJoin() method, first check if the user can connect to the room. Then check if the room has the Remote Shared Object. If not, create it and add a SharedObjectListener to it. This listener will detect any changes in the RSO.
public override bool roomJoin(IClient client, IScope room){ if (base.roomJoin(client, room)) { ISharedObject so; if (!hasSharedObject(room, sharedObjectName)){ createSharedObject(room, sharedObjectName, false); so = getSharedObject(room, sharedObjectName); so.addSharedObjectListener(new MySharedObjectListener()); } else so = getSharedObject(room, sharedObjectName); … } }
Note: The code in roomJoin could also be placed inside of the appStart method.
 
The next step is to check and update the values of some attributes of the RSO, such as the number of users connected and whose turn is next. These attributes hold all the information you want to share across clients.
 
For example, your code will get the existing number of users connected to the room from the shared object (SO) and then increment that number by one. This value is then written back to the SO:
 
if(so.hasAttribute("totalUsers")) totalUsers = so.getLongAttribute("totalUsers"); totalUsers++; … so.setAttribute("totalUsers", totalUsers);
In addition to totalUsers, the RSO shares the following data:
 
  • userList: List of all the clients connected to the same room
  • currentUser: id and name of the client whose turn it is to add the next word
  • sentence: the actual sentence being formed
  • word: the last word submitted
    Each client that connects to a room is assigned unique ID based on the connection order.
     
  1. To finish up the roomJoin() function, add the following code, which sends this ID to the client using the invokeClients method (explained in more detail below):
object[] args = new Object[] {client.getId()}; invokeClients("SetUserId", args, client.getConnections(room));
  1. Next, add the Listener class implementing Weborb.Messaging.Api.SO.ISharedObjectListener interface:
public class MySharedObjectListener : ISharedObjectListener { }
This class provides methods that can be used to check for different types of changes on the shared object. This tutorial focuses on the onSharedObjectUpdate() method, which is used to check for changes on the remote object. A change on the word attribute of the RSO invokes a method that will process the information and set the next user.
 
  1. Update onSharedObjectUpdate() as follows:
public void onSharedObjectUpdate(ISharedObjectBase so, string key, object value){ if (key == "word") WeborbSharpRSO.nextUser(so, value.ToString()); }
The nextUser() method is called when there’s a new word to add to the sentence. Even though you could modify the RSO directly in this function, do not use this approach.
 
When the server-side code changes the RSO during a client-side request, the client that initiated the request does not get the changes added by the server. The server-side accumulates all the changes as independent events. The accumulated events are sent out to the original sender first and then to all other subscribers. Each event has a corresponding RSO version number. If there are multiple change events all going out with the same version number, Flash Player lets only the first one through. This will cause the client that initiated the request to get only the changes it requested, not the changes added by the server to the RSO on the same request.
 
  1. To address this, start a new thread where the actual server-side RSO changes will take place:
public static void nextUser(ISharedObjectBase so,string word) { ThreadPool.QueueUserWorkItem(ChangeCurrentUser, new object[] {so, word}); }
  1. Add a ChangeCurrentUser() function to actually change the RSO on the server. Here’s where you add the word to the sentence and write it back to the RSO:
public static void changeCurrentUser( object state ){ … if( word != "" ){ string text = ""; if( so.hasAttribute("sentence") ) text = so.getStringAttribute("sentence"); so.setAttribute("sentence", text += " " + word); } … }
  1. Use this same function to identify who the next user is and then write that information back to the RSO too.
so.setAttribute("currentUser", newCurrentUser.ToString() );
  1. Add the roomLeave() method to handle clean-up tasks when a user leaves the room. This method updates the list of clients still playing the game.
  2. Note the three-step process: the content of the userList attribute is retrieved and set to the users dictionary, the client that is leaving the room is removed from the dictionary, and then a copy of the content of this object is put into a newUsers dictionary. The reason behind this awkward process is that the server won’t detect the change on the original users object when an element is removed. As a result, if you send back the same object to the RSO, the changes will not be sent to the clients.
     
users = so.getMapAttribute("userList"); … if (users.Contains(client.getId())) users.Remove(client.getId()); newUsers = new Dictionary<string, string>(); foreach (DictionaryEntry de in users) newUsers[de.Key] = de.Value; … so.setAttribute("totalUsers", totalUsers);
  1. Lastly, add the invokeClients() to the main class. This function uses the connection.invoke method to call ActionScript methods on the client. You will need to pass the name of the method to call, any needed parameters, and the list of client connections. The name used in the functionName variable must exist as a function name in the client application:
private void invokeClients(string functionName, object[] args, IList<IConnection> ILconn ){ foreach(IConnection conn in ILconn){ ((IServiceCapableConnection)conn).invoke(functionName, args); } }
A detailed explanation of the invoke() method is outside of the scope of this tutorial. For more information about this method please consult the WebORB documentation or see Invoke ActionScript functions from .NET.
 
  1. Once you have finished coding the class, build the DLL and copy it to the bin directory inside the WebORB folder.

 
Configuring WebORB

You’ll need to configure WebORB before your application will work. Specifically, you need to add a messaging application so WebORB is aware of its existence and can manage the user connections. Follow these steps:
 
Open the WebORB management console (see Figure 1) using a web browser. If you installed WebORB using the default settings, the console is available at: http://localhost/weborb4/weborbconsole.html
 
  1. Click the Messaging Server tab to begin creating a new messaging application.
  1. Select Applications under WebORB Messaging Server (see Figure 2) and then click the Add Application button (the green plus sign ("+") button).
  2. In the Application Editor pop-up window, type MyRSO as the application name and then select WeborbSharpRSO.WeborbSharpRSO as the Application Handler (see Figure 3).
  1. Click Save. Your application will be listed in the list of applications.
  2. Browse your hard drive, and locate the folder named MyRSO inside the {weborb4 path}\Applications\ folder. Verify that the MyRSO folder contains a file named App.config.
Note: If the MyRSO application doesn’t show up under the list of applications, but you can see the folder in the hard drive, you may need to restart IIS and then reload the WebORB console.
 
If you cannot see the MyRSO folder in your hard drive you may need to check your permissions. For more information on permissions, refer to the WebORB installation and deployment documentation available through the Help/Resources tab of the WebORB console.
 

 
Writing the ActionScript code

With the server-side code in place, you’re ready to open Adobe Flash Builder and develop the ActionScript code.
 
Note that you could have made many of the changes to the RSO directly with ActionScript using WebORB’s own SharedObjectsApp messaging application. This tutorial used a more convoluted path to illustrate RSO access and modification from the server.
 
Note: The code snippets included in the steps below are not complete; rather they are used to illustrate the main concepts in the client-side implementation. For the complete code, see WeborbRSO.mxml in the sample files for this tutorial.
 
To create your ActionScript code, follow these steps:
 
  1. Start by creating a new Flex Project in Flash Builder (choose File > New > Flex Project).
  2. Set up a basic application with two states: login and game:
<s:states> <s:State name="login" /> <s:State name="game" /> </s:states>
  1. Add code for the login state, which comprises a TitleWindow with two textInput controls (Room Name and User Name) and a Connect button:
<s:TitleWindow includeIn="login" verticalCenter="0" horizontalCenter="0" width="300" height="180" > <s:layout> <s:VerticalLayout gap="5" verticalAlign="middle" horizontalAlign="center" /> </s:layout> <s:HGroup verticalAlign="middle"> <s:Label text="Room Name:" width="80" textAlign="right" /> <s:TextInput id="txtRoomName" text="Test1" width="150" /> </s:HGroup> <s:HGroup verticalAlign="middle"> <s:Label text="Your Name:" width="80" textAlign="right" /> <s:TextInput id="txtYourName" text="Damian" width="150" /> </s:HGroup> <s:Button label="Connect" click="onConnect()" /> </s:TitleWindow>
  1. Add code for the game state, which comprises an HGroup that displays a list of the connected users on the left, the sentence the users are forming on the top right, and a textarea control where the users can type the next word at the bottom right:
<s:HGroup includeIn="game" verticalCenter="0" horizontalCenter="0"> <s:TitleWindow title="User List ({totalUsers})" width="150" height="300"> <s:List id="lUsers" dataProvider="{userList}" labelField="Name" width="100%" height="100%"> <!-- We use an item renderer to color red the name of the current user --> <s:itemRenderer> <fx:Component> <s:ItemRenderer height="20"> <s:Label id="myLabel" width="100%" verticalCenter="0" text="{data.name}" color="{data.userId==data.current?0xff0000:0x000000}"/> </s:ItemRenderer> </fx:Component> </s:itemRenderer> </s:List> </s:TitleWindow> <s:TitleWindow title="Add a Word -- {userName} -- User Id:{userId}" width="500" height="300"> <s:layout> <s:VerticalLayout verticalAlign="middle" horizontalAlign="center" /> </s:layout> <s:TextArea editable="false" selectable="false" width="450" height="200" text="{text}" /> <s:HGroup id="hgSendText" gap="10" horizontalAlign="center" verticalAlign="middle" enabled="{userId==currentUser?true:false}"> <s:TextInput id="txtAddText" width="400" /> <s:Button label="Add Word" click="onSendText()" /> </s:HGroup> </s:TitleWindow> </s:HGroup>
The Connect button in the login state invokes the onConnect() function. This function connects to the server and obtains the remote shared object.
 
  1. Add onConnect() to the block:
public function onConnect():void{ roomName = txtRoomName.text; userName = txtYourName.text; SharedObject.defaultObjectEncoding = ObjectEncoding.AMF0; /** * Establish connection * */ nc = new NetConnection(); nc.client = this; nc.objectEncoding = ObjectEncoding.AMF0; nc.addEventListener( NetStatusEvent.NET_STATUS, onNetStatus ); nc.connect( urlServer + "/" + weborbApplicationName + "/" + roomName ); /** * Get Remote Object * */ so = SharedObject.getRemote( sharedObjectName, nc.uri, false ,false); so.client = this; so.addEventListener( SyncEvent.SYNC, onSync ); so.connect( nc ); }
The server URL, name of the WebORB application, and name of the remote object are previously declared in variables. The name of the chat room is obtained from the login window. Users can login to different chat rooms to work on different sentences.
 
  1. Add the setUserId() function, which is called from the server at the end of the connection process. Use this function to save the userID of the client so you can match it to the username. This function also changes the application’s state from login to game.
public function setUserId(userId:String):void { this.userId = userId; this.currentState="game"; setName = true; }
  1. Add the onSync() function, which is called each time there’s a sync event on the remote shared object. Use this function to set the client’s name the first time they login. Also use this function to enable/disable the button to submit text as well as to update the list of users logged in. Each time there is a change, this function creates the list and sets the id of the user whose turn it is. (See WeborbRSO.mxml for the full implementation.)
  2. Finally, add the onSendText() function, which send the word to the server. This function is called when the user clicks the Add Word button. It simply sets the word property on the shared object.
public function onSendText():void { so.setProperty("word",txtAddText.text); txtAddText.text = ""; }
  1. Build the application and try it out!

 
Where to go from here

This add-a-word game is just a simple application that showcases some of the possibilities of Remote Shared Objects. Of course, there are several ways to improve this application. To start with, you could limit the content sent by a user to just one word, and not allow any number of words. You could also use the RSO to keep track of several sentences at a time inside the same room if you wanted.
 
This tutorial demonstrated that RSOs can be used in many different situations, ranging from sharing information between clients to managing and synchronizing real-time online games. Now that you’re familiar with them, you can adapt these techniques for your own applications.
 
You can try this application at Anden Solutions.
 
For more information about WebORB for .NET visit its overview page.