Submit to Reddit      
  

ChannelAdam WCF Library — Version 1 Documentation

Retry for Transient Fault Handling

Overview

When you call your web services, do you need to handle transient errors that may occur (such as the network briefly dropping out), and have some automatic retry logic?

The ChannelAdam WCF Library has some built-in support for the Microsoft Practices Transient Fault Handling Library - but in Version 1, only when you use the Consume method and not when you use the Operations property. (Note: this has changed in Version 2).

Different Ways To Retry

Below is a description about different ways that you could implement retry functionality:

  1. Naively - all custom code
  2. With the Microsoft Practices Transient Fault Handling Library; or
  3. More cleanly with the Consume Method

1. Naive Retry Logic with the Operations Property

Below is an example naive implementation of retry logic (without the help of any library) while using the Operations property.

int retryCount = 1;
Exception lastException = null;

using (var service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient()))
{
    while (retryCount > 0)
    {
        Console.WriteLine("#### Retry countdown: " + retryCount);

        try
        {
            int actual = service.Operations.AddIntegers(1, 1);
            break;
        }
        catch (FaultException fe)
        {
            lastException = fe;
            Console.WriteLine("Service operation threw a fault: " + fe.ToString());
        }
        catch (Exception ex)
        {
            lastException = ex;
            Console.WriteLine("Technical error occurred while calling the service operation: " + ex.ToString());
        }

        retryCount--;
    }
}

if (lastException != null)
{
    ...    
} 

Unfortunately that code is lengthy and error prone.

2. Retry Logic with Operations Property and the Microsoft Library

The code above can be improved upon by using the Microsoft Practices Transient Fault Handling Library, as below.

You will notice in this example, I am using the ChannelAdam.ServiceModel.SoapFaultWebServiceTransientErrorDetectionStrategy to determine if there was a transient error or not. If the given exception was a FaultException, this strategy says that was not a transient error and therefore do not retry. If on the other hand any other type of exception occurs, then this strategy will tell the Microsoft library to retry according to the retry policy.

using Microsoft.Practices.TransientFaultHandling;
...

using (var service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient()))
{
    int actual = 0;
    var retryStrategy = new Incremental(5, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(2));
    var retryPolicy = new RetryPolicy<SoapFaultWebServiceTransientErrorDetectionStrategy>(retryStrategy);

    try
    {
        retryPolicy.ExecuteAction(() =>
        {
            actual = service.Operations.AddIntegers(1, 1);
        });
    }
    catch (FaultException fe)
    {
        Console.WriteLine("Service operation threw a fault: " + fe.ToString());
    }
    catch (Exception ex)
    {
        Console.WriteLine("Technical error occurred while calling the service operation: " + ex.ToString());
    }
}

3. Retry Logic with the Consume Method

For me, both code examples above that use the Operations property is clunky - which is why I prefer using the Consume method!

When using the Consume method, there are three ways to set the retry policy:

  1. Setting the static property DefaultRetryPolicy on the ServiceConsumerFactory - which will apply to all created instances;
  2. As an overload to the Create method on the ServiceConsumerFactory - which will apply just to that ServiceConsumer instance to be created; or
  3. As one of the overloads on the Consume method itself.

For example, the overload on the Consume method:

using Microsoft.Practices.TransientFaultHandling;
...

var retryStrategy = new Incremental(5, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(2));
var retryPolicy = new RetryPolicy<SoapFaultWebServiceTransientErrorDetectionStrategy>(retryStrategy);

using (var service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient()))
{
    var result = service.Consume(operation => operation.AddIntegers(1, 1), retryPolicy);

    if (result.HasNoException)
    {
        Console.WriteLine("Actual: " + result.Value);
        ...
    }
    else
    {
        if (result.HasFaultException)
        {
            Console.WriteLine("Service operation threw a fault: " + result.Exception.ToString());
        }
        else if (result.HasException)
        {
            Console.WriteLine("An unexpected error occurred while calling the service operation: " + result.Exception.ToString());
        }
    }
}

Perhaps you too may agree that using the retry strategy with the Consume method is simpler and cleaner, in comparison with the Operations property.

Note: The retry functionality has changed significantly in Version 2.



comments powered by Disqus