Как изменить «цель» для всех вызовов в DynamicProxy? - PullRequest
0 голосов
/ 31 марта 2020

В основном я пытаюсь реализовать отказоустойчивость соединения для клиентов WCF.

Я создал свой собственный MyChannelFactory, который упаковывает WCF ChannelFactory для работы с временными ошибками, то есть, когда служба WCF закрыта и через пару секунд снова работает. В этом случае при использовании NetNamedPipeBinding все клиентские экземпляры, созданные с помощью var instance = channelFactory.CreateChannel(), разрушаются, потому что он больше не работает, вам нужно снова явно создать канал, вызвав var newInstance = channelFactory.CreateChannel() и использовать newInstance. Одним из способов решения этой проблемы является создание оболочки интерфейса службы и в каждом методе добавьте try-catch(CommunicationException), создайте другой канал и попробуйте снова вызвать метод. Как видите, обязательно для создания другого экземпляра и использования этого нового экземпляра во всех методах; предыдущий экземпляр больше не подходит.

Итак, чтобы придерживаться лучших практик DynamicProxy и ограничений WCF, я успешно создал прокси (код ниже), и он работает ( производство еще не готово) но, как вы видите, я никогда не звоню invocation.Proceed(), вместо этого я звоню invocation.Method.Invoke(newinstance,invocation.Arguments); Я не думаю, что это правильно, потому что, если мы добавим больше перехватчиков, это просто не будет работать. Я думаю, интерфейсный прокси с целевым интерфейсом - это путь к go, однако, как указано в руководстве , вы можете изменить цель для этого вызова, а не для всего прокси - и как я упоминал выше, необходимо изменить экземпляр канала (предыдущий сейчас бесполезен).

class MyChannelFactory<TChannel> where TChannel : class
    {
        private readonly ProxyGenerator generator;
        private readonly ProxyGenerationOptions options;
        private readonly ChannelFactory<TChannel> factory;
        private readonly EndpointAddress address;

        public MyChannelFactory(Binding binding, EndpointAddress address)
        {
            this.address = address;
            factory = new ChannelFactory<TChannel>(binding);
            factory.ApplyClientBehaviors();
            options = new ProxyGenerationOptions { Selector = new MySelector() };
            generator = new ProxyGenerator();
        }

        public TChannel CreateChannel()
        {
            return generator.CreateInterfaceProxyWithoutTarget<TChannel>(new MyInterceptor(factory, address));
        }

        class MyInterceptor : IInterceptor
        {
            private const int DefaultMaxRetryCount = 5;
            private const double DefaultRandomFactor = 1.1;
            private const double DefaultExponentialBase = 2;
            private static readonly TimeSpan DefaultCoefficient = TimeSpan.FromSeconds(1);
            private static readonly TimeSpan DefaultMaxDelay = TimeSpan.FromSeconds(30);

            private readonly int maxRetryCount;
            private readonly TimeSpan maxDelay;
            private readonly Random random = new Random();
            private readonly IChannelFactory<TChannel> factory;
            private readonly EndpointAddress address;
            private TChannel currentChannel;

            public MyInterceptor(IChannelFactory<TChannel> factory, EndpointAddress address)
                : this(DefaultMaxRetryCount, DefaultMaxDelay, factory, address) { }

            public MyInterceptor(int maxRetryCount, TimeSpan maxDelay, IChannelFactory<TChannel> factory, EndpointAddress address)
            {
                this.maxRetryCount = maxRetryCount;
                this.maxDelay = maxDelay;
                this.factory = factory;
                this.address = address;
            }

            void IInterceptor.Intercept(IInvocation invocation)
            {
                using (var timer = TimeExecution.Start())
                {
                    var exceptions = new List<Exception>();
                    TimeSpan? delay;
                    while (true)
                    {
                        try
                        {
                            if (currentChannel == null)
                                currentChannel = factory.CreateChannel(address);

                            //invocation.Proceed();
                            invocation.Method.Invoke(currentChannel, invocation.Arguments);
                            break;
                        }
                        catch (TargetInvocationException ex) when (ex.InnerException is CommunicationException) //TODO: is pipe closed (only transient exceptions)
                        {
                            currentChannel = null;
                            exceptions.Add(ex.InnerException);
                            delay = GetNextDelay(exceptions);
                            if (delay == null)
                                throw new Exception($"Max retry of {maxRetryCount} exceeded in {timer.Elapsed}", ex.InnerException);
                        }
                        //catch (CommunicationException ex) 
                        //{
                        //    currentChannel = null;
                        //    exceptions.Add(ex);
                        //    delay = GetNextDelay(exceptions);
                        //    if (delay == null)
                        //        throw new Exception($"Max retry of {maxRetryCount} exceeded in {timer.Elapsed}", ex);
                        //}

                        Thread.Sleep(delay.Value);
                    }
                }
            }

            private TimeSpan? GetNextDelay(IEnumerable<Exception> exceptions)
            {
                var currentRetryCount = exceptions.Count() - 1;
                if (currentRetryCount < maxRetryCount)
                {
                    var delta = (Math.Pow(DefaultExponentialBase, currentRetryCount) - 1.0)
                        * (1.0 + random.NextDouble() * (DefaultRandomFactor - 1.0));

                    var delay = Math.Min(DefaultCoefficient.TotalMilliseconds * delta, maxDelay.TotalMilliseconds);

                    return TimeSpan.FromMilliseconds(delay);
                }

                return null;
            }
        }

        class TimeExecution : IDisposable
        {
            private readonly Stopwatch stopwatch;
            private readonly bool enabled;
            private readonly string message;

            public TimeSpan Elapsed { get { return stopwatch.Elapsed; } }
            public bool IsRunning { get { return stopwatch.IsRunning; } }

            private TimeExecution()
            {
                stopwatch = new Stopwatch();
                stopwatch.Start();
            }

            public void Dispose()
            {
                stopwatch.Stop();
            }

            public static TimeExecution Start()
            {
                return new TimeExecution();
            }
        }

        class MySelector : IInterceptorSelector
        {

            public MySelector()
            {

            }

            IInterceptor[] IInterceptorSelector.SelectInterceptors(Type type, MethodInfo method, IInterceptor[] interceptors)
            {
                throw new NotImplementedException();
            }
        }
    }

Есть ли способ изменить цель для всего прокси? Или другой дизайн, который мне не хватает?

...