Click here to Skip to main content
6,629,377 members and growing! (20,161 online)
Email Password   helpLost your password?
Web Development » Silverlight » Applications     Beginner License: The Code Project Open License (CPOL)

Silver Draw - A Silverlight Based Collaboration White Board with Drawing and Chat

By Anoop Madhusudanan

Silver Draw shows how to use Silverlight and WCF Polling Duplex services to create realtime collaboration apps.
C#, Visual Studio, WCF, Silverlight, Architect, Dev, Design
Version:10 (See All)
Posted:1 Nov 2009
Updated:2 Nov 2009
Views:5,871
Bookmarked:41 times
Announcements
Loading...
 
Search    
Advanced Search
Add to IE Search
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
17 votes for this article.
Popularity: 6.06 Rating: 4.92 out of 5

1

2
1 vote, 5.9%
3

4
16 votes, 94.1%
5

Introduction

Silverlight + WCF Polling Duplex services = Real Awesomeness. In this article, I'll show how to create a real-time white board that can sync information between various participants, using Silverlight + Polling Duplex. Silverlight + WCF Polling Duplex services enables you to write Silverlight apps that can share information almost real time between users, over HTTP.

Users can draw together in the white board, and may chat with each other. Here is a quick screenshot of the client running in two different browsers. And if you are more interested, have a look at this video of the app, in my blog.

The app has a login panel, a couple of drawing tools (pencil, pen, and a brush), a drawing canvas, a chat area, a color picker to choose color for line and fill, and a notification area to view activities from users in the session.

Internals made simple

Needless to say, the objective of the article is to explain how Polling Duplex can be implemented along with Silverlight and WCF. The application has two parts:

  1. Server - A WCF service with a polling duplex end point for clients to register, to publish information, and to receive notifications.
  2. Client - A Silverlight client that consumes the end point to register the user, to publish the user's drawing, and to receive and plot the drawing data from other users.

The server side web service interface (IDuplexDrawService) has these important methods:

  • Register - A client may call this to register itself to a conversation.
  • Draw - A client may call this to request the server to pump the drawing information to other clients in the conversation
  • NotifyClients - From the Draw method, we'll invoke NotifyClients to iterate the registered clients, to publish the data.

Also, as we are using Polling Duplex, we need a callback interface too - so that the server can 'call back' or notify the clients. (In Polling Duplex, you may need to note that what is happening is not actually a direct callback from server to client. The client should poll the server periodically over HTTP to fetch any possible information that the server is willing to pass to the client, to simulate a callback.) Anyway, our 'call back' service interface (IDuplexDrawCallback) has a Notify method, for the server to notify the rest of the clients when some client calls the Draw method.

So, in short - o party may publish some information using 'Draw', and others can subscribe to the published information. This is a simple implementation of the Publisher/Subscriber pattern.

When you load the client the first time, you'll be asked for a username to join the session. From there, the logic goes something like:

In client-side:

  • The client will try to connect to the service end point.
  • If success, the client will register itself with the client, by calling the Register method, and by passing the username.
  • The client will hook up a few event handlers in the proxy. Mainly, the NotifyCompleted event. NotifyCompleted will be fired each time you receive a callback from the server.

In server-side:

  • Inside the Register method, the server will grab the client's session ID and callback channel from the current operation context, and add it to a list.
  • Whenever a client submits information by calling 'Draw', the server will pump this information by iterating each registered client, and by calling their 'Notify' method.

Server implementation

Let us have a quick look at how the server side implementation is done. First of all, you need to add a reference to System.ServiceModel.PollingDuplexService.dll in your Silverlight SDK's Server folder. For me, this path is: C:\Program Files\Microsoft SDKs\Silverlight\v3.0\Libraries\Server\.

Service and callback interfaces

Now, let us create a simple interface for our service - IDuplexDrawService.

[ServiceContract (CallbackContract = typeof(IDuplexDrawCallback))]
public interface IDuplexDrawService
{
    [OperationContract(IsOneWay = true)]
    void Register(string name);
    [OperationContract(IsOneWay = true)]
    void Draw(string data);
}

You may note that we are specifying the CallbackContract attribute as the type of the callback interface. The callback interface looks like:

[ServiceContract]
public interface IDuplexDrawCallback
{
    [OperationContract(IsOneWay = true, 
          AsyncPattern = true, Action = DrawData.DrawAction)]
    IAsyncResult BeginNotify(Message message, 
           AsyncCallback callback, object state);
    void EndNotify(IAsyncResult result);
}

Nothing special there, other than that we are specifying our Notify method as two parts, to facilitate an asynchronous call. Later, we'll create a message from our custom object to pass it as the first parameter of BeginNotify. We should specify a message contract as well.

[MessageContract]
public class DrawData
{
    public const string DrawAction = 
               "http://amazedsaint.net/SilverPaint/draw";

    [MessageBodyMember]
    public string Content { get; set; }

    [MessageBodyMember]
    public string From { get; set; }
}

Alright, now we have the pieces ready, so let us create the concrete service. At this point, you might need to have a look at the DuplexDraw service source code for a side reference. Firstly, note that we are specifying a couple of attributes for the service.

[ServiceBehavior
        (ConcurrencyMode = ConcurrencyMode.Multiple, 
        InstanceContextMode = InstanceContextMode.Single)]
public class DuplexDrawService : IDuplexDrawService
{
   //Code here
}

InstanceContextMode is set to Single, which means only one instance context is used for handling all incoming client calls. Also, ConcurrencyMode is set to Multiple so that the service instance will be multi-threaded (we'll end up doing some explicit locking).

We may not go through all the methods in the service, but essentially the idea is as follows. As mentioned earlier, after creating a connection, the client should call the Register method. Inside the Register method in the server, we'll grab the callback channel and add it to a dictionary, with Session ID as the key.

string sessionId = OperationContext.Current.Channel.SessionId;
var callback = 
    OperationContext.Current.GetCallbackChannel   
                            <IDuplexDrawCallback>();

lock (syncRoot)
{
    clients[sessionId] = callback;
    userNames[sessionId] = name;
}

Draw and NotifyClients

The Draw and NotifyClients methods in the service are self explanatory. When some client calls the Draw method, we'll iterate the dictionary we have, to publish the data to all subscribed clients. Here is the essence of what is happening in the Draw and NotifyClients methods.

Inside the Draw method, we grab the incoming data to notify the connected clients.

/// A client will call this, to publish the drawing data
public void Draw(string data)
{
    lock (this.syncRoot)
    {
        string sessionId = 
          OperationContext.Current.Channel.SessionId;
        if (userNames.ContainsKey(sessionId))
            NotifyClients("@draw:" + data, userNames
           [sessionId],sessionId);
    }
}

Inside the NotifyClients method in the service, we actually pump the data to various clients. The NotifyClients method is as below. We create a message buffer from the DrawData.

//In the actual code, this is a global static variable
static TypedMessageConverter messageConverter = 
TypedMessageConverter.Create(
  typeof(DrawData),
  DrawData.DrawAction,
  "http://schemas.datacontract.org/2004/07/SilverPaintService");


/// Send the notification to all clients
public void NotifyClients(string data,string from,string sessionId)
{
    MessageBuffer notificationMessageBuffer = 
        messageConverter.ToMessage
        (new DrawData 
      { Content = data, From = from }).CreateBufferedCopy
        (65536);
    foreach (var client in clients.Values)
    {
        try
        {
           client.BeginNotify
               (notificationMessageBuffer.CreateMessage(), 
               onNotifyCompleted, client);
        }
        catch
        {}
    }
}

If you are wondering what is there in the data variable, it is a JSON serialized string that contains the drawing board information. We'll see more about that when we walk over the client side.

Endpoint configuration

And finally, a quick word on configuring the end points. Have a look at the system.ServiceModel section in the server web.config. Especially, have a look at the extensions section, where we are adding a new binding extension, named pollingDuplex. Then, we need to create a custom binding type (customBinding) and specify that as the binding of our endpoint.

<extensions>
    <bindingElementExtensions>
      <add name="pollingDuplex" 
        type="System.ServiceModel.Configuration.PollingDuplexElement, 
              System.ServiceModel.PollingDuplex"/>
    </bindingElementExtensions>
    <bindingExtensions>
      <add name="pollingDuplex" 
        type="System.ServiceModel.Configuration.PollingDuplexElement, 
              System.ServiceModel.PollingDuplex"/>
    </bindingExtensions>
  </extensions>
  <behaviors>
    <serviceBehaviors>
      <behavior
         name="SilverdrawServiceBehaviour">
        <serviceMetadata httpGetEnabled="true"/>
        <serviceDebug 
            includeExceptionDetailInFaults="true"/>
        <serviceThrottling maxConcurrentSessions
                 ="2147483647"/>
      </behavior>
    </serviceBehaviors>
  </behaviors>
  <bindings>
    <customBinding>
      <binding name="SilverdrawServiceBinding">
        <binaryMessageEncoding />
        <pollingDuplex 
          inactivityTimeout="02:00:00" 
          serverPollTimeout="00:05:00"
          maxPendingMessagesPerSession="2147483647" 
          maxPendingSessions="2147483647" />
        <httpTransport />
      </binding>
    </customBinding>
  </bindings>
  <services>
    <service behaviorConfiguration="SilverdrawServiceBehaviour" 
         name="Silverdraw.Server.DuplexDrawService">
      <endpoint address="" 
         binding="customBinding"
         bindingConfiguration="SilverdrawServiceBinding"
         contract="Silverdraw.Server.IDuplexDrawService"/>
      <endpoint address="mex" 
         binding="mexHttpBinding" 
         contract="IMetadataExchange"/>
    </service>
  </services>

Now, let us examine what is there in the client side.

Silverlight client

You need to add a reference to System.ServiceModel.PollingDuplexService.dll in your Silverlight SDK's Client folder. For me, this path is: C:\Program Files\Microsoft SDKs\Silverlight\v3.0\Libraries\Client\.

Connecting to the server

In the client side, most of the work is done inside the DuplexClientHelper class. First of all, we need to add a service reference, and create a proxy for the service we created.

DuplexClientHelper is a thin wrapper on top of the proxy generated out of the service.

When the Silverlight control is loaded, we'll show a quick login panel, and from the button click there, we'll invoke the Initialize method in DuplexClientHelper.cs, via the InitServiceConnection method in Page.xaml.cs.

There, we need to create a new client instance, and wire up the events to receive notifications.

public void Initialize(string endPointAddress)
{
    this.client = new Proxy.DuplexDrawServiceClient(
    new PollingDuplexHttpBinding(),
    new EndpointAddress(endPointAddress));

    this.client.NotifyReceived += new 
        EventHandler<proxy.notifyreceivedeventargs>
                (client_NotifyReceived);
}

As you might have guessed, NotifyReceived is invoked whenever the server pumps a message to the client. When we receive the notification, we may fetch the DrawingData from the request. Here is a stripped down version of the event handler:

/// Callback to get the notification
void client_NotifyReceived
   (object sender, Proxy.NotifyReceivedEventArgs e)
{
    var data = e.request.GetBody<drawdata>();
}

Drawing logic

Most of the drawing logic is in DrawingArea.cs and Page.xaml.cs in our Silverlight client. Most of the code is self explanatory - we use the traditional way of drawing - setting a boolean flag to true in mouse down, to draw the elements in mouse move.

Shapes are drawn to the canvas with respect to the selected tool (brush, pen etc.), by invoking a couple of methods in DrawingArea.cs, from the mouse down events in Page.xaml.cs.

Remember - when we add shapes to the canvas, we need to publish this information to other clients, right? For this, we have a temporary list of shapes in the DrawingArea class - where we keep the shapes added to the local canvas. When the number of shapes in our temporary list reaches a specific count, we serialize the shapes to a JSON string, and pass it to the server to publish - using the Draw method described earlier.

You may also note that we are converting the shapes to a serializable object model (see ScreenObject.cs) before doing the actual serialization. The methods for converting the shapes back and forth from the ScreenObject type resides in the CanvasHelper.cs file.

And how do we convert this to JSON? We have a couple of extension methods in the JsonSerializerHelper.cs file. Also, I have a blog post here on these extension methods and how JSON works.

Future ideas

The source code is available in CodePlex - join and contribute to evolve the application. Here are a few ideas:

  • Registration and user authentication upon login (probably using ASP.NET membership providers)
  • Dynamic loading of shapes using MEF
  • Evolve the application to a full fledged prototyping tool - similar functionalities as in balsamiq.com
  • Multiple drawing rooms + users can join rooms upon invitation
  • Persist the messages drawing back and forth - so that we can replay the activity as in Google Wave, and pump the drawing data so far to a user up on connect

Conclusion

Credits are due - The idea itself came out after reading this post in the Tomek on Software blog, and the source code from that post is used as the primary reference. The color picker control in the drawing app is from designerwpf.com. And, thanks to my wife for letting me spend an entire Saturday and Sunday on this. LOL!

A few more points - Of course, it requires more efforts to come up with a scalable architecture for using this in a commercial application. And real time information sharing is not new - but Silverlight + WCF Polling Duplex is "over HTTP" - which makes it accessible to a wider audience. I have a few more thoughts on this here.

And finally, subscribe to my blog or bookmark the same - for more stuff: http://amazedsaint.blogspot.com.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Anoop Madhusudanan


Member
Follow: In Twitter | Blog

Don't Miss this
  1. .NET Reactive Extensions and LINQ to Events

  2. MEF or Managed Extensibility Framework – Creating a Zoo and Animals

  3. C# 4.0 Dynamic Features - 'Attaching' properties/methods at runtime

  4. Applying Design Patterns - Thought Process with examples

  5. Fluent programming in C#

About Anoop Madhusudanan

Anoop's primary expertisation is in areas like application modelling, VS SDK, GIS, Security, code generation & meta coding, neural networks, internet/intranet technologies etc.

For the past couple of years, he is mainly working with Visual Studio Extensibility (VSX) and Enterprise Integration tools, to build domain specific frameworks and packages, mainly for Finance/Tax domain.

Now he is working as a Solution Architect, mainly in the .NET space.

He holds his BTech In Computer Engineering, Diploma in Computer Hardware Engineering and Post Diploma in Software Engineering.

You may contact him at amazedsaint (at) gmail.com.
Occupation: Architect
Location: India India

Other popular Silverlight articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 20 of 20 (Total in Forum: 20) (Refresh)FirstPrevNext
GeneralAwesome PinmemberXmen W.K.5:07 10 Nov '09  
GeneralGood article, Anoop PinsitebuilderNishant Sivakumar2:14 4 Nov '09  
GeneralRe: Good article, Anoop PinmemberAnoop Madhusudanan18:22 4 Nov '09  
GeneralRe: Good article, Anoop PinsitebuilderNishant Sivakumar2:46 5 Nov '09  
GeneralRe: Good article, Anoop PinmemberAnoop Madhusudanan7:59 5 Nov '09  
GeneralRe: Good article, Anoop PinsitebuilderNishant Sivakumar13:01 5 Nov '09  
GeneralDid you consider using the InkPresenter PinmemberDewey10:13 3 Nov '09  
GeneralRe: Did you consider using the InkPresenter PinmemberAnoop Madhusudanan16:57 3 Nov '09  
GeneralExcellent job Anoop PinmvpPete O'Hanlon10:03 3 Nov '09  
GeneralRe: Excellent job Anoop PinmemberAnoop Madhusudanan17:04 3 Nov '09  
GeneralExcellent PinmvpSacha Barber1:46 3 Nov '09  
GeneralRe: Excellent PinmemberAnoop Madhusudanan2:53 3 Nov '09  
GeneralRe: Excellent PinmvpSacha Barber3:14 3 Nov '09  
GeneralAwesome! Pinmembermattiassundstrom23:15 2 Nov '09  
GeneralMay be nice, but dumb release PinmemberDewey19:38 2 Nov '09  
GeneralRe: May be nice, but dumb release PinmemberJanusPien0:34 3 Nov '09  
GeneralRe: May be nice, but dumb release PinmemberAnoop Madhusudanan2:59 3 Nov '09  
GeneralRe: May be nice, but dumb release PinmemberDewey10:10 3 Nov '09  
GeneralDude, this is awesome! PinmemberThinkMud11:15 2 Nov '09  
GeneralRe: Dude, this is awesome! PinmemberAnoop Madhusudanan17:32 2 Nov '09  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 2 Nov 2009
Editor: Smitha Vijayan
Copyright 2009 by Anoop Madhusudanan
Everything else Copyright © CodeProject, 1999-2009
Web22 | Advertise on the Code Project