Подтвердить что ты не робот

Прокси-сервер WCF

Я изо всех сил пытаюсь найти лучший способ реализовать повторения WCF. Я надеюсь, что клиент будет максимально чистым. Есть два подхода, о которых я знаю (см. Ниже). Мой вопрос: " Есть ли третий подход, который мне не хватает? Возможно, это общепринятый способ сделать это?"

Подход # 1. Создайте прокси-сервер, который реализует интерфейс службы. Для каждого вызова прокси выполните повторные попытки.

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.
    }
}

Подход # 2: измените MakeWcfCall() (выше) на общедоступный, и код потребления отправит func напрямую.

То, что мне не нравится в подходе №1, необходимо обновлять класс Proxy каждый раз, когда изменяется интерфейс.

То, что мне не нравится в подходе № 2, заключается в том, что клиент должен завернуть свой вызов в func.

Я пропустил лучший способ?

ИЗМЕНИТЬ

Я разместил здесь ответ (см. ниже) на основе принятого ответа, который указал мне в правильном направлении. Я подумал, что поделился бы своим кодом в ответ, чтобы помочь кому-то работать через то, что мне пришлось проработать. Надеюсь, что это поможет.

4b9b3361

Ответ 1

Я сделал это очень похоже, и я решил эту проблему, используя класс .net RealProxy.

Используя RealProxy, вы можете создать фактический прокси-сервер во время выполнения, используя предоставленный интерфейс. Из вызывающего кода он как будто использует канал IFoo, но на самом деле это прокси-сервер IFoo, а затем вы получаете возможность перехватывать вызовы любому методу, свойству, конструкторам и т.д....

Производя из RealProxy, вы можете переопределить метод Invoke для перехвата вызовов в WCF-методах и обработать CommunicationException и т.д.

Ответ 2

Примечание. Это не должно быть принятым ответом, но я хотел опубликовать решение, если оно помогает другим. Джим ответил мне в этом направлении.

Во-первых, код потребления, показывающий, как он работает:

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

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

WcfClientProxy: (Здесь мы перехватываем и повторяем.)

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

Схема последовательности перехвата вызова прокси:

enter image description here

Ответ 3

Вы можете реализовать общий прокси, например, с помощью Castle. Здесь есть хорошая статья http://www.planetgeek.ch/2010/10/13/dynamic-proxy-for-wcf-with-castle-dynamicproxy/. Этот подход даст объект пользователя, который реализует интерфейс, и у вас будет один класс, ответственный за общение

Ответ 4

Не путайте с сгенерированным кодом, потому что, как вы упомянули, он будет сгенерирован снова, поэтому любая настройка будет переопределена.

Я вижу два пути:

  • Запишите/создайте частичный класс для каждого прокси-сервера, который содержит изменение повтора. Это грязно, потому что вам все равно придется его корректировать, когда изменяется прокси-сервер.

  • Создайте пользовательскую версию svcutil, которая позволяет вам генерировать прокси-сервер, который происходит из другого базового класса и помещает код повтора в этот базовый класс. Это довольно много работы, но это можно сделать и решительно решить проблему.

Ответ 5

