17

我正在努力寻找实现 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()(上)更改为 public,并让消费代码直接发送 func。

我不喜欢方法#1 的地方是每次接口更改时都必须更新代理类。

我不喜欢方法 #2 的地方是客户端必须将他们的调用包装在一个 func 中。

我错过了更好的方法吗?

编辑

我在此处发布了一个答案(见下文),基于已接受的答案,该答案为我指明了正确的方向。我想我会在答案中分享我的代码,以帮助某人完成我必须完成的工作。希望能帮助到你。

4

6 回答 6

17

我已经完成了这种类型的事情,并使用.net 的RealProxy类解决了这个问题。

使用RealProxy,您可以使用您提供的接口在运行时创建一个实际的代理。从调用代码来看,他们好像在使用一个IFoo通道,但实际上它是一个IFoo代理,然后你有机会拦截对任何方法、属性、构造函数等的调用……</p>

派生自RealProxy,然后可以重写 Invoke 方法以拦截对 WCF 方法的调用并处理 CommunicationException 等。

于 2013-04-22T16:58:50.420 回答
16

注意:这不应该是公认的答案,但我想发布解决方案以防它帮助其他人。吉姆的回答为我指明了这个方向。

首先,消费代码,展示它是如何工作的:

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

被代理拦截的调用时序图:

在此处输入图像描述

于 2013-04-22T20:12:35.837 回答
5

例如,您可以使用 Castle 实现通用代理。这里有一篇很好的文章http://www.planetgeek.ch/2010/10/13/dynamic-proxy-for-wcf-with-castle-dynamicproxy/。这种方法将提供实现接口的用户对象,您将拥有一个负责通信的类

于 2013-04-22T16:21:14.333 回答
0

只需将所有服务调用包装在一个函数中,获取一个委托,该委托将在必要的时间内执行传递的委托

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);
}
于 2013-04-22T16:24:11.737 回答
0

不要弄乱生成的代码,因为正如您所提到的,它将再次生成,因此任何自定义都将被覆盖。

我看到两种方法:

  1. 为每个包含重试变体的代理编写/生成部分类。这很混乱,因为当代理更改时您仍然需要对其进行调整

  2. 制作一个自定义版本的 svcutil,它允许您生成从不同基类派生的代理,并将重试代码放在该基类中。这是一项相当多的工作,但可以完成并以稳健的方式解决问题。

于 2013-04-22T16:12:30.083 回答
0

通过方法 1,然后将所有上下文事件(打开、打开、故障……)包装到要由您的类代理公开的事件中,一旦通信发生故障,然后重新创建代理或调用代理类中的某些递归方法. 我可以和你分享一些我刚刚测试过的炒锅。

    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
}
于 2013-04-22T16:21:40.190 回答