Providing simple collaboration support for your diagramming tools with WCF services.


Notes: This article assumes you are already familiar with WCF services or studying them.

Several months ago I’ve posted simple draft project for a small diagramming tool. Though I didn’t intend it to be a tool or starter kit it is still the most visited post among all other more interesting things 😉 As I’ve been playing with WCF and wanted some simple environment for testing some ideas I remembered about old CTP project and found it was quite a good playground for my experiments. So this article will be using the reviewed and edited version of the project mentioned in the old article.

I’d also recommend you visiting the Code Project to see the wonderful samples by Marcus. I’ve dared to take his toolbox and tool item implementation for my article samples 🙂

What is the key changes made since the previous sample:

1. Simple MVC pattern implementation sample

The classes introducing the pattern are:

DiagramView: WPF canvas hosting the shapes and connection lines

DiagramController: Simply the controller

DiagramModel: Data model hosting the business objects (entities) that are not related to presentation layer.

As my intention was processing business objects rather that shapes and controls I’ve decided to perform a dummy bridging between raw "DiagramEntity" classes and their UI representation.

I’ve also simplified event system between Model, Controller and View based on Action<DiagramEntity> delegates for clarity purposes. Model is capable only of storing the entities, adding them and removing. In this implementation the View is also responsible for instantiating the Collaboration Client that will be described later on.

2. Unity Application Block

I’ve reduced the influence of the service bus for many parts of the application in favor to Unity Application Block. That made my life much easier.

3. Toolbox

As I moved on for using a simple toolbox I’ve eliminated the shape tools and shape creators that were rudimentary parts of Factory design pattern. But left the connection creator tools with a simple Strategy pattern implementation just for educational purposes.

4. Extensions and IExtensibleObject<T> interface

In this article I’ve described some ideas for extending Canvas panels by means of IExtensibleObject<T> interface taken from WCF. I’ve decided it would be fair enough reusing this stuff to show that it’s working fine. The Canvas (View) is extended with Selection and Drag functionality that can be simple switched off if required.

5. Aero theme by default and many minor improvements…

Here’s how it looks like

image

 

Basic collaboration support using WCF service

My intention was giving some sketches for how it could be implemented and some raw material for starting to play with. So I’ll be concentrating on it omitting thread safeties, error checking and the stuff like that for clarity purposes. Guess it would be much easier extending the sources based raw skeleton implementation.

Business objects

Entities

As I’ve mentioned the entities my service will be operating on are not related to presentation layer so it won’t be aware of WPF and the stuff like that. It will be the View responsibility turning them into the visuals and applying different styles and templates. Meanwhile we need at least some basic UI related information be passed like positioning coordinates so this is what UIEntity class serves for. I’ll be playing with three entities named "DataEntity", "SystemEntity" and "UserEntity" that will be turned later on to the appropriate shapes at client side. This provides nice presentation flexibility allowing different clients using specific visuals and styles though following one remote model.

Remote Model and Remote Callbacks

I’ve chosen the Duplex WCF service for the sample that requires implementing a callback contract also. For more details on duplex services better refer to MSDN library as it might take long time for me describing theory that is already perfectly presented 😉

This is how the remote model might look like:

using System.ServiceModel;

namespace Collaboration.Contracts
{
  [ServiceContract(
    Name = "RemoteModel",
    Namespace = "http://dvuyka.spaces.live.com/diagramming/types",
    CallbackContract = typeof(IRemoteModelCallback),
    SessionMode = SessionMode.Required)]
  [ServiceKnownType(typeof(UserEntity))]
  [ServiceKnownType(typeof(DataEntity))]
  [ServiceKnownType(typeof(SystemEntity))]
  public interface IRemoteModel
  {
    [OperationContract(IsOneWay = true)]
    void AddEntity(DiagramEntity entity);

    [OperationContract(IsOneWay = true)]
    void RemoveEntity(DiagramEntity entity);

    [OperationContract(IsOneWay = true)]
    void Connect();
  }
}

Due to serialization purposes our model should be aware of all the inheritances that might take part in the collaboration process. I’ve declared them as "known" to the service being described by this interface. Our remote model will also provide Adding and Removing capabilities like the "local" tool’s model and subscription to service via the Connect method.

The callback contract might look like the following:

using System.ServiceModel;

namespace Collaboration.Contracts
{
  public interface IRemoteModelCallback
  {
    [OperationContract(IsOneWay = true)]
    void OnEntityAdded(DiagramEntity entity);

    [OperationContract(IsOneWay = true)]
    void OnEntityRemoved(DiagramEntity entity);
  }
}

It means that remote model (or simply service) will be raising "OnEntityAdded" and "OnEntityRemoved" events for all the subscribers providing them with the entities to be immediately added to the local model and so presented with the View.

Wiring up the "local" model

At the client side we are creating a simple wrapper delegating remote model events to the local model like the following

using Collaboration.Contracts;