перейдите к подходу 1, затем очистите все события контекста (открытые, открытые, сбойные,...) в событии, которое будет отображаться вашим прокси-сервером класса, после того, как связь будет сбой, затем заново создайте прокси-сервер или вызовите некоторый рекурсивный метод внутри класса прокси. я могу поделиться с вами некоторыми wok, которые я только что проверил.

    public class DuplexCallBackNotificationIntegrationExtension : IExtension, INotificationPusherCallback {
    #region - Field(s) -
    private static Timer _Timer = null;
    private static readonly object m_SyncRoot = new Object();
    private static readonly Guid CMESchedulerApplicationID = Guid.NewGuid();
    private static CancellationTokenSource cTokenSource = new CancellationTokenSource();
    private static CancellationToken cToken = cTokenSource.Token;
    #endregion

    #region - Event(s) -
    /// <summary>
    /// Event fired during Duplex callback.
    /// </summary>
    public static event EventHandler<CallBackEventArgs> CallBackEvent;

    public static event EventHandler<System.EventArgs> InstanceContextOpeningEvent;
    public static event EventHandler<System.EventArgs> InstanceContextOpenedEvent;
    public static event EventHandler<System.EventArgs> InstanceContextClosingEvent;
    public static event EventHandler<System.EventArgs> InstanceContextClosedEvent;
    public static event EventHandler<System.EventArgs> InstanceContextFaultedEvent;
    #endregion

    #region - Property(ies) -
    /// <summary>
    /// Interface extension designation.
    /// </summary>
    public string Name {
        get {
            return "Duplex Call Back Notification Integration Extension.";
        }
    }

    /// <summary>
    /// GUI Interface extension.
    /// </summary>
    public IUIExtension UIExtension {
        get {
            return null;
        }
    }
    #endregion

    #region - Constructor(s) / Finalizer(s) -
    /// <summary>
    /// Initializes a new instance of the DuplexCallBackNotificationIntegrationExtension class.
    /// </summary>
    public DuplexCallBackNotificationIntegrationExtension() {
        CallDuplexNotificationPusher();
    }
    #endregion

    #region - Delegate Invoker(s) -

    void ICommunicationObject_Opening(object sender, System.EventArgs e) {
        DefaultLogger.DUPLEXLogger.Info("context_Opening");
        this.OnInstanceContextOpening(e);
    }

    void ICommunicationObject_Opened(object sender, System.EventArgs e) {
        DefaultLogger.DUPLEXLogger.Debug("context_Opened");
        this.OnInstanceContextOpened(e);
    }

    void ICommunicationObject_Closing(object sender, System.EventArgs e) {
        DefaultLogger.DUPLEXLogger.Debug("context_Closing");
        this.OnInstanceContextClosing(e);
    }

    void ICommunicationObject_Closed(object sender, System.EventArgs e) {
        DefaultLogger.DUPLEXLogger.Debug("context_Closed");
        this.OnInstanceContextClosed(e);
    }

    void ICommunicationObject_Faulted(object sender, System.EventArgs e) {
        DefaultLogger.DUPLEXLogger.Error("context_Faulted");
        this.OnInstanceContextFaulted(e);

        if (_Timer != null) {
            _Timer.Dispose();
        }

        IChannel channel = sender as IChannel;
        if (channel != null) {
            channel.Abort();
            channel.Close();
        }

        DoWorkRoutine(cToken);
    }

    protected virtual void OnCallBackEvent(Notification objNotification) {
        if (CallBackEvent != null) {
            CallBackEvent(this, new CallBackEventArgs(objNotification));
        }
    }

    protected virtual void OnInstanceContextOpening(System.EventArgs e) {
        if (InstanceContextOpeningEvent != null) {
            InstanceContextOpeningEvent(this, e);
        }
    }

    protected virtual void OnInstanceContextOpened(System.EventArgs e) {
        if (InstanceContextOpenedEvent != null) {
            InstanceContextOpenedEvent(this, e);
        }
    }

    protected virtual void OnInstanceContextClosing(System.EventArgs e) {
        if (InstanceContextClosingEvent != null) {
            InstanceContextClosingEvent(this, e);
        }
    }

    protected virtual void OnInstanceContextClosed(System.EventArgs e) {
        if (InstanceContextClosedEvent != null) {
            InstanceContextClosedEvent(this, e);
        }
    }

    protected virtual void OnInstanceContextFaulted(System.EventArgs e) {
        if (InstanceContextFaultedEvent != null) {
            InstanceContextFaultedEvent(this, e);
        }
    }
    #endregion

    #region - IDisposable Member(s) -

    #endregion

    #region - Private Method(s) -

    /// <summary>
    /// 
    /// </summary>
    void CallDuplexNotificationPusher() {
        var routine = Task.Factory.StartNew(() => DoWorkRoutine(cToken), cToken);
        cToken.Register(() => cancelNotification());
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="ct"></param>
    void DoWorkRoutine(CancellationToken ct) {
        lock (m_SyncRoot) {
            var context = new InstanceContext(this);
            var proxy = new NotificationPusherClient(context);
            ICommunicationObject communicationObject = proxy as ICommunicationObject;
            communicationObject.Opening += new System.EventHandler(ICommunicationObject_Opening);
            communicationObject.Opened += new System.EventHandler(ICommunicationObject_Opened);
            communicationObject.Faulted += new System.EventHandler(ICommunicationObject_Faulted);
            communicationObject.Closed += new System.EventHandler(ICommunicationObject_Closed);
            communicationObject.Closing += new System.EventHandler(ICommunicationObject_Closing);


            try {
                proxy.Subscribe(CMESchedulerApplicationID.ToString());
            }
            catch (Exception ex) {
                Logger.HELogger.DefaultLogger.DUPLEXLogger.Error(ex);                    

                switch (communicationObject.State) {
                    case CommunicationState.Faulted:
                        proxy.Close();
                        break;

                    default:
                        break;
                }

                cTokenSource.Cancel();
                cTokenSource.Dispose();                    
                cTokenSource = new CancellationTokenSource();
                cToken = cTokenSource.Token;
                CallDuplexNotificationPusher();  
            }

            bool KeepAliveCallBackEnabled = Properties.Settings.Default.KeepAliveCallBackEnabled;
            if (KeepAliveCallBackEnabled) {
                _Timer = new Timer(new TimerCallback(delegate(object item) {
                    DefaultLogger.DUPLEXLogger.Debug(string.Format("._._._._._. New Iteration {0: yyyy MM dd hh mm ss ffff} ._._._._._.", DateTime.Now.ToUniversalTime().ToString()));
                    DBNotificationPusherService.Acknowledgment reply = DBNotificationPusherService.Acknowledgment.NAK;
                    try {
                        reply = proxy.KeepAlive();
                    }
                    catch (Exception ex) {
                        DefaultLogger.DUPLEXLogger.Error(ex);

                        switch (communicationObject.State) {
                            case CommunicationState.Faulted:
                            case CommunicationState.Closed:
                                proxy.Abort();
                                ICommunicationObject_Faulted(null, null);
                                break;

                            default:
                                break;
                        }
                    }

                    DefaultLogger.DUPLEXLogger.Debug(string.Format("Acknowledgment = {0}.", reply.ToString()));
                    _Timer.Change(Properties.Settings.Default.KeepAliveCallBackTimerInterval, Timeout.Infinite);
                }), null, Properties.Settings.Default.KeepAliveCallBackTimerInterval, Timeout.Infinite);
            }
        }
    }

    /// <summary>
    /// 
    /// </summary>
    void cancelNotification() {
       DefaultLogger.DUPLEXLogger.Warn("Cancellation request made!!");
    }
    #endregion 

    #region - Public Method(s) -
    /// <summary>
    /// Fire OnCallBackEvent event and fill automatic-recording collection with newest 
    /// </summary>
    /// <param name="action"></param>
    public void SendNotification(Notification objNotification) {

        // Fire event callback.
        OnCallBackEvent(objNotification);
    }
    #endregion

    #region - Callback(s) -
    private void OnAsyncExecutionComplete(IAsyncResult result) {

    }
    #endregion
}

Ответ 6

Просто оберните все вызовы службы в функцию, взяв делегат, который выполнил бы переданный делегат, необходимое количество времени

internal R ExecuteServiceMethod<I, R>(Func<I, R> serviceCall, string userName, string password) {

    //Note all clients have the name Manager, but this isn't a problem as they get resolved        
    //by type
    ChannelFactory<I> factory = new ChannelFactory<I>("Manager");
    factory.Credentials.UserName.UserName = userName;
    factory.Credentials.UserName.Password = password;

    I manager = factory.CreateChannel();
    //Wrap below in a retry loop
    return serviceCall.Invoke(manager);
}