Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

The Fancy Proxy - Having fun with WCF - 6-DynamicDuplexProxy

4.33/5 (3 votes)
16 Feb 2010CPOL3 min read 10.9K  
Using Dynamic Proxy & MSMQ Duplex to create a proxy that switches automatically between online & offline network state

A little project I was doing at home lately lead me to search for a solution to disconnected application.

In my imagination, I imagined an application that knows to work both "online" and "offline"...not just that but also knows how to switch between them when needed and of course - controlled from WCF configuration or other infrastructure that will be as "transparent" as possible for the programmer who writes the application.

Sounds a little like science fiction? not quite...

Gathering information from all sorts of good articles & blogs, I've reached a nice project which I've decided to share with you in a kind of tutorial structure to help whoever finds this interesting - step by step.
I know the code could and should pass a bit of polish, but hey! remember it's just an idea not production material :-)

Each step in this "tutorial" represents a step in the way for the full solution, this is only to help understand each concept separately, feel free to jump over a few steps or go directly to the final step...

  1. Simple TCP
  2. Simple MSMQ
  3. Simple Duplex
  4. MSMQ Duplex
  5. Simple Dynamic proxy
  6. Dynamic & Duplex proxy

This is the final step of this tutorial.

Let's review the main modifications from previous steps and test it.

The final step shows a combination of two previously reviewed steps -
TCP-Duplex and MSMQ-Duplex.
The TCP-Duplex will work when the client is connected to the server and the MSMQ-Duplex will work when it's disconnected.
We'll start with the contract, here we can see a regular duplex contract built from two "one-way" interfaces, the 2nd interface is the callback interface of the 1st one.

C#
[ServiceContract(CallbackContract = typeof(ISampleContractCallback))]
public interface ISampleContract
{
    [OperationContract(IsOneWay = true)]
    void GetData(Guid identifier);
    
    [OperationContract(IsOneWay = true)]
    void Execute(Guid identifier);
}

public interface ISampleContractCallback
{
    [OperationContract(IsOneWay = true)]
    void SendData(Guid identifier, string answer);
}

The server implementation of this contract is very simple, the only thing worth mentioning here is the 'GetCallbackChannel' call which allows us to retrieve the client's endpoint & allows the server to send back an 'answer' (see 3- Simple Duplex step for more information on this).

C#
public void Execute(Guid identifier)
{
    Console.WriteLine("{1} received Execute request (id {0})", identifier, DateTime.Now);
}

public void GetData(Guid identifier)
{
    Console.WriteLine("{1} received GetData request (id {0})", identifier, DateTime.Now);
    
    ISampleContractCallback client = OperationContext.Current.GetCallbackChannel<isamplecontractcallback />();
    
    //get data by identifier
    string answer = "hi! from server";
    
    client.SendData(identifier, answer);
}

The host didn't change much from previous steps, the only change is the call for 'AddToExistingServiceHost', this helper method adds the MSMQ-Duplex endpoint to the previously configured TCP-Duplex endpoint.

C#
private static void startListening()
{           
    serviceHost = new ServiceHost(typeof(testFancyProxyServer.testFancyProxyService));
             //msmq duplex server
    DuplexMsmqServices.AddToExistingServiceHost(serviceHost,
                              	typeof(testFancyProxyServer.testFancyProxyService),
                                   	typeof(ISampleContract),
                                   	ConfigurationSettings.AppSettings["MSMQServerURI"]);
                                                        
    // Open the ServiceHostBase to create listeners and start 
    // listening for messages.
    serviceHost.Open();
}

