Chat Demo - Silverlight Clients
Silverlight
Silverlight is Microsoft's solution for creating rich web applications using .NET technology. A Silverlight program consists of .NET code compiled into a DLL assembly together with Extended Application Markup Language (XAML) for describing the user interface. The application is downloaded on demand by the web browser and executed by a Silverlight plug-in.
Ice for Silverlight
ZeroC introduced Ice for Silverlight as the first experimental project of ZeroC Labs. Early versions of Ice for Silverlight used an HTTP transport that encapsulated Ice requests and sent them to an ASP.NET bridge to be forwarded to the target servers. The HTTP transport did not support callbacks, therefore the Silverlight client in the initial release of the chat application used the pull model together with a customized version of the bridge that, for security reasons, would only forward requests to the chat server.
A second transport was added in Ice for Silverlight 0.3 that uses a standard socket connection to a Glacier2 router. This transport not only allows a Silverlight application to integrate more seamlessly with an organization's existing Glacier2-based infrastructure, but also makes it possible for applications to receive callbacks from servers. As a result, we haveadded a Silverlight push client to the chat application that demonstrates the use of callbacks.
User Interface
The user interface of our Silverlight chat clients is constructed primarily using HTML and Cascading Style Sheets (CSS), similar to that of the PHP client. JavaScript code invokes directly into the C# application to relay commands, and C# code calls back into JavaScript to update the user interface.
The Silverlight clients have the same considerations as a graphical client in that they must maintain a responsive user interface. Unlike the C# and Java language mappings, Ice for Silverlight raises an exception if a program attempts to issue a synchronous invocation from the UI thread because doing so could cause the program to hang. Consequently, our Silverlight clients use Ice's asynchronous programming model, called Asynchronous Method Invocation (AMI). To invoke an operation using AMI, the operation's Slice definition must be annotated with metadata, as shown in the following example:
interface ChatSession extends Glacier2::Session {
["ami"] long send(string message) throws InvalidMessageException;;
// ...
};
Push Client
The Silverlight push client establishes a regular socket connection to a Glacier2 router and receives callbacks over that same connection. This client has much in common with other push clients, such as the steps a client must take to create a Glacier2 session. One aspect that is unique to the Silverlight clients is the JavaScript integration, as shown in the diagram below detailing the interactions of the various components when the user submits a new chat message:
Now let's examine the code that implements the interactions shown on the diagram, starting with the JavaScript event handler that is invoked when the user presses the Enter key:
function processInputEvent(event)
{
if(event.keyCode == 13)
{
var sl = document.getElementById("SilverlightControl");
sl.Content.ChatCoordinator.send($('txtMessage').value);
}
}
The script obtains a reference to an object that is exported by our Silverlight code and supports a method named send. The event handler passes the contents of a text input field to send, whose C# definition is shown below:
[ScriptableMember]
public void send(string message)
{
Chat.ChatSessionPrx session = ...
session.send_async(sendMessageResponse, sendMessageException, message);
}
The attribute ScriptableMember makes it possible for JavaScript code to invoke this method. The implementation of the method invokes an asynchronous Ice operation on its session proxy to publish a new chat message. The first two arguments to send_async are delegates that handle the success or failure of the invocation when it eventually completes. For example, the sendMessageResponse method schedules a delegate that displays a new chat message. It accomplishes this with the help of a ScriptObject, which allows Silverlight code to call back into JavaScript to invoke the function userSay:
public void sendMessageResponse(long timestamp)
{
Dispatcher.BeginInvoke(delegate()
{
ScriptObject chatView = ...
chatView.Invoke("userSay", formatTimestamp(timestamp), _username, _message);
});
}
The JavaScript function userSay updates the user interface:
userSay:function(timestamp, name, message)
{
this.addMessage("<div>" + timestamp + " - <" + name + "> " + message+ "</div>");
}
While the client is updating the user interface after receiving a notification that the send operation completed successfully, the server is busy distributing the new chat message to all of the clients. The last series of invocations on the diagram depicts the server issuing a send callback to the client that originated the message. The client must ignore any messages that were sent by the current user to avoid displaying duplicates:
public class ChatRoomCallbackI : Chat.ChatRoomCallbackDisp_
{
public override void send(long timestamp, string name, string message,
Ice.Current current)
{
if (!name.Equals(getUsername()))
{
userSayEvent(timestamp, name, message);
}
}
...
}
If you would like to see the Silverlight push client in action, you can use ZeroC's chat server.
Pull Client
The Silverlight pull client communicates with the chat server via the HTTP bridge. The interaction diagram for the pull client looks quite similar to that of the push client, except in this case the client must poll for new updates by periodically calling getUpdates:
As in the PHP client, the Silverlight pull client must iterate over the updates it has retrieved and determine the derived type of each event before deciding how to process it:
private void getUpdatesResponse(PollingChat.ChatRoomEvent[] events)
{
Dispatcher.BeginInvoke(delegate()
{
for(int i = 0; i < events.Length; ++i)
{
if(events[i] is PollingChat.MessageEvent)
{
PollingChat.MessageEvent e = (PollingChat.MessageEvent)events[i];
if(!_username.Equals(e.name))
{
_chatView.Invoke("userSay", formatTimestamp(e.timestamp),
e.name, e.message);
}
}
else ...
}
}
}
The client processes the chat updates in the event loop thread because it interacts directly with user interface elements. In the case of a new chat message, the code invokes the JavaScript function userSay to add the message to the chat history.
The second part of the article on the chat application describes the pull client in greater detail. To see this client in action, visit ZeroC's chat server.
| Previous: PHP Client | Next: iPhone Client |