Click here to Skip to main content
15,889,879 members
Articles / Programming Languages / C#

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

Rate me:
Please Sign up or sign in to vote.
4.33/5 (3 votes)
16 Feb 2010CPOL3 min read 10.8K   12   1
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)


Written By
Architect Ness Technologies
Israel Israel

Comments and Discussions

 
QuestionGreat article Pin
axxxxxa2-Nov-11 0:09
axxxxxa2-Nov-11 0:09 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.