How to implement a generic retry mechanism for a web service proxy?

For a customer I work for I had the need to implement a web service proxy that can do an automatic retry for me when I call a web service. I have a system that has web services in the local environment, but in the case the web service is not responding I have to retry the call on another server at the other end of the country cross a slow link. I did not want to implement the logic of switching the server in all subsequent client invocations so there my quest started for a generic implementation. When investigating the proxy generated by Visual Studio I discovered that the generated class unfortunately does not provide virtual methods for the method invocations. This means I could not work my way around this using a simple inheritance solution. (Implement my own invoke and do the retry there in case of an exception)


 


But fortunately I found that the generated proxy class is derived from SoapHttpClientProtocol and that class on his turn inherits from MarshalByRefObject. In the past I have done some interception on MarshalByRefObjects and that is exactly the way I could implement this as well.


 


I created a AutoRetryProxy that derives from RealProxy. The real proxy has a method called Invoke that will pass on all methods calls. This invoke method is the place where I could do my implementation of the retry. See the code sample below.


 


public class AutoRetryProxy :RealProxy


{


  private const string normalEndpoint = “http://localhost/webservice1/service1.asmx”;


  private const string alternativeEndpoint = “http://localhost/webservice1/service2.asmx”;


 


  HttpWebClientProtocol target;


 


  public AutoRetryProxy (HttpWebClientProtocol targetObj) : base(targetObj.GetType())


  {


   target=targetObj;


  }


 


  public override IMessage Invoke(IMessage message)


  {


   // just let the remoting infrastructure handle the call


   IMethodReturnMessage returnMessage =


                    RemotingServices.ExecuteMessage(target, (IMethodCallMessage) message);


 


   // check if we got an exception that we need to handle. These are WebExceptions


   // that indicate connectivity problems


   if((returnMessage.Exception != null) &&


                                  (returnMessage.Exception is System.Net.WebException))


   {


     System.Net.WebException ex = (System.Net.WebException )returnMessage.Exception;


     if((ex.Status == WebExceptionStatus.ConnectFailure) ||


                                               (ex.Status == WebExceptionStatus.Timeout))


       {


         // Do some nifty alternative service discovery here 🙂


         target.Url = alternativeEndpoint;


        // now retry the call on the alternative server


         returnMessage =


                    RemotingServices.ExecuteMessage(target, (IMethodCallMessage) message);               


       }


     }


     return returnMessage;


   }


 


   public static object CreateProxy(Type generatedWebserviceProxyType)


   {


     if(generatedWebserviceProxyType.IsSubclassOf(typeof(SoapHttpClientProtocol)))


     {


       SoapHttpClientProtocol webserviceProxy =


         (SoapHttpClientProtocol)Activator.CreateInstance(generatedWebserviceProxyType);


       // use the default endpoint


       webserviceProxy.Url = normalEndpoint;


       return new AutoRetryProxy(webserviceProxy).GetTransparentProxy();


     }


     else


       throw new ArgumentException(“The type must be a derived from SoapHttpClientProtocol”);


   }


  }


 


At the client side I only need to create the instance of the web service proxy generated by Visual Studio using the static Method I created on the AutoRetryProxy proxy.  You can see the client only needs to do a different create then the new operator, but this saves a lot of other code so that works fine for me. See the code below on how the client works with the proxy.


 


private void button1_Click(object sender, System.EventArgs e)


{


  webservice.Timeout = 5000;


  label1.Text =  webservice.HelloWorld();     


}


 


private void Form1_Load(object sender, System.EventArgs e)


{


  webservice = (Service1)AutoRetryWSProxy.AutoRetryProxy.CreateProxy(typeof(Service1));


}