namespace HomeDiagramming.Collaboration
{  
  public sealed class RemoteModelCallback : IRemoteModelCallback
  {
    public DiagramModel LocalModel { get; private set; }

    public RemoteModelCallback(DiagramModel localModel)
    {
      this.LocalModel = localModel;
    }

    #region IRemoteModelCallback Members

    public void OnEntityAdded(DiagramEntity entity)
    {
      this.LocalModel.AddEntity(entity);
    }

    public void OnEntityRemoved(DiagramEntity entity)
    {
      this.LocalModel.RemoveEntity(entity);
    }

    #endregion
  }
}

So each time the service will be publishing "OnEntityAdded" or "OnEntityRemoved" events to it’s subscribers the event execution will be delegated to the underlying local model by calling it’s appropriate members.

The client connecting our remote model will be as simple as possible:

using System.Diagnostics;
using System.ServiceModel;
using System.ServiceModel.Channels;
using Collaboration.Contracts;

namespace HomeDiagramming.Collaboration
{
  [DebuggerStepThrough]
  public class RemoteModelClient : DuplexClientBase<IRemoteModel>, IRemoteModel
  {
    public RemoteModelClient(InstanceContext callbackInstance) :
      base(callbackInstance)
    {
    }

    public RemoteModelClient(InstanceContext callbackInstance, Binding binding, 
EndpointAddress address)
      : base(callbackInstance, binding, address)
    {      
    }

    #region IRemoteModel Members

    public void AddEntity(DiagramEntity entity)
    {
      base.Channel.AddEntity(entity);
    }

    public void RemoveEntity(DiagramEntity entity)
    {
      base.Channel.RemoveEntity(entity);
    }

    public void Connect()
    {
      base.Channel.Connect();
    }

    #endregion
  }
}

During creation of the View that instantiates the Controller we can initialize our remote controller in the following way

InstanceContext context = new InstanceContext(new RemoteModelCallback(this.Controller.Model));
NetTcpBinding binding = new NetTcpBinding();
EndpointAddress address = new EndpointAddress("net.tcp://localhost:8080/RemoteModel");    
remoteModel = new RemoteModelClient(context, binding, address);
remoteModel.Connect();

Remote model skeleton

And finally here comes the raw skeleton for the remote model

using System;
using System.Collections.Generic;
using System.ServiceModel;
using Collaboration.Contracts;

namespace Collaboration.Service
{
  [ServiceBehavior(
    InstanceContextMode = InstanceContextMode.Single, 
    ConcurrencyMode=ConcurrencyMode.Multiple)]
  public class RemoteModel : IRemoteModel
  {
    // TODO: should be the thread-safe collections
    private List<DiagramEntity> entities = new List<DiagramEntity>();
    private List<IRemoteModelCallback> callbacks = new List<IRemoteModelCallback>();

    #region IRemoteModel Members

    public void AddEntity(DiagramEntity entity)
    {
      // TODO: should be performed in a thread-safe manner and fully async
      Console.WriteLine("Adding entity :" + entity.Name);
      lock (entities)
      {
        this.entities.Add(entity);        
      }

      Console.WriteLine("Entity added. Total count: " + entities.Count);
      IRemoteModelCallback callback = GetCallback();      
      foreach (IRemoteModelCallback c in callbacks)
        if (c != callback)
          c.OnEntityAdded(entity);
    }

    public void RemoveEntity(DiagramEntity entity)
    {
      // TODO: should be performed in a thread-safe manner      
    }

    public void Connect()
    {
      IRemoteModelCallback callback = GetCallback();
      if (!callbacks.Contains(callback))
      {        
        callbacks.Add(callback);
        Console.WriteLine("User connected. Total count: " + callbacks.Count);
      }
    }

    private IRemoteModelCallback GetCallback()
    {
      return OperationContext.Current.GetCallbackChannel<IRemoteModelCallback>();
    }

    #endregion
  }
}

As you can see it is a Singleton service with Multiple ConcurrencyMode. Each connected client will be registered within the subscribers collection (callbacks). Each model change causes all the subscribers to be notified.

In my implementation each client immediately applies changes to the Model and View. Guess the rest of functionality might depend on your ideas.

The source code for the article can be  found here:

Possible issues:

If you encounter exceptions related to connection try changing the ports for your endpoints. On some machines localhost:8080 may be not allowed. Change it to localhost:8000 ("App.config" for server and "DiagramView.cs" for client) or whatever port is available free and doesn’t violate your firewall policy.

Notes: In order to build solution you’ll need the release version of Unity Application Block. Solution is configured to run both service and demo application at a time. Open the binaries folder for demo application and run several instances of application in order to ensure all clients receive notifications and create shapes at a time.

Advertisements

One thought on “Providing simple collaboration support for your diagramming tools with WCF services.

  1. Denis,
     
    Just a note of encouragement. I am new to this stuff and am trying to get up to speed on latest .NET, WPF and WCF simulatenously. I am really impressed by your ideas and work.
     
    Leon.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s