Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WCF Retry Proxy

Tags:

c#

oop

wcf

I'm struggling with trying to find the best way to implement WCF retries. I'm hoping to make the client experience as clean as possible. There are two approaches of which I'm aware (see below). My question is: "Is there a third approach that I'm missing? Maybe one that's the generally accepted way of doing this?"

Approach #1: Create a proxy that implements the service interface. For each call to the proxy, implement retries.

public class Proxy : ISomeWcfServiceInterface
{
    public int Foo(int snurl)
    {
        return MakeWcfCall<int>(() => _channel.Foo(snurl));
    }

    public string Bar(string snuh)
    {
        return MakeWcfCall<string>(() => _channel.Bar(snuh));
    }

    private static T MakeWcfCall<T>(Func<T> func)
    {
        // Invoke the func and implement retries.
    }
}

Approach #2: Change MakeWcfCall() (above) to public, and have the consuming code send the func directly.

What I don't like about approach #1 is having to update the Proxy class every time the interface changes.

What I don't like about approach #2 is the client having to wrap their call in a func.

Am I missing a better way?

EDIT

I've posted an answer here (see below), based on the accepted answer that pointed me in the right direction. I thought I'd share my code, in an answer, to help someone work through what I had to work through. Hope it helps.

like image 885
Bob Horn Avatar asked Apr 22 '13 15:04

Bob Horn


2 Answers

I have done this very type of thing and I solved this problem using .net's RealProxy class.

Using RealProxy, you can create an actual proxy at runtime using your provided interface. From the calling code it is as if they are using an IFoo channel, but in fact it is a IFoo to the proxy and then you get a chance to intercept the calls to any method, property, constructors, etc…

Deriving from RealProxy, you can then override the Invoke method to intercept calls to the WCF methods and handle CommunicationException, etc.

like image 147
Jim Avatar answered Sep 28 '22 12:09

Jim


Note: This shouldn't be the accepted answer, but I wanted to post the solution in case it helps others. Jim's answer pointed me in this direction.

First, the consuming code, showing how it works:

static void Main(string[] args)
{
    var channelFactory = new WcfChannelFactory<IPrestoService>(new NetTcpBinding());
    var endpointAddress = ConfigurationManager.AppSettings["endpointAddress"];

    // The call to CreateChannel() actually returns a proxy that can intercept calls to the
    // service. This is done so that the proxy can retry on communication failures.            
    IPrestoService prestoService = channelFactory.CreateChannel(new EndpointAddress(endpointAddress));

    Console.WriteLine("Enter some information to echo to the Presto service:");
    string message = Console.ReadLine();

    string returnMessage = prestoService.Echo(message);

    Console.WriteLine("Presto responds: {0}", returnMessage);

    Console.WriteLine("Press any key to stop the program.");
    Console.ReadKey();
}

The WcfChannelFactory:

public class WcfChannelFactory<T> : ChannelFactory<T> where T : class
{
    public WcfChannelFactory(Binding binding) : base(binding) {}

    public T CreateBaseChannel()
    {
        return base.CreateChannel(this.Endpoint.Address, null);
    }

    public override T CreateChannel(EndpointAddress address, Uri via)
    {
        // This is where the magic happens. We don't really return a channel here;
        // we return WcfClientProxy.GetTransparentProxy(). That class will now
        // have the chance to intercept calls to the service.
        this.Endpoint.Address = address;            
        var proxy = new WcfClientProxy<T>(this);
        return proxy.GetTransparentProxy() as T;
    }
}

The WcfClientProxy: (This is where we intercept and retry.)

    public class WcfClientProxy<T> : RealProxy where T : class
    {
        private WcfChannelFactory<T> _channelFactory;

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

        public override IMessage Invoke(IMessage msg)
        {
            // When a service method gets called, we intercept it here and call it below with methodBase.Invoke().

            var methodCall = msg as IMethodCallMessage;
            var methodBase = methodCall.MethodBase;

            // We can't call CreateChannel() because that creates an instance of this class,
            // and we'd end up with a stack overflow. So, call CreateBaseChannel() to get the
            // actual service.
            T wcfService = this._channelFactory.CreateBaseChannel();

            try
            {
                var result = methodBase.Invoke(wcfService, methodCall.Args);

                return new ReturnMessage(
                      result, // Operation result
                      null, // Out arguments
                      0, // Out arguments count
                      methodCall.LogicalCallContext, // Call context
                      methodCall); // Original message
            }
            catch (FaultException)
            {
                // Need to specifically catch and rethrow FaultExceptions to bypass the CommunicationException catch.
                // This is needed to distinguish between Faults and underlying communication exceptions.
                throw;
            }
            catch (CommunicationException ex)
            {
                // Handle CommunicationException and implement retries here.
                throw new NotImplementedException();
            }            
        }
    }

Sequence diagram of a call being intercepted by the proxy:

enter image description here

like image 38
Bob Horn Avatar answered Sep 28 '22 11:09

Bob Horn