What is the best workaround for the WCF client `using` block issue?

前端 未结 26 1601
余生分开走
余生分开走 2020-11-22 00:03

I like instantiating my WCF service clients within a using block as it\'s pretty much the standard way to use resources that implement IDisposable:

相关标签:
26条回答
  • 2020-11-22 00:32

    What is this?

    This is the CW version of the accepted answer but with (what I consider complete) Exception handling included.

    The accepted answer references this website that is no longer around. To save you trouble, I am including the most relevant parts here. In addition, I modified it slightly to include exception retry handling to handle those pesky network timeouts.

    Simple WCF Client Usage

    Once you generate your client side proxy, this is all you need to implement it.

    Service<IOrderService>.Use(orderService=>
    {
      orderService.PlaceOrder(request);
    });
    

    ServiceDelegate.cs

    Add this file to your solution. No changes are needed to this file, unless you want to alter the number of retries or what exceptions you want to handle.

    public delegate void UseServiceDelegate<T>(T proxy);
    
    public static class Service<T>
    {
        public static ChannelFactory<T> _channelFactory = new ChannelFactory<T>(""); 
    
        public static void Use(UseServiceDelegate<T> codeBlock)
        {
            IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel();
            bool success = false;
    
    
           Exception mostRecentEx = null;
           int millsecondsToSleep = 1000;
    
           for(int i=0; i<5; i++)  // Attempt a maximum of 5 times 
           {
               try
               {
                   codeBlock((T)proxy);
                   proxy.Close();
                   success = true; 
                   break;
               }
    
               // The following is typically thrown on the client when a channel is terminated due to the server closing the connection.
               catch (ChannelTerminatedException cte)
               {
                  mostRecentEx = cte;
                   proxy.Abort();
                   //  delay (backoff) and retry 
                   Thread.Sleep(millsecondsToSleep  * (i + 1)); 
               }
    
               // The following is thrown when a remote endpoint could not be found or reached.  The endpoint may not be found or 
               // reachable because the remote endpoint is down, the remote endpoint is unreachable, or because the remote network is unreachable.
               catch (EndpointNotFoundException enfe)
               {
                  mostRecentEx = enfe;
                   proxy.Abort();
                   //  delay (backoff) and retry 
                   Thread.Sleep(millsecondsToSleep * (i + 1)); 
               }
    
               // The following exception that is thrown when a server is too busy to accept a message.
               catch (ServerTooBusyException stbe)
               {
                  mostRecentEx = stbe;
                   proxy.Abort();
    
                   //  delay (backoff) and retry 
                   Thread.Sleep(millsecondsToSleep * (i + 1)); 
               }
               catch (TimeoutException timeoutEx)
               {
                   mostRecentEx = timeoutEx;
                   proxy.Abort();
    
                   //  delay (backoff) and retry 
                   Thread.Sleep(millsecondsToSleep * (i + 1)); 
               } 
               catch (CommunicationException comException)
               {
                   mostRecentEx = comException;
                   proxy.Abort();
    
                   //  delay (backoff) and retry 
                   Thread.Sleep(millsecondsToSleep * (i + 1)); 
               }
               catch(Exception )
               {
                    // rethrow any other exception not defined here
                    // You may want to define a custom Exception class to pass information such as failure count, and failure type
                    proxy.Abort();
                    throw ;  
               }
           }
           if (success == false && mostRecentEx != null) 
           { 
               proxy.Abort();
               throw new Exception("WCF call failed after 5 retries.", mostRecentEx );
           }
    
        }
    }
    

    PS: I've made this post a community wiki. I won't collect "points" from this answer, but prefer you upvote it if you agree with the implementation, or edit it to make it better.

    0 讨论(0)
  • 2020-11-22 00:32

    I referred few answers on this post and customized it as per my needs.

    I wanted the ability to do something with WCF client before using it so the DoSomethingWithClient() method.

    public interface IServiceClientFactory<T>
    {
        T DoSomethingWithClient();
    }
    public partial class ServiceClient : IServiceClientFactory<ServiceClient>
    {
        public ServiceClient DoSomethingWithClient()
        {
            var client = this;
            // do somthing here as set client credentials, etc.
            //client.ClientCredentials = ... ;
            return client;
        }
    }
    

    Here is the helper class:

    public static class Service<TClient>
        where TClient : class, ICommunicationObject, IServiceClientFactory<TClient>, new()
    {
        public static TReturn Use<TReturn>(Func<TClient, TReturn> codeBlock)
        {
            TClient client = default(TClient);
            bool success = false;
            try
            {
                client = new TClient().DoSomethingWithClient();
                TReturn result = codeBlock(client);
                client.Close();
                success = true;
                return result;
            }
            finally
            {
                if (!success && client != null)
                {
                    client.Abort();
                }
            }
        }
    }
    

    And I can use it as:

    string data = Service<ServiceClient>.Use(x => x.GetData(7));
    
    0 讨论(0)
  • 2020-11-22 00:34

    Given a choice between the solution advocated by IServiceOriented.com and the solution advocated by David Barret's blog, I prefer the simplicity offered by overriding the client's Dispose() method. This allows me to continue to use the using() statement as one would expect with a disposable object. However, as @Brian pointed out, this solution contains a race condition in that the State might not be faulted when it is checked but could be by the time Close() is called, in which case the CommunicationException still occurs.

    So, to get around this, I've employed a solution that mixes the best of both worlds.

    void IDisposable.Dispose()
    {
        bool success = false;
        try 
        {
            if (State != CommunicationState.Faulted) 
            {
                Close();
                success = true;
            }
        } 
        finally 
        {
            if (!success) 
                Abort();
        }
    }
    
    0 讨论(0)
  • 2020-11-22 00:34

    I've finally found some solid steps towards a clean solution to this problem.

    This custom tool extends WCFProxyGenerator to provide an exception handling proxy. It generates an additional proxy called ExceptionHandlingProxy<T> which inherits ExceptionHandlingProxyBase<T> - the latter of which implements the meat of the proxy's functionality. The result is that you can choose to use the default proxy that inherits ClientBase<T> or ExceptionHandlingProxy<T> which encapsulates managing the lifetime of the channel factory and channel. ExceptionHandlingProxy respects your selections in the Add Service Reference dialog with respect to asynchronous methods and collection types.

    Codeplex has a project called Exception Handling WCF Proxy Generator. It basically installs a new custom tool to Visual Studio 2008, then use this tool to generate the new service proxy (Add service reference). It has some nice functionality to deal with faulted channels, timeouts and safe disposal. There's an excellent video here called ExceptionHandlingProxyWrapper explaining exactly how this works.

    You can safely use the Using statement again, and if the channel is faulted on any request (TimeoutException or CommunicationException), the Wrapper will re-initialize the faulted channel and retry the query. If that fails then it will call the Abort() command and dispose of the proxy and rethrow the Exception. If the service throws a FaultException code it will stop executing, and the proxy will be aborted safely throwing the correct exception as expected.

    0 讨论(0)
  • If you don't need IoC or are using an autogenerated client (Service Reference), then you can simple use a wrapper to manage the closing and let the GC take the clientbase when it is in a safe state that will not throw any exception. The GC will call Dispose in serviceclient, and this will call Close. Since it is alread closed, it cannot cause any damage. I am using this without problems in production code.

    public class AutoCloseWcf : IDisposable
    {
    
        private ICommunicationObject CommunicationObject;
    
        public AutoDisconnect(ICommunicationObject CommunicationObject)
        {
            this.CommunicationObject = CommunicationObject;
        }
    
        public void Dispose()
        {
            if (CommunicationObject == null)
                return;
            try {
                if (CommunicationObject.State != CommunicationState.Faulted) {
                    CommunicationObject.Close();
                } else {
                    CommunicationObject.Abort();
                }
            } catch (CommunicationException ce) {
                CommunicationObject.Abort();
            } catch (TimeoutException toe) {
                CommunicationObject.Abort();
            } catch (Exception e) {
                CommunicationObject.Abort();
                //Perhaps log this
    
            } finally {
                CommunicationObject = null;
            }
        }
    }
    

    Then when you are accessing the server, you create the client and use using in the autodisconect:

    var Ws = new ServiceClient("netTcpEndPointName");
    using (new AutoCloseWcf(Ws)) {
    
        Ws.Open();
    
        Ws.Test();
    }
    
    0 讨论(0)
  • 2020-11-22 00:36

    A wrapper like this would work:

    public class ServiceClientWrapper<ServiceType> : IDisposable
    {
        private ServiceType _channel;
        public ServiceType Channel
        {
            get { return _channel; }
        }
    
        private static ChannelFactory<ServiceType> _channelFactory;
    
        public ServiceClientWrapper()
        {
            if(_channelFactory == null)
                 // Given that the endpoint name is the same as FullName of contract.
                _channelFactory = new ChannelFactory<ServiceType>(typeof(T).FullName);
            _channel = _channelFactory.CreateChannel();
            ((IChannel)_channel).Open();
        }
    
        public void Dispose()
        {
            try
            {
                ((IChannel)_channel).Close();
            }
            catch (Exception e)
            {
                ((IChannel)_channel).Abort();
                // TODO: Insert logging
            }
        }
    }
    

    That should enable you to write code like:

    ResponseType response = null;
    using(var clientWrapper = new ServiceClientWrapper<IService>())
    {
        var request = ...
        response = clientWrapper.Channel.MyServiceCall(request);
    }
    // Use your response object.
    

    The wrapper could of course catch more exceptions if that is required, but the principle remains the same.

    0 讨论(0)
提交回复
热议问题