简体   繁体   English

WCF重试代理

[英]WCF Retry Proxy

I'm struggling with trying to find the best way to implement WCF retries. 我正在努力寻找实现WCF重试的最佳方法。 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. 方法#1 :创建实现服务接口的代理。 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. 方法#2 :将MakeWcfCall()(上面)更改为public,并让消费代码直接发送func。

What I don't like about approach #1 is having to update the Proxy class every time the interface changes. 我不喜欢方法#1,每次接口更改时都必须更新Proxy类。

What I don't like about approach #2 is the client having to wrap their call in a func. 方法#2我不喜欢的是客户端必须将其调用包装在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. 希望能帮助到你。

I have done this very type of thing and I solved this problem using .net's RealProxy class. 我已经做了这种类型的事情,我使用.net的RealProxy类解决了这个问题。

Using RealProxy , you can create an actual proxy at runtime using your provided interface. 使用RealProxy ,您可以使用提供的界面在运行时创建实际代理。 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… 从调用代码来看,它就像是在使用IFoo通道,但实际上它是代理的IFoo ,然后你有机会拦截对任何方法,属性,构造函数等的调用......

Deriving from RealProxy , you can then override the Invoke method to intercept calls to the WCF methods and handle CommunicationException, etc. RealProxy派生,然后您可以覆盖Invoke方法来拦截对WCF方法的调用并处理CommunicationException等。

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

Sequence diagram of a call being intercepted by the proxy: 被代理拦截的呼叫的序列图:

在此输入图像描述

You can implement generic proxy for example using Castle. 您可以使用Castle实现通用代理。 There is a good article here http://www.planetgeek.ch/2010/10/13/dynamic-proxy-for-wcf-with-castle-dynamicproxy/ . 这里有一篇很好的文章http://www.planetgeek.ch/2010/10/13/dynamic-proxy-for-wcf-with-castle-dynamicproxy/ This approach will give user object which implements interface and you will have one class responsible for comunication 这种方法将提供实现接口的用户对象,并且您将拥有一个负责通信的类

Do not mess with generated code because, as you mentioned, it will be generated again so any customization will be overridden. 不要乱用生成的代码,因为正如您所提到的,它将再次生成,因此将覆盖任何自定义。

I see two ways: 我看到两种方式:

  1. Write/generate a partial class for each proxy that contains the retry variation. 为包含重试变体的每个代理写入/生成部分类。 This is messy because you will still have to adjust it when the proxy changes 这很麻烦,因为当代理更改时您仍需要调整它

  2. Make a custom version of svcutil that allows you to generate a proxy that derives from a different base class and put the retry code in that base class. 创建一个svcutil的自定义版本,允许您生成从不同基类派生的代理,并将重试代码放在该基类中。 This is quite some work but it can be done and solves the issue in a robust way. 这是相当多的工作,但它可以完成并以健壮的方式解决问题。

go through approach 1, then wrape all context event (open, opened, faulted, ...) into event to be exposed by your class proxy, once the communication is faulted then re-create the proxy or call some recursive method inside proxy class. 通过方法1,然后将所有上下文事件(打开,打开,故障,...)擦除到由类代理公开的事件中,一旦通信出现故障然后重新创建代理或在代理类中调用一些递归方法。 i can share with you some wok i have just tested. 我可以和你分享我刚刚测试过的一些炒锅。

    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
}

Just wrap all service calls in a function, taking a delegate that would execute the passed delegate the amount of time necessary 只需将所有服务调用包装在一个函数中,将一个委托执行传递的委托所需的时间

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM