28 March 2011
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.
Intermediate
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.
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:
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:
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:
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)
public string sharedObjectName = "addWord";
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 roomcurrentUser: id and name of the client whose turn it is to add the next wordsentence: the actual sentence being formedword: the last word submittedEach client that connects to a room is assigned unique ID based on the connection order.
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));
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.
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.
public static void nextUser(ISharedObjectBase so,string word) {
ThreadPool.QueueUserWorkItem(ChangeCurrentUser, new object[] {so, word});
}
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);
}
…
}
so.setAttribute("currentUser", newCurrentUser.ToString() );
roomLeave() method to handle clean-up tasks when a user leaves the room. This method updates the list of clients still playing the game.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);
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.
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
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.
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:
<s:states>
<s:State name="login" />
<s:State name="game" />
</s:states>
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>
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.
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.
public function setUserId(userId:String):void
{
this.userId = userId;
this.currentState="game";
setName = true;
}
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.)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 = "";
}
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.
This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License. Permissions beyond the scope of this license, pertaining to the examples of code included within this work are available at Adobe.