In the client, we will see a proxy very similar to the previous step.
The proxy here uses:

  1. 'NetworkChange.NetworkAvailabilityChanged' to track the network availability.
  2. DynamicTargetProxyInterceptor which wraps "Castle DynamicProxy" 'ChangeInvocationTarget' to transparently switch between the 'online' and 'offline' proxies.
    C#
    /// <summary>
    /// this class intercepts each call to proxy
    /// and check if it needs to switch to secondary target
    /// </summary>
    public class DynamicTargetProxyInterceptor<t> : IInterceptor
    {
        private readonly T _secondaryTarget;
        
        public DynamicTargetProxyInterceptor(T secondaryTarget)
        {
            _secondaryTarget = secondaryTarget;
        }     
        
        public void Intercept(IInvocation invocation)
        {
            var primaryTarget = invocation.InvocationTarget as IProxySelectorDesicion;
            
            if (primaryTarget.IsOnline == false)
            {
                ChangeToSecondaryTarget(invocation);
            }
            invocation.Proceed();
        }
        
        private void ChangeToSecondaryTarget(IInvocation invocation)
        {
            var changeProxyTarget = invocation as IChangeProxyTarget;
            changeProxyTarget.ChangeInvocationTarget(_secondaryTarget);
        }
    }
  3. Nicolas Dorier's MSMQ Duplex to implement the 'offline' proxy - using the same duplex contract over MSMQ on both client & server - allowing a duplex dialog between them.

    Playing a bit with previous dynamic proxy sample I've noticed that when switching from the 'offline' proxy back to previously 'used' TCP proxy, I get a CommunicationException - the solution for this included registering to ICommunicationObject.Faulted event to handle this exception by recreating a new 'online' proxy:

    C#
    void SampleContractProxy_Faulted(object sender, EventArgs e)
    {
        ((ICommunicationObject)sender).Abort();
        
        if (sender is ISampleContract)
        {
            sender = CreateProxy(currentEndPoint);
        } 
    }

    Another modification is the 'OfflineWaitTimeOut' property which allows the proxy's consumer to wait for the MSMQ-Duplex message to arrive getting a sync-like behavior, this way the code's flow is cleaner but it has an obvious cost - the client actually waits for the answer (go figure...:-)).
    Anyway, like in the previous sample, the proxy also contains the 'AnswerArrived' event which will trigger when the server 'answers' immediately if we set the 'OfflineWaitTimeOut' to 0 or when we reach the 'OfflineWaitTimeOut' if set (it can also be set to infinite time out - not really recommended, but the option exists..).

    C#
    public string GetDataSync(Guid identifier)
    {
        Console.WriteLine("enter GetDataSync {0}", DateTime.Now.ToString("hh:MM:ss"));
        GetData(identifier);
        
        wait4Signal();
        
        Console.WriteLine("leave GetDataSync {0}", DateTime.Now.ToString("hh:MM:ss"));
        
        return Answer;
    }
    
    public void SendData(Guid identifier, string answer)
    {
        wait4Event.Set();
        Answer = answer;
        
        //this event can be useful to receive answers
        //in offline mode when time-out is defined
        if (AnswerArrived != null)
        {
            AnswerArrived(this, new AnswerArrivedArgs(identifier, answer));
        }
    }
    
    private void wait4Signal()
    {
        if (wait4Event == null)
        { 
            wait4Event = new ManualResetEvent(false);
        }
        
        wait4Event.WaitOne(offlineWaitTimeOut);
        wait4Event.Reset();
    }

Testing the solution...

Looking at the proxy's consumer code:

  • We create two proxies; one represents the 'online' endpoint & the other one the 'offline' endpoint.
  • We send both to the 'DynamicTargetProxyFactory'.
  • We call the server's methods in a loop while connecting and disconnecting from the network.
C#
public class testFancyProxyConsumer
{
    private const string ONLINE_ENDPOINT = "Online";
    private const string OFFLINE_ENDPOINT = "Offline";
    
    private SampleContractProxy onlineProxy;
    private SampleContractProxy offlineProxy;
    private ISampleContractSync proxy;
    private DynamicTargetProxyFactory<isamplecontractsync> dp;
    
    public void Run()
    {
        onlineProxy = new SampleContractProxy(ONLINE_ENDPOINT, true);
        offlineProxy = new SampleContractProxy(OFFLINE_ENDPOINT, true);
        
        offlineProxy.OfflineWaitTimeOut = 1000;
        offlineProxy.AnswerArrived += 
	new SampleContractProxy.AnswerArrivedHandler(offlineProxy_AnswerArrived);
        
        dp = new DynamicTargetProxyFactory
		<isamplecontractsync>(onlineProxy, offlineProxy);
        
        proxy = dp.GetCurrentTarget();
        
        Guid testGuid;
        
        for (int i = 0; i < 10; i++)
        {
            testGuid = Guid.NewGuid();
            
            proxy.Execute(testGuid);
            
            Console.WriteLine(string.Format("{1} execute {0}", testGuid, DateTime.Now));
            
            Console.WriteLine(string.Format("{3} 
				GetDataSync {0} on '{1}' proxy result:{2}",
                                     testGuid, proxy.CurrentEndPoint,
                                     proxy.GetDataSync(testGuid), DateTime.Now));
           
            Console.ReadLine();
        }
    }
    
    private void offlineProxy_AnswerArrived(object sender, AnswerArrivedArgs args)
    {
        Console.WriteLine("answer finally arrived, identifier={0}, 
			answer={1}", args.Identifier, args.Answer);
    }
}

The result:

The proxy handles everything and switches between the two proxies, the proxy's consumer doesn't need to do anything about this nor does it notice the switches and even more important - all calls reached the server - isn't it a fancy proxy ??! :-)

That's it on this subject.

Feel free to ask or comment...

Diego

License

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