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));
}