Communication between WCF services

1.3k Views Asked by At

I'm building a service into an existing application where each service was built with the intention that it would only be consumed by one client and the client and server are setup with duplex communication channels.

Limitations

  1. I don't have the option of re-designing that existing infrastructure
  2. I cannot use a shareable session

Requirement:

  1. I need to be able to communicate between client services (For instance, if a user clicks on an item and wants to share that item, the client, for whatever reason, might not be able to handle the "sharing" feature and needs to pass it on to another client to handle - this must be done through the service)
  2. Communication between clients must be conducted by the service.

To get this working initially, I setup an IPC channel (netNamedPipeBinding) directly between the two clients, but I was told to send everything through the server. The "server" in this scenario, in most cases, is running on the same machine as the client so I came up with this very crude proof of concept attempt (see below code block).

Issue: When a method is invoked for a subscribing service, the operation context for the current service (within which the method is being invoked) is null - this leaves the service without any way to call back to the client

I was considering using Juval Löwy's publish/subscribe framework that he provides in his ServiceModelEx framework, but it seemed unnecessary when all of the clients already have duplex communication setup between themselves and their respective services... so the intent is just to add a simple publishing/subscribing layer that sits conceptually "underneath" those services and can speak to any of them that care to subscribe.

Advice and constructive criticism is welcomed!


    public static class SubscriptionManager<TEventArgs> 
    where TEventArgs : class
{
    private static ConcurrentDictionary<int, Action<TEventArgs>> _subscribers =
       new ConcurrentDictionary<int, Action<TEventArgs>>();

    // sessionId is NOT the WCF SessionId
    public static void FireEvent( int sessionId, TEventArgs eventArgs ) 
    {
        var subscribers = _subscribers.Where( s => s.Key == sessionId );
        var actions = subscribers.Select( keyValuePair => keyValuePair.Value );

        foreach ( var action in actions )
            action.BeginInvoke( eventArgs, action.EndInvoke, null );
    }

    public static void Subscribe(int sessionId, Action<TEventArgs> eventHandler)
    {
        _subscribers.TryAdd(sessionId, eventHandler);
    }

    public static Action<TEventArgs> Unsubscribe(int sessionId)
    {
        Action<TEventArgs> eventHandler;
        _subscribers.TryRemove(sessionId, out eventHandler);
        return eventHandler;
    }
}
1

There are 1 best solutions below

0
On BEST ANSWER

So first off, it seems the pattern I was implementing could be classified as a Mediator Pattern.

So I solved this by passing in the current instance context of the FireEvent calling service which, in my case, should be the same context as the subscribing service.

The case I am handling here is disconnected client applications that are operating in the context of the same user and from the same client machine, but (per requirements) they must communicate through the service layer.

I started going down the road of using the WCF synchronization context and Juval Lowy's ThreadPoolBehavior class from his ServiceModelEx library, but I am tied to an implementation where Ninject WCF Extensions were getting in the way of this.

So this solution will have to be adapted based on your own implementation, but to give you an idea of how I got this to work, here is the gist of my updated FireEvent method:

public static void FireEvent( int sessionId, TData eventData, InstanceContext context )
{
    var subscribers = Subscribers.Where( s => s.Key == sessionId );

    var eventArguments = subscribers.Select( kvp => kvp.Value );

    foreach ( var argument in eventArguments )
    {
        // set data associated with the event
        argument.SetData( eventData );

        NinjectInstanceProvider instanceProvider = null;
        Object instance = null;

        try
        {
            // get a "synchronized instance" of the service of type defined by event args
            instanceProvider = new NinjectInstanceProvider( argument.ServiceType );
            instance = instanceProvider.GetInstance( context );

            // get the method from our "synchronized instance"
            // filter by argument types so we don't run into any issues with ambiguity
            var argumentTypes = new[] { typeof ( TEventArgs ) };
            var method = instance.GetType().GetMethod( argument.Callback, argumentTypes );

            // our method arguments
            var arguments = new object[] { argument };

            // invoke the method on our "synchronized instance"
            method.Invoke( instance, arguments );

            // release the instance
            instanceProvider.ReleaseInstance( context, instance );
        }
        catch
        {
            // log
        }
        finally
        {
            if( provider != null )
            {
                if( instance != null ) { instanceProvider.ReleaseInstance( context, instance ); }
            }
        }
    }
}