-1

I have a C# client implementing System.ServiceModel.ClientBase<TChannel> for communication with a SOAP API.

I want the client to automatically retry requests when timeouts occur.

I figure I could use a library like Polly to create an HttpClient instance with a retry policy.
However, I can't work out how to instantiate the WCF client with that HttpClient.

I'm also not sure if I'm meant to be using a different approach altogether, like creating a IEndpointBehavior.

It looks like there is a CreateChannel instance method that can be overridden and a ConfigureEndpoint static partial method that can be implemented, but I cant find any examples of how to use them correctly to implement retry either by using an instantiated HttpClient or via some other mechanism.

I've searched through the docs for building clients. There are sections on using channel factories, but it also doesn't seem to have a mechanism of supplying an HttpClient.

The docs for expected exceptions specifically mention TimeoutException, but the example just try/catches calls on the client.

I don't really want to do RetryOnTimeout(client => client.MethodToRetryOnTimeout());. I know I can do that, and I know I can create a decorator that wraps all the client calls in similar logic. I just don't think that's the right approach.

Any help pointing me in the right direction would be appreciated.

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
br3nt
  • 9,017
  • 3
  • 42
  • 63
  • Polly's retry is generic enough to be able to decorate any method (not just HttpClient's ones). Do you want to use retry for transient failure? What should be your trigger? – Peter Csala Apr 14 '23 at 14:51
  • Yes, it’s for transient failures. It seems the API I’m calling spins down after a period of inactivity. – br3nt Apr 15 '23 at 00:10

2 Answers2

1

In case of HttpClient the AddPolicyHandler registers a PolicyHttpMessageHandler, which is a DelegatingHandler. It basically overrides the SendAsync:

Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, CancellationToken cancellationToken);

I haven't used WCF for awhile but if my memory serves well then the closest thing to this is the client message interceptor ( IClientMessageInterceptor). Even though it is feasible unfortunately it is not that easy to achieve.

Another alternative could be to use RealProxy. Basically here you can override the Invoke method and decorate the MethodBase's Invoke call with a policy which triggers for TimeoutException and CommunicationException. The problem with this approach is that RealProxy is not available in .NET Core (or its successor).

So, the only option is to wrap yourself the method calls. This can be done

  • either with a generic static method (in your example the RetryOnTimeout)
  • or by creating a custom ClientBase derived class
class ResilientSampleClient: ClientBase<ISampleClient>, ISampleClient
{
    private readonly IRetryPolicy retryPolicy = Policy
            .Handle<TimeoutException>()
            .Or<CommunicationException>)()
            .WaitAndRetry(3, _ => TimeSpan.FromSeconds(1));

    public ResilientSampleClient(): base(binding, endpointAddress) {}

    public void MethodToRetryOnTimeout()     
       => retryPolicy.Execute(base.Channel.MethodToRetryOnTimeout);     
}

UPDATE #1

After spending a bit more time on RealProxy I've bumped into this migration article. It suggests to use DispatchProxy instead.

I haven't tested this code, but based on the migration doc the above RealProxy based solution could be rewritten like this:

public class WcfClientProxy<T> : DispatchProxy where T : class
{
    private readonly IRetryPolicy retryPolicy = Policy
            .Handle<TimeoutException>()
            .Or<CommunicationException>)()
            .WaitAndRetry(3, _ => TimeSpan.FromSeconds(1));
            
    private readonly WcfChannelFactory<T> _channelFactory;

    public WcfClientProxy(WcfChannelFactory<T> channelFactory) : base()
    {
        this._channelFactory = channelFactory;
    }

    public override object Invoke(MethodInfo targetMethod, object[] args)
    {
        return retryPolicy.Execute(() => targetMethod.Invoke(this._channelFactory.CreateBaseChannel(), args));
    }
}
Peter Csala
  • 17,736
  • 16
  • 35
  • 75
  • 1
    Thanks for your reply. I'm surprised I didn't come across that other answer. Yes, I see what you mean that it doesn't look easy to achieve. My only concern with using retries using wrapping, is that devs need to remember to implement that pattern as new endpoints are used. Being able to rely on configuration would be preferable, but getting the immediate job done, this would be faster and easier. – br3nt Apr 18 '23 at 23:26
  • @br3nt I've updated my post with a `DispatchProxy`-based solution which is the successor of the `RealProxy`. – Peter Csala Apr 19 '23 at 10:34
-1

You can refer to this document for the instance wcf client. It goes through the details of creating a client.

Using polly Library to create an http client refer to this article, which goes through the details of creating an instance in c# and lists the steps to create a client to see if it helps you.

Jiayao
  • 510
  • 3
  • 7
  • 1
    This doesn't answer the question. My question is: How do I get the WCF client to use a provided HttpClient instance, or more generally, what is the "correct way" to implement retry logic for WCF clients. – br3nt Apr 14 '23 at 01:38